diff --git a/ChangeLog b/ChangeLog
index c6baf65369..61a1cf18b4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/Makefile.am b/Makefile.am
index cd8fc777b3..8c398422b5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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)\"
diff --git a/Makefile.in b/Makefile.in
index dfeebc4cc8..4184e84f69 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -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)\"
diff --git a/ReleaseNotes b/ReleaseNotes
index f07c6973c3..2f62500cbe 100644
--- a/ReleaseNotes
+++ b/ReleaseNotes
@@ -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
diff --git a/api/Makefile.am b/api/Makefile.am
index 4c149f6102..c8f848cc2d 100644
--- a/api/Makefile.am
+++ b/api/Makefile.am
@@ -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 \
diff --git a/api/Makefile.in b/api/Makefile.in
index f79f728ef8..4353ea80a2 100644
--- a/api/Makefile.in
+++ b/api/Makefile.in
@@ -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:
diff --git a/api/apitypes.h b/api/apitypes.h
new file mode 100644
index 0000000000..3527a9c62c
--- /dev/null
+++ b/api/apitypes.h
@@ -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__
diff --git a/api/baseapi.cpp b/api/baseapi.cpp
index ed1ea6d7ac..d7e6de6435 100644
--- a/api/baseapi.cpp
+++ b/api/baseapi.cpp
@@ -25,38 +25,31 @@
#ifdef HAVE_LIBLEPT
// Include leptonica library only if autoconf (or makefile etc) tell us to.
#include "allheaders.h"
+#else
+#error "Sorry: Tesseract no longer compiles without leptonica!"
#endif
#include "baseapi.h"
+#include "resultiterator.h"
#include "thresholder.h"
#include "tesseractmain.h"
#include "tesseractclass.h"
-#include "tessedit.h"
-#include "ocrclass.h"
#include "pageres.h"
#include "tessvars.h"
#include "control.h"
-#include "applybox.h"
#include "pgedit.h"
-#include "varabled.h"
+#include "paramsd.h"
#include "output.h"
-#include "mainblk.h"
#include "globals.h"
-#include "adaptmatch.h"
#include "edgblob.h"
#include "tessbox.h"
-#include "tordvars.h"
#include "imgs.h"
#include "makerow.h"
#include "tstruct.h"
-#include "tessout.h"
-#include "tface.h"
#include "permute.h"
#include "otsuthr.h"
#include "osdetect.h"
-#include "chopper.h"
-#include "matchtab.h"
namespace tesseract {
@@ -74,17 +67,19 @@ const char* kInputFile = "noname.tif";
TessBaseAPI::TessBaseAPI()
: tesseract_(NULL),
+ osd_tesseract_(NULL),
// Thresholder is initialized to NULL here, but will be set before use by:
// A constructor of a derived API, SetThresholder(), or
// created implicitly when used in InternalSetImage.
thresholder_(NULL),
- threshold_done_(false),
block_list_(NULL),
page_res_(NULL),
input_file_(NULL),
output_file_(NULL),
datapath_(NULL),
language_(NULL),
+ last_oem_requested_(OEM_DEFAULT),
+ recognition_done_(false),
rect_left_(0), rect_top_(0), rect_width_(0), rect_height_(0),
image_width_(0), image_height_(0) {
}
@@ -110,17 +105,49 @@ void TessBaseAPI::SetOutputName(const char* name) {
*output_file_ = 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
-// you would in a config file.
-// Returns false if the name lookup failed.
-// SetVariable may be used before Init, to set things that control
-// initialization, but note that on End all settings are lost and
-// the next Init will use the defaults unless SetVariable is used again.
-bool TessBaseAPI::SetVariable(const char* variable, const char* value) {
- if (tesseract_ == NULL)
- tesseract_ = new Tesseract;
- return set_variable(variable, value);
+bool TessBaseAPI::SetVariable(const char* name, const char* value) {
+ if (tesseract_ == NULL) tesseract_ = new Tesseract;
+ return ParamUtils::SetParam(name, value, false, tesseract_->params());
+}
+
+bool TessBaseAPI::SetVariableIfInit(const char* name, const char* value) {
+ if (tesseract_ == NULL) tesseract_ = new Tesseract;
+ return ParamUtils::SetParam(name, value, true, tesseract_->params());
+}
+
+bool TessBaseAPI::GetIntVariable(const char *name, int *value) const {
+ IntParam *p = ParamUtils::FindParam(
+ name, GlobalParams()->int_params, tesseract_->params()->int_params);
+ if (p == NULL) return false;
+ *value = (inT32)(*p);
+ return true;
+}
+
+bool TessBaseAPI::GetBoolVariable(const char *name, bool *value) const {
+ BoolParam *p = ParamUtils::FindParam(
+ name, GlobalParams()->bool_params, tesseract_->params()->bool_params);
+ if (p == NULL) return false;
+ *value = (BOOL8)(*p);
+ return true;
+}
+
+const char *TessBaseAPI::GetStringVariable(const char *name) const {
+ StringParam *p = ParamUtils::FindParam(
+ name, GlobalParams()->string_params, tesseract_->params()->string_params);
+ return (p != NULL) ? p->string() : NULL;
+}
+
+bool TessBaseAPI::GetDoubleVariable(const char *name, double *value) const {
+ DoubleParam *p = ParamUtils::FindParam(
+ name, GlobalParams()->double_params, tesseract_->params()->double_params);
+ if (p == NULL) return false;
+ *value = (double)(*p);
+ return true;
+}
+
+// Print Tesseract parameters to the given file.
+void TessBaseAPI::PrintVariables(FILE *fp) const {
+ ParamUtils::PrintParams(fp, tesseract_->params());
}
// The datapath must be the name of the data directory (no ending /) or
@@ -130,16 +157,17 @@ bool TessBaseAPI::SetVariable(const char* variable, const char* value) {
// be returned.
// Returns 0 on success and -1 on initialization failure.
int TessBaseAPI::Init(const char* datapath, const char* language,
- char **configs, int configs_size,
- bool configs_global_only) {
- // If the datapath or the language have changed, then start again.
+ OcrEngineMode oem, char **configs, int configs_size,
+ bool configs_init_only) {
+ // If the datapath, OcrEngineMode or the language have changed - start again.
// Note that the language_ field stores the last requested language that was
// initialized successfully, while tesseract_->lang stores the language
// actually used. They differ only if the requested language was NULL, in
// which case tesseract_->lang is set to the Tesseract default ("eng").
if (tesseract_ != NULL &&
- (datapath_ == NULL || language_ == NULL || *datapath_ != datapath
- || (*language_ != language && tesseract_->lang != language))) {
+ (datapath_ == NULL || language_ == NULL ||
+ *datapath_ != datapath || last_oem_requested_ != oem ||
+ (*language_ != language && tesseract_->lang != language))) {
tesseract_->end_tesseract();
delete tesseract_;
tesseract_ = NULL;
@@ -151,7 +179,7 @@ int TessBaseAPI::Init(const char* datapath, const char* language,
tesseract_ = new Tesseract;
if (tesseract_->init_tesseract(
datapath, output_file_ != NULL ? output_file_->string() : NULL,
- language, configs, configs_size, configs_global_only) != 0) {
+ language, oem, configs, configs_size, configs_init_only) != 0) {
return -1;
}
}
@@ -164,6 +192,7 @@ int TessBaseAPI::Init(const char* datapath, const char* language,
language_ = new STRING(language);
else
*language_ = language;
+ last_oem_requested_ = oem;
// For same language and datapath, just reset the adaptive classifier.
if (reset_classifier) tesseract_->ResetAdaptiveClassifier();
@@ -181,46 +210,24 @@ int TessBaseAPI::InitLangMod(const char* datapath, const char* language) {
return tesseract_->init_tesseract_lm(datapath, NULL, language);
}
-// Init only the classifer component of Tesseract. Used to initialize the
-// specified language when no dawg models are available.
-int TessBaseAPI::InitWithoutLangModel(const char* datapath,
- const char* language) {
- // If the datapath or the language have changed, then start again.
- if (tesseract_ != NULL &&
- (datapath_ == NULL || language_ == NULL ||
- *datapath_ != datapath || *language_ != language)) {
- tesseract_->end_tesseract();
- delete tesseract_;
- tesseract_ = NULL;
- }
- if (datapath_ == NULL)
- datapath_ = new STRING(datapath);
- else
- *datapath_ = datapath;
- if (language_ == NULL)
- language_ = new STRING(language);
- else
- *language_ = language;
+// Init only for page layout analysis. Use only for calls to SetImage and
+// AnalysePage. Calls that attempt recognition will generate an error.
+void TessBaseAPI::InitForAnalysePage() {
if (tesseract_ == NULL) {
tesseract_ = new Tesseract;
- return tesseract_->init_tesseract_classifier(
- datapath, output_file_ != NULL ? output_file_->string() : NULL,
- language, NULL, 0, false);
+ tesseract_->InitAdaptiveClassifier(false);
}
- // For same language and datapath, just reset the adaptive classifier.
- tesseract_->ResetAdaptiveClassifier();
- return 0;
}
-// Read a "config" file containing a set of variable, value pairs.
+// Read a "config" file containing a set of parameter name, value pairs.
// Searches the standard places: tessdata/configs, tessdata/tessconfigs
// and also accepts a relative or absolute path name.
-void TessBaseAPI::ReadConfigFile(const char* filename, bool global_only) {
- tesseract_->read_config_file(filename, global_only);
+void TessBaseAPI::ReadConfigFile(const char* filename, bool init_only) {
+ tesseract_->read_config_file(filename, init_only);
}
// Set the current page segmentation mode. Defaults to PSM_AUTO.
-// 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 TessBaseAPI::SetPageSegMode(PageSegMode mode) {
if (tesseract_ == NULL)
@@ -236,21 +243,6 @@ PageSegMode TessBaseAPI::GetPageSegMode() const {
static_cast(tesseract_->tessedit_pageseg_mode));
}
-// 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 TessBaseAPI::SetAccuracyVSpeed(AccuracyVSpeed mode) {
- if (tesseract_ == NULL)
- tesseract_ = new Tesseract;
- tesseract_->tessedit_accuracyvspeed.set_value(mode);
-}
-
// Recognize a rectangle from an image and return the result as a string.
// May be called many times for a single Init.
// Currently has no error checking.
@@ -312,10 +304,8 @@ void TessBaseAPI::SetImage(const unsigned char* imagedata,
// Because of that, an implementation that sources and targets Pix may end up
// with less copies than an implementation that does not.
void TessBaseAPI::SetImage(const Pix* pix) {
-#ifdef HAVE_LIBLEPT
if (InternalSetImage())
thresholder_->SetImage(pix);
-#endif
}
// Restrict recognition to a sub-rectangle of the image. Call after SetImage.
@@ -331,280 +321,224 @@ void TessBaseAPI::SetRectangle(int left, int top, int width, int height) {
// ONLY available if you have Leptonica installed.
// Get a copy of the internal thresholded image from Tesseract.
Pix* TessBaseAPI::GetThresholdedImage() {
-#ifdef HAVE_LIBLEPT
if (tesseract_ == NULL)
return NULL;
if (tesseract_->pix_binary() == NULL)
Threshold(tesseract_->mutable_pix_binary());
return pixClone(tesseract_->pix_binary());
-#else
- return NULL;
-#endif
}
// Get the result of page layout analysis as a leptonica-style
// Boxa, Pixa pair, in reading order.
// Can be called before or after Recognize.
-// For now only gets text regions.
Boxa* TessBaseAPI::GetRegions(Pixa** pixa) {
-#ifdef HAVE_LIBLEPT
- if (block_list_ == NULL || block_list_->empty()) {
- FindLines();
- }
- int im_height = pixGetHeight(tesseract_->pix_binary());
- Boxa* boxa = boxaCreate(block_list_->length());
- if (pixa != NULL) {
- *pixa = pixaCreate(boxaGetCount(boxa));
- }
- BLOCK_IT it(block_list_);
- for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
- BLOCK* block = it.data();
- POLY_BLOCK* poly = block->poly_block();
- TBOX box;
- if (poly != NULL) {
- if (!poly->IsText())
- continue; // Use only text blocks.
- POLY_BLOCK image_block(poly->points(), poly->isA());
- image_block.rotate(block->re_rotation());
- box = *image_block.bounding_box();
- if (pixa != NULL) {
- Pix* pix = pixCreate(box.width(), box.height(), 1);
- PB_LINE_IT *lines;
- // Block outline is a polygon, so use a PC_LINE_IT to get the
- // rasterized interior. (Runs of interior pixels on a line.)
- lines = new PB_LINE_IT(&image_block);
- for (int y = box.bottom(); y < box.top(); ++y) {
- ICOORDELT_LIST* segments = lines->get_line(y);
- if (!segments->empty()) {
- ICOORDELT_IT s_it(segments);
- // Each element of segments is a start x and x size of the
- // run of interior pixels.
- for (s_it.mark_cycle_pt(); !s_it.cycled_list(); s_it.forward()) {
- int start = s_it.data()->x();
- int xext = s_it.data()->y();
- // Copy the run from the source image to the block image.
- pixRasterop(pix, start - box.left(),
- box.height() - 1 - (y - box.bottom()),
- xext, 1, PIX_SRC, tesseract_->pix_binary(),
- start, im_height - 1 - y);
- }
- }
- delete segments;
- }
- delete lines;
- pixaAddPix(*pixa, pix, L_INSERT);
- }
- } else {
- if (!block_list_->singleton())
- continue; // A null poly block can only be used if it is the only block.
- box = block->bounding_box();
- if (pixa != NULL) {
- Pix* pix = pixCreate(box.width(), box.height(), 1);
- // Just copy the whole block as there is only a bounding box.
- pixRasterop(pix, 0, 0, box.width(), box.height(),
- PIX_SRC, tesseract_->pix_binary(),
- box.left(), im_height - box.top());
- pixaAddPix(*pixa, pix, L_INSERT);
- }
- }
- Box* lbox = boxCreate(box.left(), im_height - box.top(),
- box.width(), box.height());
- boxaAddBox(boxa, lbox, L_INSERT);
- }
- return boxa;
-#else
- return NULL;
-#endif
+ return GetComponentImages(RIL_BLOCK, pixa, NULL);
}
-// Get the textlines as a leptonica-style
-// Boxa, Pixa pair, in reading order.
+// 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.
Boxa* TessBaseAPI::GetTextlines(Pixa** pixa, int** blockids) {
-#ifdef HAVE_LIBLEPT
- if (block_list_ == NULL || block_list_->empty()) {
- FindLines();
- }
- // A local PAGE_RES prevents the clear if Recognize is called after.
- PAGE_RES page_res(block_list_);
- PAGE_RES_IT page_res_it(page_res_ != NULL ? page_res_ : &page_res);
- // Count the lines to get a size for the arrays.
- int line_count = 0;
- for (page_res_it.restart_page(); page_res_it.word() != NULL;
- page_res_it.forward()) {
- if (page_res_it.row() != page_res_it.next_row()) {
- ++line_count;
- }
- }
+ return GetComponentImages(RIL_TEXTLINE, pixa, blockids);
+}
- int im_height = pixGetHeight(tesseract_->pix_binary());
- Boxa* boxa = boxaCreate(line_count);
- if (pixa != NULL)
- *pixa = pixaCreate(line_count);
- if (blockids != NULL)
- *blockids = new int[line_count];
- int blockid = 0;
- int lineindex = 0;
- for (page_res_it.restart_page(); page_res_it.word() != NULL;
- page_res_it.forward(), ++lineindex) {
- WERD_RES *word = page_res_it.word();
- BLOCK* block = page_res_it.block()->block;
- // Get the line bounding box.
- PAGE_RES_IT word_it(page_res_it); // Save start of line.
- TBOX line_box = word->word->bounding_box();
- while (page_res_it.next_row() == page_res_it.row()) {
- page_res_it.forward();
- word = page_res_it.word();
- TBOX word_box = word->word->bounding_box();
- word_box.rotate(block->re_rotation());
- line_box += word_box;
- }
- Box* lbox = boxCreate(line_box.left(), im_height - line_box.top(),
- line_box.width(), line_box.height());
- boxaAddBox(boxa, lbox, L_INSERT);
- if (pixa != NULL) {
- Pix* pix = pixCreate(line_box.width(), line_box.height(), 1);
- // Copy all the words to the output pix.
- while (word_it.row() == page_res_it.row()) {
- word = word_it.word();
- TBOX word_box = word->word->bounding_box();
- word_box.rotate(block->re_rotation());
- pixRasterop(pix, word_box.left() - line_box.left(),
- line_box.top() - word_box.top(),
- word_box.width(), word_box.height(),
- PIX_SRC, tesseract_->pix_binary(),
- word_box.left(), im_height - word_box.top());
- word_it.forward();
- }
- pixaAddPix(*pixa, pix, L_INSERT);
- pixaAddBox(*pixa, lbox, L_CLONE);
- }
- if (blockids != NULL) {
- (*blockids)[lineindex] = blockid;
- if (page_res_it.block() != page_res_it.next_block())
- ++blockid;
- }
- }
- return boxa;
-#else
- return NULL;
-#endif
+// 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.
+Boxa* TessBaseAPI::GetConnectedComponents(Pixa** pixa) {
+ return GetComponentImages(RIL_SYMBOL, pixa, NULL);
}
// Get the words as a leptonica-style
// Boxa, Pixa pair, in reading order.
// Can be called before or after Recognize.
Boxa* TessBaseAPI::GetWords(Pixa** pixa) {
-#ifdef HAVE_LIBLEPT
- if (block_list_ == NULL || block_list_->empty()) {
- FindLines();
- }
- // A local PAGE_RES prevents the clear if Recognize is called after.
- PAGE_RES page_res(block_list_);
- PAGE_RES_IT page_res_it(page_res_ != NULL ? page_res_ : &page_res);
- // Count the words to get a size for the arrays.
- int word_count = 0;
- for (page_res_it.restart_page(); page_res_it.word () != NULL;
- page_res_it.forward())
- ++word_count;
+ return GetComponentImages(RIL_WORD, pixa, NULL);
+}
- int im_height = pixGetHeight(tesseract_->pix_binary());
- Boxa* boxa = boxaCreate(word_count);
- if (pixa != NULL) {
- *pixa = pixaCreate(word_count);
- }
- for (page_res_it.restart_page(); page_res_it.word () != NULL;
- page_res_it.forward()) {
- WERD_RES *word = page_res_it.word();
- BLOCK* block = page_res_it.block()->block;
- TBOX box = word->word->bounding_box();
- box.rotate(block->re_rotation());
- Box* lbox = boxCreate(box.left(), im_height - box.top(),
- box.width(), box.height());
- boxaAddBox(boxa, lbox, L_INSERT);
- if (pixa != NULL) {
- Pix* pix = pixCreate(box.width(), box.height(), 1);
- // Copy the whole word bounding box to the output pix.
- pixRasterop(pix, 0, 0, box.width(), box.height(),
- PIX_SRC, tesseract_->pix_binary(),
- box.left(), im_height - box.top());
- pixaAddPix(*pixa, pix, L_INSERT);
- pixaAddBox(*pixa, lbox, L_CLONE);
+// 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* TessBaseAPI::GetComponentImages(PageIteratorLevel level,
+ Pixa** pixa, int** blockids) {
+ PageIterator* page_it = GetIterator();
+ if (page_it == NULL)
+ page_it = AnalyseLayout();
+ if (page_it == NULL)
+ return NULL; // Failed.
+
+ // Count the components to get a size for the arrays.
+ int component_count = 0;
+ int left, top, right, bottom;
+ do {
+ if (page_it->BoundingBox(level, &left, &top, &right, &bottom))
+ ++component_count;
+ } while (page_it->Next(level));
+
+ Boxa* boxa = boxaCreate(component_count);
+ if (pixa != NULL)
+ *pixa = pixaCreate(component_count);
+ if (blockids != NULL)
+ *blockids = new int[component_count];
+
+ int blockid = 0;
+ int component_index = 0;
+ page_it->Begin();
+ do {
+ if (page_it->BoundingBox(level, &left, &top, &right, &bottom)) {
+ Box* lbox = boxCreate(left, top, right - left, bottom - top);
+ boxaAddBox(boxa, lbox, L_INSERT);
+ if (pixa != NULL) {
+ Pix* pix = page_it->GetBinaryImage(level);
+ pixaAddPix(*pixa, pix, L_INSERT);
+ pixaAddBox(*pixa, lbox, L_CLONE);
+ }
+ if (blockids != NULL) {
+ (*blockids)[component_index] = blockid;
+ if (page_it->IsAtFinalElement(RIL_BLOCK, level))
+ ++blockid;
+ }
+ ++component_index;
}
- }
+ } while (page_it->Next(level));
+ delete page_it;
return boxa;
-#else
- return NULL;
-#endif // HAVE_LIBLEPT
}
// Dump the internal binary image to a PGM file.
void TessBaseAPI::DumpPGM(const char* filename) {
if (tesseract_ == NULL)
return;
- IMAGELINE line;
- line.init(page_image.get_xsize());
FILE *fp = fopen(filename, "w");
- fprintf(fp, "P5 " INT32FORMAT " " INT32FORMAT " 255\n",
- page_image.get_xsize(), page_image.get_ysize());
- for (int j = page_image.get_ysize()-1; j >= 0 ; --j) {
- page_image.get_line(0, j, page_image.get_xsize(), &line, 0);
- for (int i = 0; i < page_image.get_xsize(); ++i) {
- uinT8 b = line.pixels[i] ? 255 : 0;
+ Pix* pix = tesseract_->pix_binary();
+ int width = pixGetWidth(pix);
+ int height = pixGetHeight(pix);
+ l_uint32* data = pixGetData(pix);
+ fprintf(fp, "P5 %d %d 255\n", width, height);
+ for (int y = 0; y < height; ++y, data += pixGetWpl(pix)) {
+ for (int x = 0; x < width; ++x) {
+ uinT8 b = GET_DATA_BIT(data, x) ? 0 : 255;
fwrite(&b, 1, 1, fp);
}
}
fclose(fp);
}
+// Placeholder for call to Cube and test that the input data is correct.
+// reskew is the direction of baselines in the skewed image in
+// normalized (cos theta, sin theta) form, so (0.866, 0.5) would represent
+// a 30 degree anticlockwise skew.
+int CubeAPITest(Boxa* boxa_blocks, Pixa* pixa_blocks,
+ Boxa* boxa_words, Pixa* pixa_words,
+ const FCOORD& reskew, Pix* page_pix,
+ PAGE_RES* page_res) {
+ int block_count = boxaGetCount(boxa_blocks);
+ ASSERT_HOST(block_count == pixaGetCount(pixa_blocks));
+ // Write each block to the current directory as junk_write_display.nnn.png.
+ for (int i = 0; i < block_count; ++i) {
+ Pix* pix = pixaGetPix(pixa_blocks, i, L_CLONE);
+ pixDisplayWrite(pix, 1);
+ }
+ int word_count = boxaGetCount(boxa_words);
+ ASSERT_HOST(word_count == pixaGetCount(pixa_words));
+ int pr_word = 0;
+ PAGE_RES_IT page_res_it(page_res);
+ for (page_res_it.restart_page(); page_res_it.word () != NULL;
+ page_res_it.forward(), ++pr_word) {
+ WERD_RES *word = page_res_it.word();
+ WERD_CHOICE* choice = word->best_choice;
+ // Write the first 100 words to files names wordims/.tif.
+ if (pr_word < 100) {
+ STRING filename("wordims/");
+ if (choice != NULL) {
+ filename += choice->unichar_string();
+ } else {
+ char numbuf[32];
+ filename += "unclassified";
+ snprintf(numbuf, 32, "%03d", pr_word);
+ filename += numbuf;
+ }
+ filename += ".tif";
+ Pix* pix = pixaGetPix(pixa_words, pr_word, L_CLONE);
+ pixWrite(filename.string(), pix, IFF_TIFF_G4);
+ }
+ }
+ ASSERT_HOST(pr_word == word_count);
+ return 0;
+}
+
+// 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 or an empty page.
+// 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* TessBaseAPI::AnalyseLayout() {
+ if (FindLines() == 0) {
+ if (block_list_->empty())
+ return NULL; // The page was empty.
+ page_res_ = new PAGE_RES(block_list_, NULL);
+ // TODO(rays) Support transmission of image scaling and resolution through
+ // ImageThresholder, so it can be used here in place of literal 1, 300.
+ return new PageIterator(page_res_, tesseract_, 1, 300,
+ rect_left_, rect_top_, rect_width_, rect_height_);
+ }
+ return NULL;
+}
+
// Recognize the tesseract global image and return the result as Tesseract
// internal structures.
-int TessBaseAPI::Recognize(struct ETEXT_STRUCT* monitor) {
+int TessBaseAPI::Recognize(ETEXT_DESC* monitor) {
if (tesseract_ == NULL)
return -1;
- if (thresholder_ == NULL || thresholder_->IsEmpty()) {
- tprintf("Please call SetImage before attempting recognition.");
- return -1;
- }
- if (page_res_ != NULL)
- ClearResults();
if (FindLines() != 0)
return -1;
- if (tesseract_->tessedit_resegment_from_boxes)
- tesseract_->apply_boxes(*input_file_, block_list_);
- tesseract_->SetBlackAndWhitelist();
+ if (page_res_ != NULL)
+ delete page_res_;
- page_res_ = new PAGE_RES(block_list_);
- int result = 0;
- if (interactive_mode) {
- tesseract_->pgeditor_main(block_list_);
+ tesseract_->SetBlackAndWhitelist();
+ recognition_done_ = true;
+ if (tesseract_->tessedit_resegment_from_line_boxes)
+ page_res_ = tesseract_->ApplyBoxes(*input_file_, true, block_list_);
+ else if (tesseract_->tessedit_resegment_from_boxes)
+ page_res_ = tesseract_->ApplyBoxes(*input_file_, false, block_list_);
+ else
+ page_res_ = new PAGE_RES(block_list_, &tesseract_->prev_word_best_choice_);
+ if (tesseract_->tessedit_make_boxes_from_boxes) {
+ tesseract_->CorrectClassifyWords(page_res_);
+ return 0;
+ }
+ if (tesseract_->interactive_mode) {
+ tesseract_->pgeditor_main(rect_width_, rect_height_, page_res_);
// The page_res is invalid after an interactive session, so cleanup
// in a way that lets us continue to the next page without crashing.
delete page_res_;
page_res_ = NULL;
return -1;
} else if (tesseract_->tessedit_train_from_boxes) {
- apply_box_training(*output_file_, block_list_);
- } else if (tesseract_->global_tessedit_ambigs_training) {
- FILE *ambigs_output_file = tesseract_->init_ambigs_training(*input_file_);
+ tesseract_->ApplyBoxTraining(*output_file_, page_res_);
+ } else if (tesseract_->tessedit_ambigs_training) {
+ FILE *training_output_file = tesseract_->init_recog_training(*input_file_);
// OCR the page segmented into words by tesseract.
- tesseract_->ambigs_training_segmented(
- *input_file_, page_res_, monitor, ambigs_output_file);
- fclose(ambigs_output_file);
+ tesseract_->recog_training_segmented(
+ *input_file_, page_res_, monitor, training_output_file);
+ fclose(training_output_file);
} else {
// Now run the main recognition.
- // Running base tesseract if the inttemp for the current language loaded.
- if (tesseract_->inttemp_loaded_) {
- tesseract_->recog_all_words(page_res_, monitor);
- }
+ tesseract_->recog_all_words(page_res_, monitor, NULL, NULL, 0);
}
- return result;
+ return 0;
}
// Tests the chopper by exhaustively running chop_one_blob.
-int TessBaseAPI::RecognizeForChopTest(struct ETEXT_STRUCT* monitor) {
+int TessBaseAPI::RecognizeForChopTest(ETEXT_DESC* monitor) {
if (tesseract_ == NULL)
return -1;
if (thresholder_ == NULL || thresholder_->IsEmpty()) {
@@ -616,78 +550,43 @@ int TessBaseAPI::RecognizeForChopTest(struct ETEXT_STRUCT* monitor) {
if (FindLines() != 0)
return -1;
// Additional conditions under which chopper test cannot be run
- if (tesseract_->tessedit_train_from_boxes_word_level || interactive_mode)
- return -1;
- ASSERT_HOST(tesseract_->inttemp_loaded_);
+ if (tesseract_->interactive_mode) return -1;
- page_res_ = new PAGE_RES(block_list_);
+ recognition_done_ = true;
- PAGE_RES_IT page_res_it(page_res_);
+ page_res_ = new PAGE_RES(block_list_, &(tesseract_->prev_word_best_choice_));
- tesseract_->tess_matcher = &Tesseract::tess_default_matcher;
- tesseract_->tess_tester = NULL;
- tesseract_->tess_trainer = NULL;
+ PAGE_RES_IT page_res_it(page_res_);
while (page_res_it.word() != NULL) {
WERD_RES *word_res = page_res_it.word();
- WERD *word = word_res->word;
- if (word->cblob_list()->empty()) {
- page_res_it.forward();
- continue;
- }
- WERD *bln_word = make_bln_copy(word, page_res_it.row()->row,
- page_res_it.block()->block,
- word_res->x_height, &word_res->denorm);
- ASSERT_HOST(!bln_word->blob_list()->empty());
- TWERD *tessword = make_tess_word(bln_word, NULL);
- if (tessword->blobs == NULL) {
- make_tess_word(bln_word, NULL);
- }
- TBLOB *pblob;
- TBLOB *blob;
- init_match_table();
- BLOB_CHOICE_LIST *match_result;
- BLOB_CHOICE_LIST_VECTOR *char_choices = new BLOB_CHOICE_LIST_VECTOR();
- tesseract_->tess_denorm = &word_res->denorm;
- tesseract_->tess_word = bln_word;
- ASSERT_HOST(tessword->blobs != NULL);
- for (blob = tessword->blobs, pblob = NULL;
- blob != NULL; blob = blob->next) {
- match_result = tesseract_->classify_blob(pblob, blob, blob->next, NULL,
- "chop_word:", Green);
- if (match_result == NULL)
- tprintf("Null classifier output!\n");
- tesseract_->modify_blob_choice(match_result, 0);
- ASSERT_HOST(!match_result->empty());
- *char_choices += match_result;
- pblob = blob;
- }
- inT32 blob_number;
- SEAMS seam_list = start_seam_list(tessword->blobs);
- int right_chop_index = 0;
- while (tesseract_->chop_one_blob(tessword, char_choices,
- &blob_number, &seam_list,
- &right_chop_index)) {
- }
-
- word_res->best_choice = new WERD_CHOICE();
- word_res->raw_choice = new WERD_CHOICE();
- word_res->best_choice->make_bad();
- word_res->raw_choice->make_bad();
- tesseract_->getDict().permute_characters(*char_choices, 1000.0,
- word_res->best_choice,
- word_res->raw_choice);
-
- word_res->outword = make_ed_word(tessword, bln_word);
+ tesseract_->MaximallyChopWord(page_res_it.block()->block,
+ page_res_it.row()->row,
+ word_res);
page_res_it.forward();
}
return 0;
}
+// 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* TessBaseAPI::GetIterator() {
+ if (tesseract_ == NULL || page_res_ == NULL)
+ return NULL;
+ // TODO(rays) Support transmission of image scaling and resolution through
+ // ImageThresholder, so it can be used here in place of literal 1, 300.
+ return new ResultIterator(page_res_, tesseract_, 1, 300,
+ rect_left_, rect_top_, rect_width_, rect_height_);
+}
+
// Make a text string from the internal data structures.
char* TessBaseAPI::GetUTF8Text() {
if (tesseract_ == NULL ||
- (page_res_ == NULL && Recognize(NULL) < 0))
+ (!recognition_done_ && Recognize(NULL) < 0))
return NULL;
int total_length = TextLength(NULL);
PAGE_RES_IT page_res_it(page_res_);
@@ -755,9 +654,9 @@ static void AddBoxTohOCR(const TBOX& box, int image_height, STRING* hocr_str) {
// Make a HTML-formatted string with hOCR markup from the internal
// data structures.
+// page_number is 0-based but will appear in the output as 1-based.
// STL removed from original patch submission and refactored by rays.
-// page_id is 1-based and will appear in the output.
-char* TessBaseAPI::GetHOCRText(int page_id) {
+char* TessBaseAPI::GetHOCRText(int page_number) {
if (tesseract_ == NULL ||
(page_res_ == NULL && Recognize(NULL) < 0))
return NULL;
@@ -768,6 +667,7 @@ char* TessBaseAPI::GetHOCRText(int page_id) {
BLOCK_RES *block = NULL; // current row
BLOCK *real_block = NULL;
int lcnt = 1, bcnt = 1, wcnt = 1;
+ int page_id = page_number + 1; // hOCR uses 1-based page numbers.
STRING hocr_str;
@@ -782,13 +682,12 @@ char* TessBaseAPI::GetHOCRText(int page_id) {
for (page_res_it.restart_page(); page_res_it.word () != NULL;
page_res_it.forward()) {
- if (block != page_res_it.block ()) {
-
+ if (block != page_res_it.block()) {
if (block != NULL) {
hocr_str += "\n
\n\n";
}
- block = page_res_it.block (); // current row
+ block = page_res_it.block(); // current row
real_block = block->block;
real_row = NULL;
row = NULL;
@@ -798,14 +697,13 @@ char* TessBaseAPI::GetHOCRText(int page_id) {
AddBoxTohOCR(real_block->bounding_box(), image_height_, &hocr_str);
hocr_str += "\n\n";
}
- if (row != page_res_it.row ()) {
-
+ if (row != page_res_it.row()) {
if (row != NULL) {
hocr_str += "\n";
}
prev_row = real_row;
- row = page_res_it.row (); // current row
+ row = page_res_it.row(); // current row
real_row = row->row;
if (prev_row != NULL &&
@@ -832,18 +730,18 @@ char* TessBaseAPI::GetHOCRText(int page_id) {
if (word->bold > 0)
hocr_str += "";
if (word->italic > 0)
- hocr_str += "";
- int i;
- // escape special characters
- for (i = 0;
- choice->unichar_string()[i] != '\0';
- i++) {
- if (choice->unichar_string()[i] == '<') { hocr_str += "<"; }
- else if (choice->unichar_string()[i] == '>') { hocr_str += ">"; }
- else if (choice->unichar_string()[i] == '&') { hocr_str += "&"; }
- else if (choice->unichar_string()[i] == '"') { hocr_str += """; }
- else if (choice->unichar_string()[i] == '\'') { hocr_str += "'"; }
- else { hocr_str += choice->unichar_string()[i]; }
+ hocr_str += "";
+ int i;
+ // escape special characters
+ for (i = 0;
+ choice->unichar_string()[i] != '\0';
+ i++) {
+ if (choice->unichar_string()[i] == '<') { hocr_str += "<"; }
+ else if (choice->unichar_string()[i] == '>') { hocr_str += ">"; }
+ else if (choice->unichar_string()[i] == '&') { hocr_str += "&"; }
+ else if (choice->unichar_string()[i] == '"') { hocr_str += """; }
+ else if (choice->unichar_string()[i] == '\'') { hocr_str += "'"; }
+ else { hocr_str += choice->unichar_string()[i]; }
}
if (word->italic > 0)
hocr_str += "";
@@ -854,10 +752,10 @@ char* TessBaseAPI::GetHOCRText(int page_id) {
hocr_str += " ";
}
}
- if (block != NULL)
- hocr_str += "\n
\n\n";
- hocr_str += "\n";
-
+ if (block != NULL)
+ hocr_str += "\n\n\n";
+ hocr_str += "\n";
+
char *ret = new char[hocr_str.length() + 1];
strcpy(ret, hocr_str.string());
return ret;
@@ -872,30 +770,25 @@ static int ConvertWordToBoxText(WERD_RES *word,
int page_number,
char* word_str) {
// Copy the output word and denormalize it back to image coords.
- WERD copy_outword;
- copy_outword = *(word->outword);
- copy_outword.baseline_denormalise(&word->denorm);
- PBLOB_IT blob_it;
- blob_it.set_to_list(copy_outword.blob_list());
- int length = copy_outword.blob_list()->length();
+ // Can box_word be NULL?
+ ASSERT_HOST(word->box_word != NULL);
+ int length = word->box_word->length();
int output_size = 0;
if (length > 0) {
for (int index = 0, offset = 0; index < length;
- offset += word->best_choice->unichar_lengths()[index++],
- blob_it.forward()) {
- PBLOB* blob = blob_it.data();
- TBOX blob_box = blob->bounding_box();
+ offset += word->best_choice->unichar_lengths()[index++]) {
+ TBOX blob_box = word->box_word->BlobBox(index);
if (word->tess_failed ||
blob_box.left() < 0 ||
blob_box.right() > image_width ||
blob_box.bottom() < 0 ||
blob_box.top() > image_height) {
// Bounding boxes can be illegal when tess fails on a word.
- blob_box = word->word->bounding_box(); // Use original word as backup.
- tprintf("Using substitute bounding box at (%d,%d)->(%d,%d)\n",
- blob_box.left(), blob_box.bottom(),
- blob_box.right(), blob_box.top());
+ blob_box -= word->word->bounding_box(); // Intersect with original.
+ if (blob_box.null_box()) {
+ blob_box = word->word->bounding_box(); // Use original as backup.
+ }
}
// A single classification unit can be composed of several UTF-8
@@ -919,14 +812,24 @@ static int ConvertWordToBoxText(WERD_RES *word,
return output_size;
}
-// Multiplier for max expected textlength assumes typically 5 numbers @
-// (5 digits and a space) plus the newline = 5*(5+1)+1. Add to this the
-// orginal UTF8 characters, and one kMaxCharsPerChar.
-const int kCharsPerChar = 31;
-// A maximal single box could occupy 5 numbers at 20 digits (for 64 bit) and a
-// space plus the newline 5*(20+1)+1 and the maximum length of a UNICHAR.
+// The 5 numbers output for each box (the usual 4 and a page number.)
+const int kNumbersPerBlob = 5;
+// The number of bytes taken by each number. Since we use inT16 for ICOORD,
+// assume only 5 digits max.
+const int kBytesPerNumber = 5;
+// Multiplier for max expected textlength assumes (kBytesPerNumber + space)
+// * kNumbersPerBlob plus the newline. Add to this the
+// original UTF8 characters, and one kMaxBytesPerLine for safety.
+const int kBytesPerBlob = kNumbersPerBlob * (kBytesPerNumber + 1) + 1;
+const int kBytesPerBoxFileLine = (kBytesPerNumber + 1) * kNumbersPerBlob + 1;
+// Max bytes in the decimal representation of inT64.
+const int kBytesPer64BitNumber = 20;
+// A maximal single box could occupy kNumbersPerBlob numbers at
+// kBytesPer64BitNumber digits (if someone sneaks in a 64 bit value) and a
+// space plus the newline and the maximum length of a UNICHAR.
// Test against this on each iteration for safety.
-const int kMaxCharsPerChar = 106 + UNICHAR_LEN;
+const int kMaxBytesPerLine = kNumbersPerBlob * (kBytesPer64BitNumber + 1) + 1 +
+ UNICHAR_LEN;
// The recognized text is returned as a char* which is coded
// as a UTF8 box file and must be freed with the delete [] operator.
@@ -934,11 +837,12 @@ const int kMaxCharsPerChar = 106 + UNICHAR_LEN;
char* TessBaseAPI::GetBoxText(int page_number) {
int bottom = image_height_ - (rect_top_ + rect_height_);
if (tesseract_ == NULL ||
- (page_res_ == NULL && Recognize(NULL) < 0))
+ (!recognition_done_ && Recognize(NULL) < 0))
return NULL;
int blob_count;
int utf8_length = TextLength(&blob_count);
- int total_length = blob_count*kCharsPerChar + utf8_length + kMaxCharsPerChar;
+ int total_length = blob_count * kBytesPerBoxFileLine + utf8_length +
+ kMaxBytesPerLine;
PAGE_RES_IT page_res_it(page_res_);
char* result = new char[total_length];
char* ptr = result;
@@ -949,7 +853,7 @@ char* TessBaseAPI::GetBoxText(int page_number) {
image_width_, image_height_,
page_number, ptr);
// Just in case...
- if (ptr - result + kMaxCharsPerChar > total_length)
+ if (ptr - result + kMaxBytesPerLine > total_length)
break;
}
*ptr = '\0';
@@ -972,7 +876,7 @@ const int kLatinChs[] = {
// and must be freed with the delete [] operator.
char* TessBaseAPI::GetUNLVText() {
if (tesseract_ == NULL ||
- (page_res_ == NULL && Recognize(NULL) < 0))
+ (!recognition_done_ && Recognize(NULL) < 0))
return NULL;
bool tilde_crunch_written = false;
bool last_char_was_newline = true;
@@ -1012,10 +916,6 @@ char* TessBaseAPI::GetUNLVText() {
} else {
// NORMAL PROCESSING of non tilde crunched words.
tilde_crunch_written = false;
-
- if (word->word->flag(W_REP_CHAR) && tessedit_consistent_reps)
- ensure_rep_chars_are_consistent(word);
-
tesseract_->set_unlv_suspects(word);
const char* wordstr = word->best_choice->unichar_string().string();
const STRING& lengths = word->best_choice->unichar_lengths();
@@ -1090,7 +990,7 @@ int TessBaseAPI::MeanTextConf() {
// Returns an array of all word confidences, terminated by -1.
int* TessBaseAPI::AllWordConfidences() {
if (tesseract_ == NULL ||
- (page_res_ == NULL && Recognize(NULL) < 0))
+ (!recognition_done_ && Recognize(NULL) < 0))
return NULL;
int n_word = 0;
PAGE_RES_IT res_it(page_res_);
@@ -1120,7 +1020,6 @@ void TessBaseAPI::Clear() {
if (thresholder_ != NULL)
thresholder_->Clear();
ClearResults();
- page_image.destroy();
}
// Close down tesseract and free up all memory. End() is equivalent to
@@ -1143,8 +1042,15 @@ void TessBaseAPI::End() {
if (tesseract_ != NULL) {
tesseract_->end_tesseract();
delete tesseract_;
+ if (osd_tesseract_ == tesseract_)
+ osd_tesseract_ = NULL;
tesseract_ = NULL;
}
+ if (osd_tesseract_ != NULL) {
+ osd_tesseract_->end_tesseract();
+ delete osd_tesseract_;
+ osd_tesseract_ = NULL;
+ }
if (input_file_ != NULL) {
delete input_file_;
input_file_ = NULL;
@@ -1197,13 +1103,20 @@ bool TessBaseAPI::GetTextDirection(int* out_offset, float* out_slope) {
return true;
}
-// Set the letter_is_okay function to point somewhere else.
+// Sets Dict::letter_is_okay_ function to point to the given function.
void TessBaseAPI::SetDictFunc(DictFunc f) {
if (tesseract_ != NULL) {
tesseract_->getDict().letter_is_okay_ = f;
}
}
+// Sets Dict::probability_in_context_ function to point to the given function.
+void TessBaseAPI::SetProbabilityInContextFunc(ProbabilityInContextFunc f) {
+ if (tesseract_ != NULL) {
+ tesseract_->getDict().probability_in_context_ = f;
+ }
+}
+
// Common code for setting the image.
bool TessBaseAPI::InternalSetImage() {
if (tesseract_ == NULL) {
@@ -1216,57 +1129,83 @@ bool TessBaseAPI::InternalSetImage() {
return true;
}
-// Run the thresholder to make the thresholded image. If pix is not NULL,
-// the source is thresholded to pix instead of the internal IMAGE.
+// Run the thresholder to make the thresholded image, returned in pix,
+// which must not be NULL. *pix must be initialized to NULL, or point
+// to an existing pixDestroyable Pix.
+// The usual argument to Threshold is Tesseract::mutable_pix_binary().
void TessBaseAPI::Threshold(Pix** pix) {
-#ifdef HAVE_LIBLEPT
- if (pix != NULL)
- thresholder_->ThresholdToPix(pix);
- else
- thresholder_->ThresholdToIMAGE(&page_image);
-#else
- thresholder_->ThresholdToIMAGE(&page_image);
-#endif
+ ASSERT_HOST(pix != NULL);
+ if (!thresholder_->IsBinary()) {
+ tesseract_->set_pix_grey(thresholder_->GetPixRectGrey());
+ }
+ if (*pix != NULL)
+ pixDestroy(pix);
+ thresholder_->ThresholdToPix(pix);
thresholder_->GetImageSizes(&rect_left_, &rect_top_,
&rect_width_, &rect_height_,
&image_width_, &image_height_);
- threshold_done_ = true;
}
// Find lines from the image making the BLOCK_LIST.
int TessBaseAPI::FindLines() {
+ if (thresholder_ == NULL || thresholder_->IsEmpty()) {
+ tprintf("Please call SetImage before attempting recognition.");
+ return -1;
+ }
+ if (recognition_done_)
+ ClearResults();
if (!block_list_->empty()) {
return 0;
}
if (tesseract_ == NULL) {
tesseract_ = new Tesseract;
- tesseract_->InitAdaptiveClassifier();
+ tesseract_->InitAdaptiveClassifier(false);
}
-#ifdef HAVE_LIBLEPT
if (tesseract_->pix_binary() == NULL)
Threshold(tesseract_->mutable_pix_binary());
-#endif
- if (!threshold_done_)
- Threshold(NULL);
+ if (tesseract_->ImageWidth() > MAX_INT16 ||
+ tesseract_->ImageHeight() > MAX_INT16) {
+ tprintf("Image too large: (%d, %d)\n",
+ tesseract_->ImageWidth(), tesseract_->ImageHeight());
+ return -1;
+ }
- if (tesseract_->SegmentPage(input_file_, &page_image, block_list_) < 0)
+ Tesseract* osd_tess = osd_tesseract_;
+ OSResults osr;
+ if (PSM_OSD_ENABLED(tesseract_->tessedit_pageseg_mode) && osd_tess == NULL) {
+ if (strcmp(language_->string(), "osd") == 0) {
+ osd_tess = tesseract_;
+ } else {
+ osd_tesseract_ = new Tesseract;
+ if (osd_tesseract_->init_tesseract(
+ datapath_->string(), NULL, "osd", OEM_TESSERACT_ONLY,
+ NULL, 0, false) == 0) {
+ osd_tess = osd_tesseract_;
+ } else {
+ tprintf("Warning: Auto orientation and script detection requested,"
+ " but osd language failed to load\n");
+ delete osd_tesseract_;
+ osd_tesseract_ = NULL;
+ }
+ }
+ }
+
+ if (tesseract_->SegmentPage(input_file_, block_list_, osd_tess, &osr) < 0)
return -1;
- ASSERT_HOST(page_image.get_xsize() == rect_width_ ||
- page_image.get_xsize() == rect_width_ - 1);
- ASSERT_HOST(page_image.get_ysize() == rect_height_ ||
- page_image.get_ysize() == rect_height_ - 1);
return 0;
}
// Delete the pageres and clear the block list ready for a new page.
void TessBaseAPI::ClearResults() {
- threshold_done_ = false;
- if (tesseract_ != NULL)
+ if (tesseract_ != NULL) {
tesseract_->Clear();
+ tesseract_->ResetFeaturesHaveBeenExtracted();
+ }
if (page_res_ != NULL) {
delete page_res_;
page_res_ = NULL;
}
+ recognition_done_ = false;
if (block_list_ == NULL)
block_list_ = new BLOCK_LIST;
else
@@ -1309,12 +1248,73 @@ bool TessBaseAPI::DetectOS(OSResults* osr) {
if (tesseract_ == NULL)
return false;
ClearResults();
- Threshold(NULL);
+ if (tesseract_->pix_binary() == NULL)
+ Threshold(tesseract_->mutable_pix_binary());
if (input_file_ == NULL)
input_file_ = new STRING(kInputFile);
return orientation_and_script_detection(*input_file_, osr, tesseract_);
}
+void TessBaseAPI::set_min_orientation_margin(double margin) {
+ tesseract_->min_orientation_margin.set_value(margin);
+}
+
+// Return text orientation of each block as determined in an earlier page layout
+// analysis operation. Orientation is returned as the number of ccw 90-degree
+// rotations (in [0..3]) required to make the text in the block upright
+// (readable). Note that this may not necessary be the block orientation
+// preferred for recognition (such as the case of vertical CJK text).
+//
+// Also returns whether the text in the block is believed to have vertical
+// writing direction (when in an upright page orientation).
+//
+// The returned array is of length equal to the number of text blocks, which may
+// be less than the total number of blocks. The ordering is intended to be
+// consistent with GetTextLines().
+void TessBaseAPI::GetBlockTextOrientations(int** block_orientation,
+ bool** vertical_writing) {
+ delete[] *block_orientation;
+ *block_orientation = NULL;
+ delete[] *vertical_writing;
+ *vertical_writing = NULL;
+ BLOCK_IT block_it(block_list_);
+
+ block_it.move_to_first();
+ int num_blocks = 0;
+ for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
+ if (!block_it.data()->poly_block()->IsText()) {
+ continue;
+ }
+ ++num_blocks;
+ }
+ if (!num_blocks) {
+ tprintf("WARNING: Found no blocks\n");
+ return;
+ }
+ *block_orientation = new int[num_blocks];
+ *vertical_writing = new bool[num_blocks];
+ block_it.move_to_first();
+ int i = 0;
+ for (block_it.mark_cycle_pt(); !block_it.cycled_list();
+ block_it.forward()) {
+ if (!block_it.data()->poly_block()->IsText()) {
+ continue;
+ }
+ FCOORD re_rotation = block_it.data()->re_rotation();
+ float re_theta = re_rotation.angle();
+ FCOORD classify_rotation = block_it.data()->classify_rotation();
+ float classify_theta = classify_rotation.angle();
+ double rot_theta = - (re_theta - classify_theta) * 2.0 / PI;
+ if (rot_theta < 0) rot_theta += 4;
+ int num_rotations = static_cast(rot_theta + 0.5);
+ (*block_orientation)[i] = num_rotations;
+ // The classify_rotation is non-zero only if the text has vertical
+ // writing direction.
+ (*vertical_writing)[i] = classify_rotation.y() != 0.0f;
+ ++i;
+ }
+}
+
// ____________________________________________________________________________
// Ocropus add-ons.
@@ -1334,10 +1334,10 @@ void TessBaseAPI::DeleteBlockList(BLOCK_LIST *block_list) {
}
-static ROW *make_tess_ocrrow(float baseline,
- float xheight,
- float descender,
- float ascender) {
+ROW *TessBaseAPI::MakeTessOCRRow(float baseline,
+ float xheight,
+ float descender,
+ float ascender) {
inT32 xstarts[] = {-32000};
double quad_coeffs[] = {0, 0, baseline};
return new ROW(1,
@@ -1350,72 +1350,62 @@ static ROW *make_tess_ocrrow(float baseline,
0);
}
-// Almost a copy of make_tess_row() from ccmain/tstruct.cpp.
-static void fill_dummy_row(float baseline, float xheight,
- float descender, float ascender,
- TEXTROW* tessrow) {
- 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_baseline_offset + bln_x_height;
- tessrow->lineheight = bln_x_height;
- tessrow->ascrise = bln_x_height * (ascender - (xheight + baseline)) / xheight;
- tessrow->descdrop = bln_x_height * (descender - baseline) / xheight;
-}
-
-
-// Return a TBLOB * from the whole page_image.
-// To be freed later with free_blob().
-TBLOB *make_tesseract_blob(float baseline, float xheight,
- float descender, float ascender) {
- BLOCK *block = new BLOCK("a character",
- TRUE,
- 0, 0,
- 0, 0,
- page_image.get_xsize(),
- page_image.get_ysize());
+// Creates a TBLOB* from the whole pix.
+TBLOB *TessBaseAPI::MakeTBLOB(Pix *pix) {
+ int width = pixGetWidth(pix);
+ int height = pixGetHeight(pix);
+ BLOCK block("a character", TRUE, 0, 0, 0, 0, width, height);
// Create C_BLOBs from the page
- extract_edges(NULL, &page_image, &page_image,
- ICOORD(page_image.get_xsize(), page_image.get_ysize()),
- block);
+ extract_edges(pix, &block);
- // Create one PBLOB from all C_BLOBs
- C_BLOB_LIST *list = block->blob_list();
+ // Merge all C_BLOBs
+ C_BLOB_LIST *list = block.blob_list();
C_BLOB_IT c_blob_it(list);
- PBLOB *pblob = new PBLOB; // will be (hopefully) deleted by the pblob_list
- for (c_blob_it.mark_cycle_pt();
- !c_blob_it.cycled_list();
+ if (c_blob_it.empty())
+ return NULL;
+ // Move all the outlines to the first blob.
+ C_OUTLINE_IT ol_it(c_blob_it.data()->out_list());
+ for (c_blob_it.forward();
+ !c_blob_it.at_first();
c_blob_it.forward()) {
C_BLOB *c_blob = c_blob_it.data();
- PBLOB c_as_p(c_blob, baseline + xheight);
- merge_blobs(pblob, &c_as_p);
+ ol_it.add_list_after(c_blob->out_list());
}
- PBLOB_LIST *pblob_list = new PBLOB_LIST; // will be deleted by the word
- PBLOB_IT pblob_it(pblob_list);
- pblob_it.add_after_then_move(pblob);
-
- // Normalize PBLOB
- WERD word(pblob_list, 0, " ");
- ROW *row = make_tess_ocrrow(baseline, xheight, descender, ascender);
- word.baseline_normalise(row);
- delete row;
+ // Convert the first blob to the output TBLOB.
+ return TBLOB::PolygonalCopy(c_blob_it.data());
+}
- // Create a TBLOB from PBLOB
- return make_tess_blob(pblob, /* flatten: */ TRUE);
+// 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.
+void TessBaseAPI::NormalizeTBLOB(TBLOB *tblob, ROW *row,
+ bool numeric_mode, DENORM *denorm) {
+ TWERD word;
+ word.blobs = tblob;
+ word.Normalize(row, row->x_height(), numeric_mode, denorm);
+ word.blobs = NULL;
+}
+
+// Return a TBLOB * from the whole pix.
+// To be freed later with delete.
+TBLOB *make_tesseract_blob(float baseline, float xheight,
+ float descender, float ascender,
+ bool numeric_mode, Pix* pix) {
+ TBLOB *tblob = TessBaseAPI::MakeTBLOB(pix);
+
+ // Normalize TBLOB
+ ROW *row =
+ TessBaseAPI::MakeTessOCRRow(baseline, xheight, descender, ascender);
+ TessBaseAPI::NormalizeTBLOB(tblob, row, numeric_mode, NULL);
+ delete row;
+ return tblob;
}
// Adapt to recognize the current image as the given character.
-// The image must be preloaded and be just an image of a single character.
+// The image must be preloaded into pix_binary_ and be just an image
+// of a single character.
void TessBaseAPI::AdaptToCharacter(const char *unichar_repr,
int length,
float baseline,
@@ -1423,12 +1413,9 @@ void TessBaseAPI::AdaptToCharacter(const char *unichar_repr,
float descender,
float ascender) {
UNICHAR_ID id = tesseract_->unicharset.unichar_to_id(unichar_repr, length);
- LINE_STATS LineStats;
- TEXTROW row;
- fill_dummy_row(baseline, xheight, descender, ascender, &row);
- GetLineStatsFromRow(&row, &LineStats);
-
- TBLOB *blob = make_tesseract_blob(baseline, xheight, descender, ascender);
+ TBLOB *blob = make_tesseract_blob(baseline, xheight, descender, ascender,
+ tesseract_->classify_bln_numeric_mode,
+ tesseract_->pix_binary());
float threshold;
UNICHAR_ID best_class = 0;
float best_rating = -100;
@@ -1436,7 +1423,9 @@ void TessBaseAPI::AdaptToCharacter(const char *unichar_repr,
// Classify to get a raw choice.
BLOB_CHOICE_LIST choices;
- tesseract_->AdaptiveClassifier(blob, NULL, &row, &choices, NULL);
+ DENORM denorm;
+ tesseract_->set_denorm(&denorm);
+ tesseract_->AdaptiveClassifier(blob, &choices, NULL);
BLOB_CHOICE_IT choice_it;
choice_it.set_to_list(&choices);
for (choice_it.mark_cycle_pt(); !choice_it.cycled_list();
@@ -1448,13 +1437,13 @@ void TessBaseAPI::AdaptToCharacter(const char *unichar_repr,
}
if (id == best_class) {
- threshold = matcher_good_threshold;
+ threshold = tesseract_->matcher_good_threshold;
} else {
/* the blob was incorrectly classified - find the rating threshold
needed to create a template which will correct the error with
some margin. However, don't waste time trying to make
templates which are too tight. */
- threshold = tesseract_->GetBestRatingFor(blob, &LineStats, id);
+ threshold = tesseract_->GetBestRatingFor(blob, id);
threshold *= .9;
const float max_threshold = .125;
const float min_threshold = .02;
@@ -1469,22 +1458,24 @@ void TessBaseAPI::AdaptToCharacter(const char *unichar_repr,
}
if (blob->outlines)
- tesseract_->AdaptToChar(blob, &LineStats, id, threshold);
- free_blob(blob);
+ tesseract_->AdaptToChar(blob, id, threshold);
+ delete blob;
}
PAGE_RES* TessBaseAPI::RecognitionPass1(BLOCK_LIST* block_list) {
- PAGE_RES *page_res = new PAGE_RES(block_list);
- tesseract_->recog_all_words(page_res, NULL, NULL, 1);
+ PAGE_RES *page_res = new PAGE_RES(block_list,
+ &(tesseract_->prev_word_best_choice_));
+ tesseract_->recog_all_words(page_res, NULL, NULL, NULL, 1);
return page_res;
}
PAGE_RES* TessBaseAPI::RecognitionPass2(BLOCK_LIST* block_list,
PAGE_RES* pass1_result) {
if (!pass1_result)
- pass1_result = new PAGE_RES(block_list);
- tesseract_->recog_all_words(pass1_result, NULL, NULL, 2);
+ pass1_result = new PAGE_RES(block_list,
+ &(tesseract_->prev_word_best_choice_));
+ tesseract_->recog_all_words(pass1_result, NULL, NULL, NULL, 2);
return pass1_result;
}
@@ -1517,7 +1508,7 @@ static void add_space(TESS_CHAR_IT* it) {
static float rating_to_cost(float rating) {
- rating = 100 + 5*rating;
+ rating = 100 + rating;
// cuddled that to save from coverage profiler
// (I have never seen ratings worse than -100,
// but the check won't hurt)
@@ -1536,54 +1527,19 @@ static void extract_result(TESS_CHAR_IT* out,
WERD_RES *word = page_res_it.word();
const char *str = word->best_choice->unichar_string().string();
const char *len = word->best_choice->unichar_lengths().string();
+ TBOX real_rect = word->word->bounding_box();
if (word_count)
add_space(out);
- TBOX bln_rect;
- PBLOB_LIST *blobs = word->outword->blob_list();
- PBLOB_IT it(blobs);
int n = strlen(len);
- TBOX** boxes_to_fix = new TBOX*[n];
for (int i = 0; i < n; i++) {
- PBLOB *blob = it.data();
- TBOX current = blob->bounding_box();
- bln_rect = bln_rect.bounding_union(current);
- TESS_CHAR *tc = new TESS_CHAR(rating_to_cost(word->best_choice->certainty()),
+ TESS_CHAR *tc = new TESS_CHAR(rating_to_cost(word->best_choice->rating()),
str, *len);
- tc->box = current;
- boxes_to_fix[i] = &tc->box;
-
+ tc->box = real_rect.intersection(word->box_word->BlobBox(i));
out->add_after_then_move(tc);
- it.forward();
- str += *len;
+ str += *len;
len++;
}
-
- // Find the word bbox before normalization.
- // Here we can't use the C_BLOB bboxes directly,
- // since connected letters are not yet cut.
- TBOX real_rect = word->word->bounding_box();
-
- // Denormalize boxes by transforming the bbox of the whole bln word
- // into the denorm bbox (`real_rect') of the whole word.
- double x_stretch = static_cast(real_rect.width())
- / bln_rect.width();
- double y_stretch = static_cast(real_rect.height())
- / bln_rect.height();
- for (int j = 0; j < n; j++) {
- TBOX *box = boxes_to_fix[j];
- int x0 = static_cast(real_rect.left() +
- x_stretch * (box->left() - bln_rect.left()) + 0.5);
- int x1 = static_cast(real_rect.left() +
- x_stretch * (box->right() - bln_rect.left()) + 0.5);
- int y0 = static_cast(real_rect.bottom() +
- y_stretch * (box->bottom() - bln_rect.bottom()) + 0.5);
- int y1 = static_cast(real_rect.bottom() +
- y_stretch * (box->top() - bln_rect.bottom()) + 0.5);
- *box = TBOX(ICOORD(x0, y0), ICOORD(x1, y1));
- }
- delete [] boxes_to_fix;
-
page_res_it.forward();
word_count++;
}
@@ -1637,91 +1593,77 @@ int TessBaseAPI::TesseractExtractResult(char** text,
return n;
}
-// This method returns the features associated with the current image.
-// Make sure setimage has been called before calling this method.
-void TessBaseAPI::GetFeatures(INT_FEATURE_ARRAY int_features,
- int* num_features) {
- if (page_res_ != NULL)
- ClearResults();
- if (!threshold_done_)
- Threshold(NULL);
- // We have only one block, which is of the size of the page.
- BLOCK_LIST* blocks = new BLOCK_LIST;
- BLOCK *block = new BLOCK("", // filename.
- TRUE, // proportional.
- 0, // kerning.
- 0, // spacing.
- 0, // Left.
- 0, // Bottom.
- page_image.get_xsize(), // Right.
- page_image.get_ysize()); // Top.
- ICOORD bleft, tright;
- block->bounding_box (bleft, tright);
-
- BLOCK_IT block_it_add = blocks;
- block_it_add.add_to_end(block);
-
- ICOORD page_tr(page_image.get_xsize(), page_image.get_ysize());
- TEXTROW tessrow;
- make_tess_row(NULL, // Denormalizer.
- &tessrow); // Output row.
- LINE_STATS line_stats;
- GetLineStatsFromRow(&tessrow, &line_stats);
-
- // Perform a CC analysis to detect the blobs.
- BLOCK_IT block_it = blocks;
- for (block_it.mark_cycle_pt (); !block_it.cycled_list ();
- block_it.forward ()) {
- BLOCK* block = block_it.data();
-#ifndef GRAPHICS_DISABLED
- extract_edges(NULL, // Scrollview window.
- &page_image, // Image.
- &page_image, // Thresholded image.
- page_tr, // corner of page.
- block); // block.
-#else
- extract_edges(&page_image, // Image.
- &page_image, // Thresholded image.
- page_tr, // corner of page.
- block); // block.
-#endif
- C_BLOB_IT blob_it = block->blob_list();
- PBLOB *pblob = new PBLOB;
- // Iterate over all blobs found and get their features.
- for (blob_it.mark_cycle_pt(); !blob_it.cycled_list();
- blob_it.forward()) {
- C_BLOB* blob = blob_it.data();
- blob = blob;
- PBLOB c_as_p(blob, page_image.get_ysize());
- merge_blobs(pblob, &c_as_p);
- }
+// This method returns the features associated with the input blob.
+void TessBaseAPI::GetFeaturesForBlob(TBLOB* blob, const DENORM& denorm,
+ INT_FEATURE_ARRAY int_features,
+ int* num_features,
+ int* FeatureOutlineIndex) {
+ if (tesseract_) {
+ tesseract_->ResetFeaturesHaveBeenExtracted();
+ }
+ tesseract_->set_denorm(&denorm);
+ CLASS_NORMALIZATION_ARRAY norm_array;
+ inT32 len;
+ *num_features = tesseract_->GetIntCharNormFeatures(
+ blob, tesseract_->PreTrainedTemplates,
+ int_features, norm_array, &len, FeatureOutlineIndex);
+}
- PBLOB_LIST *pblob_list = new PBLOB_LIST;
- PBLOB_IT pblob_it(pblob_list);
- pblob_it.add_after_then_move(pblob);
- WERD word(pblob_list, // Blob list.
- 0, // Blanks in front.
- " "); // Correct text.
- ROW *row = make_tess_ocrrow(0, // baseline.
- page_image.get_ysize(), // xheight.
- 0, // ascent.
- 0); // descent.
- word.baseline_normalise(row);
- delete row;
- if (pblob->out_list () == NULL) {
- tprintf("Blob list is empty");
+// This method returns the row to which a box of specified dimensions would
+// belong. If no good match is found, it returns NULL.
+ROW* TessBaseAPI::FindRowForBox(BLOCK_LIST* blocks,
+ int left, int top, int right, int bottom) {
+ TBOX box(left, bottom, right, top);
+ BLOCK_IT b_it(blocks);
+ for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
+ BLOCK* block = b_it.data();
+ if (!box.major_overlap(block->bounding_box()))
+ continue;
+ ROW_IT r_it(block->row_list());
+ for (r_it.mark_cycle_pt(); !r_it.cycled_list(); r_it.forward()) {
+ ROW* row = r_it.data();
+ if (!box.major_overlap(row->bounding_box()))
+ continue;
+ WERD_IT w_it(row->word_list());
+ for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) {
+ WERD* word = w_it.data();
+ if (box.major_overlap(word->bounding_box()))
+ return row;
+ }
}
- TBLOB* tblob = make_tess_blob(pblob, // Blob.
- TRUE); // Flatten.
-
- CLASS_NORMALIZATION_ARRAY norm_array;
- inT32 len;
- *num_features = tesseract_->GetCharNormFeatures(
- tblob, &line_stats,
- tesseract_->PreTrainedTemplates,
- int_features, norm_array, &len);
}
- delete blocks;
+ return NULL;
+}
+
+// Method to run adaptive classifier on a blob.
+void TessBaseAPI::RunAdaptiveClassifier(TBLOB* blob, const DENORM& denorm,
+ int num_max_matches,
+ int* unichar_ids,
+ char* configs,
+ float* ratings,
+ int* num_matches_returned) {
+ BLOB_CHOICE_LIST* choices = new BLOB_CHOICE_LIST;
+ tesseract_->set_denorm(&denorm);
+ tesseract_->AdaptiveClassifier(blob, choices, NULL);
+ BLOB_CHOICE_IT choices_it(choices);
+ int& index = *num_matches_returned;
+ index = 0;
+ for (choices_it.mark_cycle_pt();
+ !choices_it.cycled_list() && index < num_max_matches;
+ choices_it.forward()) {
+ BLOB_CHOICE* choice = choices_it.data();
+ unichar_ids[index] = choice->unichar_id();
+ configs[index] = choice->config();
+ ratings[index] = choice->rating();
+ ++index;
+ }
+ *num_matches_returned = index;
+ delete choices;
+}
+
+// This method returns the string form of the specified unichar.
+const char* TessBaseAPI::GetUnichar(int unichar_id) {
+ return tesseract_->unicharset.id_to_unichar(unichar_id);
}
// Return the pointer to the i-th dawg loaded into tesseract_ object.
@@ -1740,4 +1682,9 @@ const char* TessBaseAPI::GetLastInitLanguage() const {
return (tesseract_ == NULL || tesseract_->lang.string() == NULL) ?
"" : tesseract_->lang.string();
}
+
+// Return a pointer to underlying CubeRecoContext object if present.
+CubeRecoContext *TessBaseAPI::GetCubeRecoContext() const {
+ return (tesseract_ == NULL) ? NULL : tesseract_->GetCubeRecoContext();
+}
} // namespace tesseract.
diff --git a/api/baseapi.h b/api/baseapi.h
index 39e307fac6..d06a0ee5f3 100644
--- a/api/baseapi.h
+++ b/api/baseapi.h
@@ -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);
-
-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.
-};
+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);
-/**
- * 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().
- */
- bool SetVariable(const char* variable, const char* value);
-
- /**
- * 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.
+ * 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* 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;
+
+ /**
+ * 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.
-
- /**
- * @defgroup ThresholderParams
+ 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
* 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__
diff --git a/api/pageiterator.cpp b/api/pageiterator.cpp
new file mode 100644
index 0000000000..030c091f21
--- /dev/null
+++ b/api/pageiterator.cpp
@@ -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(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(row->base_line(left) + 0.5));
+ int right = box.right();
+ ICOORD endpt(right, static_cast(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.
diff --git a/api/pageiterator.h b/api/pageiterator.h
new file mode 100644
index 0000000000..6d6d3498c4
--- /dev/null
+++ b/api/pageiterator.h
@@ -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__
diff --git a/api/resultiterator.cpp b/api/resultiterator.cpp
new file mode 100644
index 0000000000..6c9c1b6ed1
--- /dev/null
+++ b/api/resultiterator.cpp
@@ -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 &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(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.
diff --git a/api/resultiterator.h b/api/resultiterator.h
new file mode 100644
index 0000000000..ba8957be37
--- /dev/null
+++ b/api/resultiterator.h
@@ -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__
diff --git a/api/tesseractmain.cpp b/api/tesseractmain.cpp
index 4fa2b11c55..88aae4884f 100644
--- a/api/tesseractmain.cpp
+++ b/api/tesseractmain.cpp
@@ -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
#endif
#include
-#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"
-#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;
- }
-
- tesseract::TessBaseAPI api;
-
- api.SetOutputName(argv[2]);
- api.SetPageSegMode(tesseract::PSM_AUTO);
- api.Init(argv[0], lang, &(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
-
- 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;
-
- 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);
-
- // 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], &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;
+ "Warning - no liblept or libtiff - cannot read compressed"
+ " tiff files.\n"
#endif
-#endif // HAVE_LIBLEPT
-
- //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[] =
- "\n"
- "\n\n"
- " OCR Output\n"
- " \n \n \n
\n\n";
- fprintf(fout, "%s", html_header);
- }
- fwrite(text_out.string(), 1, text_out.length(), fout);
- if (output_hocr)
- fprintf(fout, "\n\n");
- fclose(fout);
-
- return 0; //Normal exit
+ , 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;
+
+ api.SetOutputName(argv[2]);
+ api.Init(argv[0], lang, tesseract::OEM_DEFAULT, &(argv[arg]), argc-arg, false);
+
+ tprintf (_("Tesseract Open Source OCR Engine with Leptonica\n"));
+
+ 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], 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);
+ }
+ }
+
+ 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[] =
+ "\n"
+ "\n\n\n"
+ "\n\n"
+ "
\n\n";
+ fprintf(fout, "%s", html_header);
+ }
+ fwrite(text_out.string(), 1, text_out.length(), fout);
+ if (output_hocr)
+ fprintf(fout, "\n\n");
+ fclose(fout);
+
+ 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;
-
- 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;
-
- RegisterClass(&wc);
-
- 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 *));
-
- /*now construct argv as it should be for C.*/
- argc = parse_args (2, argsin, argv);
-
- // 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;
+ HINSTANCE hInstance,
+ HINSTANCE hPrevInstance,
+ LPSTR lpszCmdLine,
+ int nCmdShow) {
+ WNDCLASS wc;
+ HWND hwnd;
+ MSG msg;
+
+ 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;
+
+ RegisterClass(&wc);
+
+ 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 *));
+
+ /*now construct argv as it should be for C.*/
+ argc = parse_args (2, argsin, argv);
+
+ // 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;
}
/**********************************************************************
- * 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;
-
- if (msg == WM_CREATE) {
- //
- // Create a rendering context.
- //
- hdc = GetDC (hwnd);
- ReleaseDC(hwnd, hdc);
- initialized = 1;
- return 0;
- }
- return DefWindowProc (hwnd, msg, wParam, lParam);
+ 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);
}
/**********************************************************************
- * 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 */
-
- 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 */
+ 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 */
}
#endif
diff --git a/api/tesseractmain.h b/api/tesseractmain.h
index f6c2bbcaa7..a8a043a1ec 100644
--- a/api/tesseractmain.h
+++ b/api/tesseractmain.h
@@ -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
diff --git a/ccmain/Makefile.am b/ccmain/Makefile.am
index 4de8f3f210..c2a79ed441 100644
--- a/ccmain/Makefile.am
+++ b/ccmain/Makefile.am
@@ -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)
diff --git a/ccmain/Makefile.in b/ccmain/Makefile.in
index 50455eae6b..a2b1e649bc 100644
--- a/ccmain/Makefile.in
+++ b/ccmain/Makefile.in
@@ -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:
diff --git a/ccmain/adaptions.cpp b/ccmain/adaptions.cpp
index 95cf6c00f8..0c0d13975c 100644
--- a/ccmain/adaptions.cpp
+++ b/ccmain/adaptions.cpp
@@ -33,16 +33,12 @@
#include "tessbox.h"
#include "tessvars.h"
#include "memry.h"
-#include "mainblk.h"
#include "charcut.h"
#include "imgs.h"
#include "scaleimg.h"
#include "reject.h"
#include "control.h"
-#include "adaptions.h"
#include "stopper.h"
-#include "charsample.h"
-#include "matmatch.h"
#include "secname.h"
#include "tesseractclass.h"
@@ -51,61 +47,6 @@
#include "config_auto.h"
#endif
-inT32 demo_word = 0;
-
-#define WINDOWNAMESIZE 13 /*max size of name */
-
-#define EXTERN
-
-EXTERN BOOL_VAR (tessedit_reject_ems, FALSE, "Reject all m's");
-EXTERN BOOL_VAR (tessedit_reject_suspect_ems, FALSE, "Reject suspect m's");
-
-EXTERN double_VAR (tessedit_cluster_t1, 0.20,
-"t1 threshold for clustering samples");
-EXTERN double_VAR (tessedit_cluster_t2, 0.40,
-"t2 threshold for clustering samples");
-EXTERN double_VAR (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 (tessedit_cluster_accept_fraction, 0.80,
-"Largest fraction of characters in cluster for it to be used for adaption");
-EXTERN INT_VAR (tessedit_cluster_min_size, 3,
-"Smallest number of samples in a cluster for it to be used for adaption");
-EXTERN BOOL_VAR (tessedit_cluster_debug, FALSE,
-"Generate and print debug information for adaption by clustering");
-EXTERN BOOL_VAR (tessedit_use_best_sample, FALSE,
-"Use best sample from cluster when adapting");
-EXTERN BOOL_VAR (tessedit_test_cluster_input, FALSE,
-"Set reject map to enable cluster input to be measured");
-
-EXTERN BOOL_VAR (tessedit_matrix_match, TRUE, "Use matrix matcher");
-EXTERN BOOL_VAR (tessedit_mm_use_non_adaption_set, FALSE,
-"Don't try to adapt to characters on this list");
-EXTERN STRING_VAR (tessedit_non_adaption_set, ",.;:'~@*",
-"Characters to be avoided when adapting");
-EXTERN BOOL_VAR (tessedit_mm_adapt_using_prototypes, TRUE,
-"Use prototypes when adapting");
-EXTERN BOOL_VAR (tessedit_mm_use_prototypes, TRUE,
-"Use prototypes as clusters are built");
-EXTERN BOOL_VAR (tessedit_mm_use_rejmap, FALSE,
-"Adapt to characters using reject map");
-EXTERN BOOL_VAR (tessedit_mm_all_rejects, FALSE,
-"Adapt to all characters using, matrix matcher");
-EXTERN BOOL_VAR (tessedit_mm_only_match_same_char, FALSE,
-"Only match samples against clusters for the same character");
-EXTERN BOOL_VAR (tessedit_process_rns, FALSE, "Handle m - rn ambigs");
-
-EXTERN BOOL_VAR (tessedit_demo_adaption, FALSE,
-"Display cut images and matrix match for demo purposes");
-EXTERN INT_VAR (tessedit_demo_word1, 62,
-"Word number of first word to display");
-EXTERN INT_VAR (tessedit_demo_word2, 64,
-"Word number of second word to display");
-EXTERN STRING_VAR (tessedit_demo_file, "academe",
-"Name of document containing demo words");
-EXTERN BOOL_VAR(tessedit_adapt_to_char_fragments, TRUE,
- "Adapt to words that contain "
- " a character composed form fragments");
-
namespace tesseract {
BOOL8 Tesseract::word_adaptable( //should we adapt?
WERD_RES *word,
@@ -201,938 +142,6 @@ BOOL8 Tesseract::word_adaptable( //should we adapt?
tprintf("returning status %d\n", status);
}
return status;
-
}
-
-void Tesseract::collect_ems_for_adaption(WERD_RES *word,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting) {
- PBLOB_LIST *blobs = word->outword->blob_list ();
- PBLOB_IT blob_it(blobs);
- inT16 i;
- CHAR_SAMPLE *sample;
- PIXROW_LIST *pixrow_list;
- PIXROW_IT pixrow_it;
- IMAGELINE *imlines; // lines of the image
- TBOX pix_box; // box of imlines
- // extent
- WERD copy_outword; // copy to denorm
- PBLOB_IT copy_blob_it;
- OUTLINE_IT copy_outline_it;
- inT32 resolution = page_image.get_res ();
-
- if (tessedit_reject_ems || tessedit_reject_suspect_ems)
- return; // Do nothing
-
- if (word->word->bounding_box ().height () > resolution / 3)
- return;
-
- if (tessedit_demo_adaption)
- // Make sure not set
- tessedit_display_mm.set_value (FALSE);
-
- if (word_adaptable (word, tessedit_em_adaption_mode)
- && word->reject_map.reject_count () == 0
- && (strchr (word->best_choice->unichar_string().string (), 'm') != NULL
- || (tessedit_process_rns
- && strstr (word->best_choice->unichar_string().string (),
- "rn") != NULL))) {
- if (tessedit_process_rns
- && strstr (word->best_choice->unichar_string().string (), "rn") != NULL) {
- copy_outword = *(word->outword);
- copy_blob_it.set_to_list (copy_outword.blob_list ());
- i = 0;
- while (word->best_choice->unichar_string()[i] != '\0') {
- if (word->best_choice->unichar_string()[i] == 'r'
- && word->best_choice->unichar_string()[i + 1] == 'n') {
- copy_outline_it.set_to_list (copy_blob_it.data ()->
- out_list ());
- copy_outline_it.add_list_after (copy_blob_it.
- data_relative (1)->
- out_list ());
- copy_blob_it.forward ();
- delete (copy_blob_it.extract ());
- i++;
- }
- copy_blob_it.forward ();
- i++;
- }
- }
- else
- copy_outword = *(word->outword);
-
- copy_outword.baseline_denormalise (&word->denorm);
- char_clip_word(©_outword, page_image, pixrow_list, imlines, pix_box);
- pixrow_it.set_to_list (pixrow_list);
- pixrow_it.move_to_first ();
-
- blob_it.move_to_first ();
- for (i = 0;
- word->best_choice->unichar_string()[i] != '\0';
- i++, pixrow_it.forward (), blob_it.forward ()) {
-
- if (word->best_choice->unichar_string()[i] == 'm'
- || (word->best_choice->unichar_string()[i] == 'r'
- && word->best_choice->unichar_string()[i + 1] == 'n')) {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample %c for adaption found in %s, index %d\n",
- word->best_choice->unichar_string()[i],
- word->best_choice->unichar_string().string (), i);
- #endif
- if (tessedit_matrix_match) {
- sample = clip_sample (pixrow_it.data (),
- imlines,
- pix_box,
- copy_outword.flag (W_INVERSE),
- word->best_choice->unichar_string()[i]);
-
- if (sample == NULL) { //Clip failed
- #ifndef SECURE_NAMES
- tprintf ("Unable to clip sample from %s, index %d\n",
- word->best_choice->unichar_string().string (), i);
- #endif
- if (word->best_choice->unichar_string()[i] == 'r')
- i++;
-
- continue;
- }
- }
- else
- sample = new CHAR_SAMPLE (blob_it.data (),
- &word->denorm,
- word->best_choice->unichar_string()[i]);
-
- cluster_sample(sample, char_clusters, chars_waiting);
-
- if (word->best_choice->unichar_string()[i] == 'r')
- i++; // Skip next character
- }
- }
- delete[]imlines; // Free array of imlines
- delete pixrow_list;
- }
-}
-
-
-void Tesseract::collect_characters_for_adaption(
- WERD_RES *word,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting) {
- PBLOB_LIST *blobs = word->outword->blob_list ();
- PBLOB_IT blob_it(blobs);
- inT16 i;
- CHAR_SAMPLE *sample;
- PIXROW_LIST *pixrow_list;
- PIXROW_IT pixrow_it;
- IMAGELINE *imlines; // lines of the image
- TBOX pix_box; // box of imlines
- // extent
- WERD copy_outword; // copy to denorm
- inT32 resolution = page_image.get_res ();
-
- if (word->word->bounding_box ().height () > resolution / 3)
- return;
-
- if (tessedit_demo_adaption)
- // Make sure not set
- tessedit_display_mm.set_value (FALSE);
-
- if ((word_adaptable (word, tessedit_cluster_adaption_mode)
- && word->reject_map.reject_count () == 0) || tessedit_mm_use_rejmap) {
- if (tessedit_test_cluster_input && !tessedit_mm_use_rejmap)
- return; // Reject map set to acceptable
- /* Collect information about good matches */
- copy_outword = *(word->outword);
- copy_outword.baseline_denormalise (&word->denorm);
- char_clip_word(©_outword, page_image, pixrow_list, imlines, pix_box);
- pixrow_it.set_to_list (pixrow_list);
- pixrow_it.move_to_first ();
-
- blob_it.move_to_first ();
- for (i = 0;
- word->best_choice->unichar_string()[i] != '\0';
- i++, pixrow_it.forward (), blob_it.forward ()) {
-
- if (!(tessedit_mm_use_non_adaption_set
- && STRING(tessedit_non_adaption_set).contains(
- word->best_choice->unichar_string()[i]))
- || (tessedit_mm_use_rejmap && word->reject_map[i].accepted ())) {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample %c for adaption found in %s, index %d\n",
- word->best_choice->unichar_string()[i],
- word->best_choice->unichar_string().string (), i);
- #endif
- sample = clip_sample (pixrow_it.data (),
- imlines,
- pix_box,
- copy_outword.flag (W_INVERSE),
- word->best_choice->unichar_string()[i]);
-
- if (sample == NULL) { //Clip failed
- #ifndef SECURE_NAMES
- tprintf ("Unable to clip sample from %s, index %d\n",
- word->best_choice->unichar_string().string (), i);
- #endif
- continue;
- }
- cluster_sample(sample, char_clusters, chars_waiting);
- }
- }
- delete[]imlines; // Free array of imlines
- delete pixrow_list;
- }
- else if (tessedit_test_cluster_input && !tessedit_mm_use_rejmap)
- // Set word to all rejects
- word->reject_map.rej_word_tess_failure ();
-
-}
-
-
-void Tesseract::cluster_sample(CHAR_SAMPLE *sample,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting) {
- CHAR_SAMPLES *best_cluster = NULL;
- CHAR_SAMPLES_IT c_it = char_clusters;
- CHAR_SAMPLE_IT cw_it = chars_waiting;
- float score;
- float best_score = MAX_INT32;
-
- if (c_it.empty ())
- c_it.add_to_end (new CHAR_SAMPLES (sample));
- else {
- for (c_it.mark_cycle_pt (); !c_it.cycled_list (); c_it.forward ()) {
- score = c_it.data ()->match_score (sample, this);
- if (score < best_score) {
- best_score = score;
- best_cluster = c_it.data ();
- }
- }
-
- if (tessedit_cluster_debug)
- tprintf ("Sample's best score %f\n", best_score);
-
- if (best_score < tessedit_cluster_t1) {
- if (best_score > tessedit_cluster_t3 || tessedit_mm_use_prototypes) {
- best_cluster->add_sample (sample, this);
- check_wait_list(chars_waiting, sample, best_cluster);
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample added to an existing cluster\n");
- #endif
- }
- else {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf
- ("Sample dropped, good match to an existing cluster\n");
- #endif
- }
- }
- else if (best_score > tessedit_cluster_t2) {
- c_it.add_to_end (new CHAR_SAMPLES (sample));
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("New cluster created for this sample\n");
- #endif
- }
- else {
- cw_it.add_to_end (sample);
- if (tessedit_cluster_debug)
- tprintf ("Sample added to the wait list\n");
- }
- }
-}
-
-void Tesseract::check_wait_list(CHAR_SAMPLE_LIST *chars_waiting,
- CHAR_SAMPLE *sample,
- CHAR_SAMPLES *best_cluster) {
- CHAR_SAMPLE *wait_sample;
- CHAR_SAMPLE *test_sample = sample;
- CHAR_SAMPLE_IT cw_it = chars_waiting;
- CHAR_SAMPLE_LIST add_list; //Samples added to best cluster
- CHAR_SAMPLE_IT add_it = &add_list;
- float score;
-
- add_list.clear ();
-
- if (!cw_it.empty ()) {
- do {
- if (!add_list.empty ()) {
- add_it.forward ();
- test_sample = add_it.extract ();
- best_cluster->add_sample (test_sample, this);
- }
-
- for (cw_it.mark_cycle_pt ();
- !cw_it.cycled_list (); cw_it.forward ()) {
- wait_sample = cw_it.data ();
- if (tessedit_mm_use_prototypes)
- score = best_cluster->match_score (wait_sample, this);
- else
- score = sample->match_sample (wait_sample, FALSE, this);
- if (score < tessedit_cluster_t1) {
- if (score > tessedit_cluster_t3
- || tessedit_mm_use_prototypes) {
- add_it.add_after_stay_put (cw_it.extract ());
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf
- ("Wait sample added to an existing cluster\n");
- #endif
- }
- else {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf
- ("Wait sample dropped, good match to an existing cluster\n");
- #endif
- }
- }
- }
- }
- while (!add_list.empty ());
- }
-}
-
-
-void Tesseract::complete_clustering(CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting) {
- CHAR_SAMPLES *best_cluster;
- CHAR_SAMPLES_IT c_it = char_clusters;
- CHAR_SAMPLE_IT cw_it = chars_waiting;
- CHAR_SAMPLE *sample;
- inT32 total_sample_count = 0;
-
- while (!cw_it.empty ()) {
- cw_it.move_to_first ();
- sample = cw_it.extract ();
- best_cluster = new CHAR_SAMPLES (sample);
- c_it.add_to_end (best_cluster);
- check_wait_list(chars_waiting, sample, best_cluster);
- }
-
- for (c_it.mark_cycle_pt (); !c_it.cycled_list (); c_it.forward ()) {
- c_it.data ()->assign_to_char ();
- if (tessedit_use_best_sample)
- c_it.data ()->find_best_sample ();
- else if (tessedit_mm_adapt_using_prototypes)
- c_it.data ()->build_prototype ();
-
- if (tessedit_cluster_debug)
- total_sample_count += c_it.data ()->n_samples ();
- }
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Clustering completed, %d samples in all\n", total_sample_count);
- #endif
-
-#ifndef GRAPHICS_DISABLED
- if (tessedit_demo_adaption)
- display_cluster_prototypes(char_clusters);
-#endif
-
-}
-
-void Tesseract::adapt_to_good_ems(WERD_RES *word,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting) {
- PBLOB_LIST *blobs = word->outword->blob_list ();
- PBLOB_IT blob_it(blobs);
- inT16 i;
- CHAR_SAMPLE *sample;
- CHAR_SAMPLES_IT c_it = char_clusters;
- CHAR_SAMPLE_IT cw_it = chars_waiting;
- float score;
- float best_score;
- char best_char;
- CHAR_SAMPLES *best_cluster;
- PIXROW_LIST *pixrow_list;
- PIXROW_IT pixrow_it;
- IMAGELINE *imlines; // lines of the image
- TBOX pix_box; // box of imlines
- // extent
- WERD copy_outword; // copy to denorm
- TBOX b_box;
- PBLOB_IT copy_blob_it;
- OUTLINE_IT copy_outline_it;
- PIXROW *pixrow = NULL;
-
- static inT32 word_number = 0;
-
-#ifndef GRAPHICS_DISABLED
- ScrollView* demo_win = NULL;
-#endif
-
- inT32 resolution = page_image.get_res ();
-
- if (word->word->bounding_box ().height () > resolution / 3)
- return;
-
- word_number++;
-
- if (strchr (word->best_choice->unichar_string().string (), 'm') == NULL
- && (tessedit_process_rns
- && strstr (word->best_choice->unichar_string().string (), "rn") == NULL))
- return;
-
- if (tessedit_reject_ems)
- reject_all_ems(word);
- else if (tessedit_reject_suspect_ems)
- reject_suspect_ems(word);
- else {
- if (char_clusters->length () == 0) {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("No clusters to use for em adaption\n");
- #endif
- return;
- }
-
- if (!cw_it.empty ()) {
- complete_clustering(char_clusters, chars_waiting);
- print_em_stats(char_clusters, chars_waiting);
- }
-
- if ((!word_adaptable (word, tessedit_em_adaption_mode) ||
- word->reject_map.reject_count () != 0)
- && (strchr (word->best_choice->unichar_string().string (), 'm') != NULL
- || (tessedit_process_rns
- && strstr (word->best_choice->unichar_string().string (),
- "rn") != NULL))) {
- if (tessedit_process_rns
- && strstr (word->best_choice->unichar_string().string (),
- "rn") != NULL) {
- copy_outword = *(word->outword);
- copy_blob_it.set_to_list (copy_outword.blob_list ());
- i = 0;
- while (word->best_choice->unichar_string()[i] != '\0') {
- if (word->best_choice->unichar_string()[i] == 'r'
- && word->best_choice->unichar_string()[i + 1] == 'n') {
- copy_outline_it.set_to_list (copy_blob_it.data ()->
- out_list ());
- copy_outline_it.add_list_after (copy_blob_it.
- data_relative (1)->
- out_list ());
- copy_blob_it.forward ();
- delete (copy_blob_it.extract ());
- i++;
- }
- copy_blob_it.forward ();
- i++;
- }
- }
- else
- copy_outword = *(word->outword);
-
- copy_outword.baseline_denormalise (&word->denorm);
- copy_blob_it.set_to_list (copy_outword.blob_list ());
- char_clip_word(©_outword, page_image, pixrow_list, imlines, pix_box);
- pixrow_it.set_to_list (pixrow_list);
- pixrow_it.move_to_first ();
-
- // For debugging only
- b_box = copy_outword.bounding_box ();
- pixrow = pixrow_it.data ();
-
- blob_it.move_to_first ();
- copy_blob_it.move_to_first ();
- for (i = 0;
- word->best_choice->unichar_string()[i] != '\0';
- i++, pixrow_it.forward (), blob_it.forward (),
- copy_blob_it.forward ()) {
- if ((word->best_choice->unichar_string()[i] == 'm'
- || (word->best_choice->unichar_string()[i] == 'r'
- && word->best_choice->unichar_string()[i + 1] == 'n'))
- && !word->reject_map[i].perm_rejected ()) {
- if (tessedit_cluster_debug)
- tprintf ("Sample %c to check found in %s, index %d\n",
- word->best_choice->unichar_string()[i],
- word->best_choice->unichar_string().string (), i);
-
- if (tessedit_demo_adaption)
- tprintf
- ("Sample %c to check found in %s (%d), index %d\n",
- word->best_choice->unichar_string()[i],
- word->best_choice->unichar_string().string (), word_number,
- i);
-
- if (tessedit_matrix_match) {
- TBOX copy_box = copy_blob_it.data ()->bounding_box ();
-
- sample = clip_sample (pixrow_it.data (),
- imlines,
- pix_box,
- copy_outword.flag (W_INVERSE),
- word->best_choice->unichar_string()[i]);
-
- //Clip failed
- if (sample == NULL) {
- tprintf
- ("Unable to clip sample from %s, index %d\n",
- word->best_choice->unichar_string().string (), i);
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample rejected (no sample)\n");
- #endif
- word->reject_map[i].setrej_mm_reject ();
- if (word->best_choice->unichar_string()[i] == 'r') {
- word->reject_map[i + 1].setrej_mm_reject ();
- i++;
- }
- continue;
- }
- }
- else
- sample = new CHAR_SAMPLE(blob_it.data(),
- &word->denorm,
- word->best_choice->unichar_string()[i]);
-
- best_score = MAX_INT32;
- best_char = '\0';
- best_cluster = NULL;
-
- for (c_it.mark_cycle_pt ();
- !c_it.cycled_list (); c_it.forward ()) {
- if (c_it.data ()->character () != '\0') {
- score = c_it.data ()->match_score (sample, this);
- if (score < best_score) {
- best_cluster = c_it.data ();
- best_score = score;
- best_char = c_it.data ()->character ();
- }
- }
- }
-
- if (best_score > tessedit_cluster_t1) {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample rejected (score %f)\n", best_score);
- if (tessedit_demo_adaption)
- tprintf ("Sample rejected (score %f)\n", best_score);
- #endif
- word->reject_map[i].setrej_mm_reject ();
- if (word->best_choice->unichar_string()[i] == 'r')
- word->reject_map[i + 1].setrej_mm_reject ();
- }
- else {
- if (word->best_choice->unichar_string()[i] == best_char) {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample accepted (score %f)\n",
- best_score);
- if (tessedit_demo_adaption)
- tprintf ("Sample accepted (score %f)\n",
- best_score);
- #endif
- word->reject_map[i].setrej_mm_accept ();
- if (word->best_choice->unichar_string()[i] == 'r')
- word->reject_map[i + 1].setrej_mm_accept ();
- }
- else {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample rejected (char %c, score %f)\n",
- best_char, best_score);
- if (tessedit_demo_adaption)
- tprintf ("Sample rejected (char %c, score %f)\n",
- best_char, best_score);
- #endif
- word->reject_map[i].setrej_mm_reject ();
- if (word->best_choice->unichar_string()[i] == 'r')
- word->reject_map[i + 1].setrej_mm_reject ();
- }
- }
-
- if (tessedit_demo_adaption) {
- if (strcmp (imagebasename.string (),
- tessedit_demo_file.string ()) != 0
- || word_number == tessedit_demo_word1
- || word_number == tessedit_demo_word2) {
-#ifndef GRAPHICS_DISABLED
- demo_win =
- display_clip_image(©_outword,
- page_image,
- pixrow_list,
- pix_box);
-#endif
- demo_word = word_number;
- best_cluster->match_score (sample, this);
- demo_word = 0;
- }
- }
- if (word->best_choice->unichar_string()[i] == 'r')
- i++; // Skip next character
- }
- }
- delete[]imlines; // Free array of imlines
- delete pixrow_list;
- }
- }
-}
-
-
-
-void Tesseract::adapt_to_good_samples(WERD_RES *word,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting) {
- PBLOB_LIST *blobs = word->outword->blob_list ();
- PBLOB_IT blob_it(blobs);
- inT16 i;
- CHAR_SAMPLE *sample;
- CHAR_SAMPLES_IT c_it = char_clusters;
- CHAR_SAMPLE_IT cw_it = chars_waiting;
- float score;
- float best_score;
- char best_char;
- CHAR_SAMPLES *best_cluster;
- PIXROW_LIST *pixrow_list;
- PIXROW_IT pixrow_it;
- IMAGELINE *imlines; // lines of the image
- TBOX pix_box; // box of imlines
- // extent
- WERD copy_outword; // copy to denorm
- TBOX b_box;
- PBLOB_IT copy_blob_it;
- PIXROW *pixrow = NULL;
-
- static inT32 word_number = 0;
-
-#ifndef GRAPHICS_DISABLED
- ScrollView* demo_win = NULL;
-#endif
-
- inT32 resolution = page_image.get_res ();
-
- word_number++;
-
- if (tessedit_test_cluster_input)
- return;
-
- if (word->word->bounding_box ().height () > resolution / 3)
- return;
-
- if (char_clusters->length () == 0) {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("No clusters to use for adaption\n");
- #endif
- return;
- }
-
- if (!cw_it.empty ()) {
- complete_clustering(char_clusters, chars_waiting);
- print_em_stats(char_clusters, chars_waiting);
- }
-
- if ((!word_adaptable (word, tessedit_cluster_adaption_mode)
- && word->reject_map.reject_count () != 0) || tessedit_mm_use_rejmap) {
- if (tessedit_cluster_debug) {
- tprintf ("\nChecking: \"%s\" MAP ",
- word->best_choice->unichar_string().string ());
- word->reject_map.print (debug_fp);
- tprintf ("\n");
- }
-
- copy_outword = *(word->outword);
- copy_outword.baseline_denormalise (&word->denorm);
- copy_blob_it.set_to_list (copy_outword.blob_list ());
- char_clip_word(©_outword, page_image, pixrow_list, imlines, pix_box);
- pixrow_it.set_to_list (pixrow_list);
- pixrow_it.move_to_first ();
-
- // For debugging only
- b_box = copy_outword.bounding_box ();
- pixrow = pixrow_it.data ();
-
- blob_it.move_to_first ();
- copy_blob_it.move_to_first ();
- for (i = 0;
- word->best_choice->unichar_string()[i] != '\0';
- i++, pixrow_it.forward (), blob_it.forward (),
- copy_blob_it.forward ()) {
- if (word->reject_map[i].recoverable ()
- || (tessedit_mm_all_rejects && word->reject_map[i].rejected ())) {
- TBOX copy_box = copy_blob_it.data ()->bounding_box ();
-
- if (tessedit_cluster_debug)
- tprintf ("Sample %c to check found in %s, index %d\n",
- word->best_choice->unichar_string()[i],
- word->best_choice->unichar_string().string (), i);
-
- if (tessedit_demo_adaption)
- tprintf ("Sample %c to check found in %s (%d), index %d\n",
- word->best_choice->unichar_string()[i],
- word->best_choice->unichar_string().string (),
- word_number, i);
-
- sample = clip_sample (pixrow_it.data (),
- imlines,
- pix_box,
- copy_outword.flag (W_INVERSE),
- word->best_choice->unichar_string()[i]);
-
- if (sample == NULL) { //Clip failed
- tprintf ("Unable to clip sample from %s, index %d\n",
- word->best_choice->unichar_string().string (), i);
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample rejected (no sample)\n");
- #endif
- word->reject_map[i].setrej_mm_reject ();
-
- continue;
- }
-
- best_score = MAX_INT32;
- best_char = '\0';
- best_cluster = NULL;
-
- for (c_it.mark_cycle_pt ();
- !c_it.cycled_list (); c_it.forward ()) {
- if (c_it.data ()->character () != '\0') {
- score = c_it.data ()->match_score (sample, this);
- if (score < best_score) {
- best_cluster = c_it.data ();
- best_score = score;
- best_char = c_it.data ()->character ();
- }
- }
- }
-
- if (best_score > tessedit_cluster_t1) {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample rejected (score %f)\n", best_score);
- if (tessedit_demo_adaption)
- tprintf ("Sample rejected (score %f)\n", best_score);
- #endif
- word->reject_map[i].setrej_mm_reject ();
- }
- else {
- if (word->best_choice->unichar_string()[i] == best_char) {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample accepted (score %f)\n", best_score);
- if (tessedit_demo_adaption)
- tprintf ("Sample accepted (score %f)\n", best_score);
- #endif
- if (tessedit_test_adaption)
- word->reject_map[i].setrej_minimal_rej_accept ();
- else
- word->reject_map[i].setrej_mm_accept ();
- }
- else {
- #ifndef SECURE_NAMES
- if (tessedit_cluster_debug)
- tprintf ("Sample rejected (char %c, score %f)\n",
- best_char, best_score);
- if (tessedit_demo_adaption)
- tprintf ("Sample rejected (char %c, score %f)\n",
- best_char, best_score);
- #endif
- word->reject_map[i].setrej_mm_reject ();
- }
- }
-
- if (tessedit_demo_adaption) {
- if (strcmp (imagebasename.string (),
- tessedit_demo_file.string ()) != 0
- || word_number == tessedit_demo_word1
- || word_number == tessedit_demo_word2) {
-#ifndef GRAPHICS_DISABLED
- demo_win =
- display_clip_image(©_outword,
- page_image,
- pixrow_list,
- pix_box);
-#endif
- demo_word = word_number;
- best_cluster->match_score (sample, this);
- demo_word = 0;
- }
- }
- }
- }
- delete[]imlines; // Free array of imlines
- delete pixrow_list;
-
- if (tessedit_cluster_debug) {
- tprintf ("\nFinal: \"%s\" MAP ",
- word->best_choice->unichar_string().string ());
- word->reject_map.print (debug_fp);
- tprintf ("\n");
- }
- }
-}
-} // namespace tesseract
-
-
-void print_em_stats(CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting) {
- CHAR_SAMPLES_IT c_it = char_clusters;
-
- if (!tessedit_cluster_debug)
- return;
- #ifndef SECURE_NAMES
- tprintf ("There are %d clusters and %d samples waiting\n",
- char_clusters->length (), chars_waiting->length ());
-
- for (c_it.mark_cycle_pt (); !c_it.cycled_list (); c_it.forward ())
- c_it.data ()->print (debug_fp);
- #endif
- tprintf ("\n");
-}
-
-
-CHAR_SAMPLE *clip_sample( //lines of the image
- PIXROW *pixrow,
- IMAGELINE *imlines,
- TBOX pix_box, //box of imlines extent
- BOOL8 white_on_black,
- char c) {
- TBOX b_box = pixrow->bounding_box ();
- float baseline_pos = 0;
- inT32 resolution = page_image.get_res ();
-
- if (!b_box.null_box ()) {
- ASSERT_HOST (b_box.width () < page_image.get_xsize () &&
- b_box.height () < page_image.get_ysize ());
-
- if (b_box.width () > resolution || b_box.height () > resolution) {
- tprintf ("clip sample: sample too big (%d x %d)\n",
- b_box.width (), b_box.height ());
-
- return NULL;
- }
-
- IMAGE *image = new (IMAGE);
- if (image->create (b_box.width (), b_box.height (), 1) == -1) {
- tprintf ("clip sample: create image failed (%d x %d)\n",
- b_box.width (), b_box.height ());
-
- delete image;
- return NULL;
- }
-
- if (!white_on_black)
- invert_image(image); // Set background to white
- pixrow->char_clip_image (imlines, pix_box, NULL, *image, baseline_pos);
- if (white_on_black)
- invert_image(image); //invert white on black for scaling &NN
- return new CHAR_SAMPLE (image, c);
- }
- else
- return NULL;
-}
-
-
-#ifndef GRAPHICS_DISABLED
-void display_cluster_prototypes(CHAR_SAMPLES_LIST *char_clusters) {
- inT16 proto_number = 0;
- CHAR_SAMPLES_IT c_it = char_clusters;
- char title[WINDOWNAMESIZE];
-
- for (c_it.mark_cycle_pt (); !c_it.cycled_list (); c_it.forward ()) {
- proto_number++;
-
- #ifndef SECURE_NAMES
- tprintf ("Displaying proto number %d\n", proto_number);
- #endif
-
- if (c_it.data ()->prototype () != NULL) {
- sprintf (title, "Proto - %d", proto_number);
- display_image (c_it.data ()->prototype ()->make_image (),
- title, (proto_number - 1) * 400, 0, FALSE);
- }
- }
-}
-#endif
-
-// *********************************************************************
-// Simplistic routines to test the effect of rejecting ems and fullstops
-// *********************************************************************
-
-void reject_all_ems(WERD_RES *word) {
- inT16 i;
-
- for (i = 0; word->best_choice->unichar_string()[i] != '\0'; i++) {
- if (word->best_choice->unichar_string()[i] == 'm')
- // reject all ems
- word->reject_map[i].setrej_mm_reject ();
- }
-}
-
-
-void reject_all_fullstops(WERD_RES *word) {
- inT16 i;
-
- for (i = 0; word->best_choice->unichar_string()[i] != '\0'; i++) {
- if (word->best_choice->unichar_string()[i] == '.')
- // reject all fullstops
- word->reject_map[i].setrej_mm_reject ();
- }
-}
-
-namespace tesseract {
-void Tesseract::reject_suspect_ems(WERD_RES *word) {
- inT16 i;
-
- if (!word_adaptable (word, tessedit_cluster_adaption_mode))
- for (i = 0; word->best_choice->unichar_string()[i] != '\0'; i++) {
- if (word->best_choice->unichar_string()[i] == 'm' && suspect_em (word, i))
- // reject all ems
- word->reject_map[i].setrej_mm_reject ();
- }
-}
} // namespace tesseract
-
-
-void reject_suspect_fullstops(WERD_RES *word) {
- inT16 i;
-
- for (i = 0; word->best_choice->unichar_string()[i] != '\0'; i++) {
- if (word->best_choice->unichar_string()[i] == '.'
- && suspect_fullstop (word, i))
- // reject all commas
- word->reject_map[i].setrej_mm_reject ();
- }
-}
-
-
-BOOL8 suspect_em(WERD_RES *word, inT16 index) {
- PBLOB_LIST *blobs = word->outword->blob_list ();
- PBLOB_IT blob_it(blobs);
- inT16 j;
-
- for (j = 0; j < index; j++)
- blob_it.forward ();
-
- return (blob_it.data ()->out_list ()->length () != 1);
-}
-
-
-BOOL8 suspect_fullstop(WERD_RES *word, inT16 i) {
- float aspect_ratio;
- PBLOB_LIST *blobs = word->outword->blob_list ();
- PBLOB_IT blob_it(blobs);
- inT16 j;
- TBOX box;
- inT16 width;
- inT16 height;
-
- for (j = 0; j < i; j++)
- blob_it.forward ();
-
- box = blob_it.data ()->bounding_box ();
-
- width = box.width ();
- height = box.height ();
-
- aspect_ratio = ((width > height) ? ((float) width) / height :
- ((float) height) / width);
-
- return (aspect_ratio > tessed_fullstop_aspect_ratio);
-}
diff --git a/ccmain/adaptions.h b/ccmain/adaptions.h
deleted file mode 100644
index 7033045511..0000000000
--- a/ccmain/adaptions.h
+++ /dev/null
@@ -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
diff --git a/ccmain/ambigsrecog.cpp b/ccmain/ambigsrecog.cpp
deleted file mode 100644
index 9ffa2555d6..0000000000
--- a/ccmain/ambigsrecog.cpp
+++ /dev/null
@@ -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
diff --git a/ccmain/applybox.cpp b/ccmain/applybox.cpp
index b83ab5bdbc..8aa7a3c915 100644
--- a/ccmain/applybox.cpp
+++ b/ccmain/applybox.cpp
@@ -22,1084 +22,675 @@
#pragma warning(disable:4244) // Conversion warnings
#endif
-#include "applybox.h"
#include
#include
#ifdef __UNIX__
#include
#include
#endif
+#include "allheaders.h"
#include "boxread.h"
-#include "control.h"
-#include "genblob.h"
-#include "globals.h"
-#include "fixxht.h"
-#include "mainblk.h"
-#include "matchdefs.h"
-#include "secname.h"
-#include "tessbox.h"
+#include "chopper.h"
+#include "pageres.h"
#include "unichar.h"
#include "unicharset.h"
-#include "matchdefs.h"
#include "tesseractclass.h"
-#define SECURE_NAMES
-#ifndef SECURE_NAMES
-#include "wordstats.h"
-#endif
-
-// 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
-
-#define EXTERN
-EXTERN BOOL_VAR (applybox_rebalance, TRUE, "Drop dead");
-EXTERN INT_VAR (applybox_debug, 5, "Debug level");
-EXTERN INT_VAR (applybox_page, 0, "Page number to apply boxes from");
-EXTERN STRING_VAR (applybox_test_exclusions, "",
- "Chars ignored for testing");
-EXTERN double_VAR (applybox_error_band, 0.15, "Err band as fract of xht");
-
-EXTERN STRING_VAR(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");
-
-EXTERN BOOL_VAR(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.");
-
-extern IMAGE page_image;
-
-// The unicharset used during box training
-static UNICHARSET unicharset_boxes;
-
-static void clear_any_old_text(BLOCK_LIST *block_list);
-
-// Register uch with unicharset_boxes.
-static UNICHAR_ID register_char(const char *uch);
-
-static BOOL8 read_next_box(int page,
- FILE* box_file,
- TBOX *box,
- UNICHAR_ID *uch_id);
-
+// Max number of blobs to classify together in FindSegmentation.
+const int kMaxGroupSize = 4;
/*************************************************************************
- * The code re-assigns outlines to form words each with ONE labelled blob.
- * Noise is left in UNLABELLED words. The chars on the page are checked crudely
- * for sensible position relative to baseline and xht. Failed boxes are
- * compensated for by duplicating other believable instances of the character.
- *
* The box file is assumed to contain box definitions, one per line, of the
- * following format:
- * ... arbitrary trailing fields unused
- *
- * The approach taken is to search the WHOLE page for stuff overlapping each box.
- * - This is not too inefficient and is SAFE.
- * - We can detect overlapping blobs as we will be attempting to put a blob
- * from a LABELLED word into the current word.
- * - When all the boxes have been processed we can detect any stuff which is
- * being ignored - it is the unlabelled words left on the page.
- *
- * A box should only overlap one row.
+ * following format for blob-level boxes:
+ *
+ * and for word/line-level boxes:
+ * WordStr #
+ * NOTES:
+ * The boxes use tesseract coordinates, i.e. 0,0 is at BOTTOM-LEFT.
*
- * A warning is given if the box is on the same row as the previous box, but NOT
- * on the same row as the previous blob.
+ * is 0-based, and the page number is used for multipage input (tiff).
*
- * Any OUTLINE which overlaps the box is put into the new word.
- *
- * ascender chars must ascend above xht significantly
- * xht chars must not rise above row xht significantly
- * bl chars must not descend below baseline significantly
- * descender chars must descend below baseline significantly
- *
- * ?? Certain chars are DROPPED - to limit the training data.
+ * In the blob-level form, each line represents a recognizable unit, which may
+ * be several UTF-8 bytes, but there is a bounding box around each recognizable
+ * unit, and no classifier is needed to train in this mode (bootstrapping.)
*
+ * In the word/line-level form, the line begins with the literal "WordStr", and
+ * the bounding box bounds either a whole line or a whole word. The recognizable
+ * units in the word/line are listed after the # at the end of the line and
+ * are space delimited, ignoring any original spaces on the line.
+ * Eg.
+ * word -> #w o r d
+ * multi word line -> #m u l t i w o r d l i n e
+ * The recognizable units must be space-delimited in order to allow multiple
+ * unicodes to be used for a single recognizable unit, eg Hindi.
+ * In this mode, the classifier must have been pre-trained with the desired
+ * character set, or it will not be able to find the character segmentations.
*************************************************************************/
-namespace tesseract {
-void Tesseract::apply_boxes(const STRING& fname,
- BLOCK_LIST *block_list //real blocks
- ) {
- inT16 boxfile_lineno = 0;
- inT16 boxfile_charno = 0;
- TBOX box; //boxfile box
- UNICHAR_ID uch_id; //correct ch from boxfile
- ROW *row;
- ROW *prev_row = NULL;
- inT16 prev_box_right = MAX_INT16;
- inT16 block_id;
- inT16 row_id;
- inT16 box_count = 0;
- inT16 box_failures = 0;
- inT16 labels_ok;
- inT16 rows_ok;
- inT16 bad_blobs;
- inT16 *tgt_char_counts = NULL; // No. of box samples
- inT16 i;
- inT16 rebalance_count = 0;
- UNICHAR_ID min_uch_id = INVALID_UNICHAR_ID;
- inT16 min_samples;
- inT16 final_labelled_blob_count;
- bool low_exposure = false;
-
- // Clean the unichar set
- unicharset_boxes.clear();
- // Space character needed to represent NIL classification
- unicharset_boxes.unichar_insert(" ");
-
- // Figure out whether this image file's exposure is less than 1, in which
- // case when learning we will only pay attention to character fragments.
- const char *ptr = strstr(imagefile.string(), exposure_pattern.string());
- if (ptr != NULL &&
- strtol(ptr += strlen(exposure_pattern.string()), NULL, 10) < 0) {
- low_exposure = true;
- }
-
- FILE* box_file;
- STRING filename = fname;
- const char *lastdot; //of name
-
- lastdot = strrchr (filename.string (), '.');
- if (lastdot != NULL)
- filename[lastdot - filename.string()] = '\0';
-
- filename += ".box";
- if (!(box_file = fopen (filename.string(), "r"))) {
- CANTOPENFILE.error ("read_next_box", EXIT,
- "Cant open box file %s %d",
- filename.string(), errno);
- }
- tgt_char_counts = new inT16[MAX_NUM_CLASSES];
- for (i = 0; i < MAX_NUM_CLASSES; i++)
- tgt_char_counts[i] = 0;
-
- clear_any_old_text(block_list);
- while (read_next_box(applybox_page, box_file, &box, &uch_id)) {
- box_count++;
- if (!low_exposure || learn_chars_and_char_frags_mode) {
- tgt_char_counts[uch_id]++;
- }
- row = find_row_of_box (block_list, box, block_id, row_id);
- if (box.left () < prev_box_right) {
- boxfile_lineno++;
- boxfile_charno = 1;
- }
- else
- boxfile_charno++;
+namespace tesseract {
- if (row == NULL) {
- box_failures++;
- report_failed_box (boxfile_lineno, boxfile_charno, box,
- unicharset_boxes.id_to_unichar(uch_id),
- "FAILURE! box overlaps no blobs or blobs in multiple rows");
- }
- else {
- if ((box.left () >= prev_box_right) && (row != prev_row))
- report_failed_box (boxfile_lineno, boxfile_charno, box,
- unicharset_boxes.id_to_unichar(uch_id),
- "WARNING! false row break");
- box_failures += resegment_box (row, box, uch_id, block_id, row_id,
- boxfile_lineno, boxfile_charno, tgt_char_counts, low_exposure, true);
- prev_row = row;
+static void clear_any_old_text(BLOCK_LIST *block_list) {
+ BLOCK_IT block_it(block_list);
+ for (block_it.mark_cycle_pt();
+ !block_it.cycled_list(); block_it.forward()) {
+ ROW_IT row_it(block_it.data()->row_list());
+ for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
+ WERD_IT word_it(row_it.data()->word_list());
+ for (word_it.mark_cycle_pt();
+ !word_it.cycled_list(); word_it.forward()) {
+ word_it.data()->set_text("");
+ }
}
- prev_box_right = box.right ();
}
- tidy_up(block_list,
- labels_ok,
- rows_ok,
- bad_blobs,
- tgt_char_counts,
- rebalance_count,
- &min_uch_id,
- min_samples,
- final_labelled_blob_count,
- low_exposure,
- true);
- tprintf ("APPLY_BOXES:\n");
- tprintf (" Boxes read from boxfile: %6d\n", box_count);
- tprintf (" Initially labelled blobs: %6d in %d rows\n",
- labels_ok, rows_ok);
- tprintf (" Box failures detected: %6d\n", box_failures);
- tprintf (" Duped blobs for rebalance:%6d\n", rebalance_count);
- tprintf (" \"%s\" has fewest samples:%6d\n",
- unicharset_boxes.id_to_unichar(min_uch_id), min_samples);
- tprintf (" Total unlabelled words: %6d\n",
- bad_blobs);
- tprintf (" Final labelled words: %6d\n",
- final_labelled_blob_count);
-
- // Clean up.
- delete[] tgt_char_counts;
}
-int Tesseract::Boxes2BlockList(int box_cnt, TBOX *boxes,
- BLOCK_LIST *block_list,
- bool right2left) {
- inT16 boxfile_lineno = 0;
- inT16 boxfile_charno = 0;
- TBOX box;
- ROW *row;
- ROW *prev_row = NULL;
- inT16 prev_box_right = MAX_INT16;
- inT16 prev_box_left = 0;
- inT16 block_id;
- inT16 row_id;
- inT16 box_failures = 0;
- inT16 labels_ok;
- inT16 rows_ok;
- inT16 bad_blobs;
- inT16 rebalance_count = 0;
- UNICHAR_ID min_uch_id;
- inT16 min_samples;
- inT16 final_labelled_blob_count;
-
+// Applies the box file based on the image name fname, and resegments
+// the words in the block_list (page), with:
+// blob-mode: one blob per line in the box file, words as input.
+// word/line-mode: one blob per space-delimited unit after the #, and one word
+// per line in the box file. (See comment above for box file format.)
+// If find_segmentation is true, (word/line mode) then the classifier is used
+// to re-segment words/lines to match the space-delimited truth string for
+// each box. In this case, the input box may be for a word or even a whole
+// text line, and the output words will contain multiple blobs corresponding
+// to the space-delimited input string.
+// With find_segmentation false, no classifier is needed, but the chopper
+// can still be used to correctly segment touching characters with the help
+// of the input boxes.
+// In the returned PAGE_RES, the WERD_RES are setup as they would be returned
+// from normal classification, ie. with a word, chopped_word, rebuild_word,
+// seam_array, denorm, box_word, and best_state, but NO best_choice or
+// raw_choice, as they would require a UNICHARSET, which we aim to avoid.
+// Instead, the correct_text member of WERD_RES is set, and this may be later
+// converted to a best_choice using CorrectClassifyWords. CorrectClassifyWords
+// is not required before calling ApplyBoxTraining.
+PAGE_RES* Tesseract::ApplyBoxes(const STRING& fname,
+ bool find_segmentation,
+ BLOCK_LIST *block_list) {
+ // In word mode, we use the boxes to make a word for each box, but
+ // in blob mode we use the existing words and maximally chop them first.
+ PAGE_RES* page_res = find_segmentation ? NULL : SetupApplyBoxes(block_list);
+ int box_count = 0;
+ int box_failures = 0;
+
+ FILE* box_file = OpenBoxFile(fname);
clear_any_old_text(block_list);
- for (int box_idx = 0; box_idx < box_cnt; box_idx++) {
- box = boxes[box_idx];
-
- row = find_row_of_box(block_list, box, block_id, row_id);
- // check for a new row
- if ((right2left && box.right () > prev_box_left) ||
- (!right2left && box.left () < prev_box_right)) {
- boxfile_lineno++;
- boxfile_charno = 1;
- }
- else {
- boxfile_charno++;
- }
-
- if (row == NULL) {
- box_failures++;
+ TBOX prev_box, box, next_box;
+ bool found_box = false;
+ char text[kBoxReadBufSize];
+ do {
+ prev_box = box;
+ box = next_box;
+ int line_number = 0; // Line number of the box file.
+ int x_min;
+ int y_min;
+ int x_max;
+ int y_max;
+ char next_text[kBoxReadBufSize];
+ // Keep a look-ahead box, so we can pass the next box into the resegment
+ // functions.
+ found_box = read_next_box(applybox_page, &line_number, box_file, next_text,
+ &x_min, &y_min, &x_max, &y_max);
+ if (found_box) {
+ next_box = TBOX(ICOORD(x_min, y_min), ICOORD (x_max, y_max));
+ ++box_count;
+ } else {
+ next_box = TBOX();
+ next_text[0] = '\0';
}
- else {
- box_failures += resegment_box(row, box, 0, block_id, row_id,
- boxfile_lineno, boxfile_charno,
- NULL, false, false);
- prev_row = row;
+ if (!box.null_box()) {
+ bool foundit = false;
+ if (page_res != NULL)
+ foundit = ResegmentCharBox(page_res, box, next_box, text);
+ else
+ foundit = ResegmentWordBox(block_list, box, next_box, text);
+ if (!foundit) {
+ box_failures++;
+ ReportFailedBox(box_count, box, text,
+ "FAILURE! Couldn't find a matching blob");
+ }
}
- prev_box_right = box.right ();
- prev_box_left = box.left ();
+ strcpy(text, next_text);
+ } while (found_box);
+ if (page_res == NULL) {
+ // In word/line mode, we now maximally chop all the words and resegment
+ // them with the classifier.
+ page_res = SetupApplyBoxes(block_list);
+ ReSegmentByClassification(page_res);
}
-
- tidy_up(block_list, labels_ok, rows_ok, bad_blobs, NULL,
- rebalance_count, &min_uch_id, min_samples, final_labelled_blob_count,
- false, false);
-
- return box_failures;
+ if (applybox_debug > 0) {
+ tprintf("APPLY_BOXES:\n");
+ tprintf(" Boxes read from boxfile: %6d\n", box_count);
+ tprintf(" Boxes failed resegmentation: %6d\n", box_failures);
+ }
+ TidyUp(page_res);
+ return page_res;
}
-} // namespace tesseract
-
-
-static
-void clear_any_old_text( //remove correct text
- BLOCK_LIST *block_list //real blocks
- ) {
- BLOCK_IT block_it(block_list);
- ROW_IT row_it;
- WERD_IT word_it;
-
- for (block_it.mark_cycle_pt ();
- !block_it.cycled_list (); block_it.forward ()) {
- row_it.set_to_list (block_it.data ()->row_list ());
- for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
- word_it.set_to_list (row_it.data ()->word_list ());
- for (word_it.mark_cycle_pt ();
- !word_it.cycled_list (); word_it.forward ()) {
- word_it.data ()->set_text ("");
+// Builds a PAGE_RES from the block_list in the way required for ApplyBoxes:
+// All fuzzy spaces are removed, and all the words are maximally chopped.
+PAGE_RES* Tesseract::SetupApplyBoxes(BLOCK_LIST *block_list) {
+ // Strip all fuzzy space markers to simplify the PAGE_RES.
+ BLOCK_IT b_it(block_list);
+ for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
+ BLOCK* block = b_it.data();
+ ROW_IT r_it(block->row_list());
+ for (r_it.mark_cycle_pt(); !r_it.cycled_list(); r_it.forward ()) {
+ ROW* row = r_it.data();
+ WERD_IT w_it(row->word_list());
+ for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) {
+ WERD* word = w_it.data();
+ if (word->cblob_list()->empty()) {
+ delete w_it.extract();
+ } else {
+ word->set_flag(W_FUZZY_SP, false);
+ word->set_flag(W_FUZZY_NON, false);
+ }
}
}
}
+ PAGE_RES* page_res = new PAGE_RES(block_list, NULL);
+ PAGE_RES_IT pr_it(page_res);
+ WERD_RES* word_res;
+ while ((word_res = pr_it.word()) != NULL) {
+ MaximallyChopWord(pr_it.block()->block, pr_it.row()->row, word_res);
+ pr_it.forward();
+ }
+ return page_res;
}
-static
-UNICHAR_ID register_char(const char *uch) {
- if (!unicharset_boxes.contains_unichar(uch)) {
- unicharset_boxes.unichar_insert(uch);
- if (unicharset_boxes.size() > MAX_NUM_CLASSES) {
- tprintf("Error: Size of unicharset of boxes is "
- "greater than MAX_NUM_CLASSES (%d)\n", MAX_NUM_CLASSES);
- exit(1);
- }
+// Helper to make a WERD_CHOICE from the BLOB_CHOICE_LIST_VECTOR using only
+// the top choices. Avoids problems with very long words.
+static void MakeWordChoice(const BLOB_CHOICE_LIST_VECTOR& char_choices,
+ const UNICHARSET& unicharset,
+ WERD_CHOICE* word_choice) {
+ word_choice->make_bad();
+ for (int i = 0; i < char_choices.size(); ++i) {
+ BLOB_CHOICE_IT it(char_choices[i]);
+ BLOB_CHOICE* bc = it.data();
+ word_choice->append_unichar_id(bc->unichar_id(), 1,
+ bc->rating(), bc->certainty());
}
- return unicharset_boxes.unichar_to_id(uch);
+ word_choice->populate_unichars(unicharset);
}
-static
-BOOL8 read_next_box(int page,
- FILE* box_file,
- TBOX *box,
- UNICHAR_ID *uch_id) {
- int x_min;
- int y_min;
- int x_max;
- int y_max;
- char uch[kBoxReadBufSize];
-
- if (read_next_box(page, box_file, uch, &x_min, &y_min, &x_max, &y_max)) {
- *uch_id = register_char(uch);
- *box = TBOX (ICOORD (x_min, y_min), ICOORD (x_max, y_max));
- return TRUE; // read a box ok
- } else {
- return FALSE; // EOF
+// Tests the chopper by exhaustively running chop_one_blob.
+// The word_res will contain filled chopped_word, seam_array, denorm,
+// box_word and best_state for the maximally chopped word.
+void Tesseract::MaximallyChopWord(BLOCK* block, ROW* row, WERD_RES* word_res) {
+ if (!word_res->SetupForRecognition(unicharset, false, row, block))
+ return;
+ if (chop_debug) {
+ tprintf("Maximally chopping word at:");
+ word_res->word->bounding_box().print();
+ }
+ blob_match_table.init_match_table();
+ BLOB_CHOICE_LIST *match_result;
+ BLOB_CHOICE_LIST_VECTOR *char_choices = new BLOB_CHOICE_LIST_VECTOR();
+ set_denorm(&word_res->denorm);
+ ASSERT_HOST(word_res->chopped_word->blobs != NULL);
+ float rating = static_cast(MAX_INT8);
+ for (TBLOB* blob = word_res->chopped_word->blobs; blob != NULL;
+ blob = blob->next) {
+ // The rating and certainty are not quite arbitrary. Since
+ // select_blob_to_chop uses the worst certainty to choose, they all have
+ // to be different, so starting with MAX_INT8, subtract 1/8 for each blob
+ // in here, and then divide by e each time they are chopped, which
+ // should guarantee a set of unequal values for the whole tree of blobs
+ // produced, however much chopping is required. The chops are thus only
+ // limited by the ability of the chopper to find suitable chop points,
+ // and not by the value of the certainties.
+ match_result = fake_classify_blob(0, rating, -rating);
+ modify_blob_choice(match_result, 0);
+ ASSERT_HOST(!match_result->empty());
+ *char_choices += match_result;
+ rating -= 0.125f;
+ }
+ inT32 blob_number;
+ int right_chop_index = 0;
+ while (chop_one_blob(word_res->chopped_word, char_choices,
+ &blob_number, &word_res->seam_array, &right_chop_index));
+ MakeWordChoice(*char_choices, unicharset, word_res->best_choice);
+ MakeWordChoice(*char_choices, unicharset, word_res->raw_choice);
+ word_res->CloneChoppedToRebuild();
+ blob_match_table.end_match_table();
+ if (char_choices != NULL) {
+ char_choices->delete_data_pointers();
+ delete char_choices;
}
}
+// Helper to compute the dispute resolution metric.
+// Disputed blob resolution. The aim is to give the blob to the most
+// appropriate boxfile box. Most of the time it is obvious, but if
+// two boxfile boxes overlap significantly it is not. If a small boxfile
+// box takes most of the blob, and a large boxfile box does too, then
+// we want the small boxfile box to get it, but if the small box
+// is much smaller than the blob, we don't want it to get it.
+// Details of the disputed blob resolution:
+// Given a box with area A, and a blob with area B, with overlap area C,
+// then the miss metric is (A-C)(B-C)/(AB) and the box with minimum
+// miss metric gets the blob.
+static double BoxMissMetric(const TBOX& box1, const TBOX& box2) {
+ int overlap_area = box1.intersection(box2).area();
+ double miss_metric = box1.area()- overlap_area;
+ miss_metric /= box1.area();
+ miss_metric *= box2.area() - overlap_area;
+ miss_metric /= box2.area();
+ return miss_metric;
+}
-ROW *find_row_of_box( //
- BLOCK_LIST *block_list, //real blocks
- const TBOX &box, //from boxfile
- inT16 &block_id,
- inT16 &row_id_to_process) {
- BLOCK_IT block_it(block_list);
- BLOCK *block;
- ROW_IT row_it;
- ROW *row;
- ROW *row_to_process = NULL;
- inT16 row_id;
- WERD_IT word_it;
- WERD *word;
- BOOL8 polyg;
- PBLOB_IT blob_it;
- PBLOB *blob;
- OUTLINE_IT outline_it;
- OUTLINE *outline;
-
- /*
- Find row to process - error if box REALLY overlaps more than one row. (I.e
- it overlaps blobs in the row - not just overlaps the bounding box of the
- whole row.)
- */
-
- block_id = 0;
- for (block_it.mark_cycle_pt ();
- !block_it.cycled_list (); block_it.forward ()) {
- block_id++;
- row_id = 0;
- block = block_it.data ();
- if (block->bounding_box ().overlap (box)) {
- row_it.set_to_list (block->row_list ());
- for (row_it.mark_cycle_pt ();
- !row_it.cycled_list (); row_it.forward ()) {
- row_id++;
- row = row_it.data ();
- if (row->bounding_box ().overlap (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 ();
- polyg = word->flag (W_POLYGON);
- if (word->bounding_box ().overlap (box)) {
- blob_it.set_to_list (word->gblob_list ());
- for (blob_it.mark_cycle_pt ();
- !blob_it.cycled_list (); blob_it.forward ()) {
- blob = blob_it.data ();
- if (gblob_bounding_box (blob, polyg).
- overlap (box)) {
- outline_it.
- set_to_list (gblob_out_list
- (blob, polyg));
- for (outline_it.mark_cycle_pt ();
- !outline_it.cycled_list ();
- outline_it.forward ()) {
- outline = outline_it.data ();
- if (goutline_bounding_box
- (outline, polyg).major_overlap (box)) {
- if ((row_to_process == NULL) ||
- (row_to_process == row)) {
- row_to_process = row;
- row_id_to_process = row_id;
- }
- else
- /* RETURN ERROR Box overlaps blobs in more than one row */
- return NULL;
- }
- }
- }
- }
- }
+// Gather consecutive blobs that match the given box into the best_state
+// and corresponding correct_text.
+// Fights over which box owns which blobs are settled by pre-chopping and
+// applying the blobs to box or next_box with the least non-overlap.
+// Returns false if the box was in error, which can only be caused by
+// failing to find an appropriate blob for a box.
+// This means that occasionally, blobs may be incorrectly segmented if the
+// chopper fails to find a suitable chop point.
+bool Tesseract::ResegmentCharBox(PAGE_RES* page_res,
+ const TBOX& box, const TBOX& next_box,
+ const char* correct_text) {
+ if (applybox_debug > 1) {
+ tprintf("\nAPPLY_BOX: in ResegmentCharBox() for %s\n", correct_text);
+ }
+ PAGE_RES_IT page_res_it(page_res);
+ WERD_RES* word_res;
+ for (word_res = page_res_it.word(); word_res != NULL;
+ word_res = page_res_it.forward()) {
+ if (!word_res->box_word->bounding_box().major_overlap(box))
+ continue;
+ if (applybox_debug > 1) {
+ tprintf("Checking word box:");
+ word_res->box_word->bounding_box().print();
+ }
+ int word_len = word_res->box_word->length();
+ for (int i = 0; i < word_len; ++i) {
+ int blob_count = 0;
+ for (blob_count = 0; i + blob_count < word_len; ++blob_count) {
+ TBOX blob_box = word_res->box_word->BlobBox(i + blob_count);
+ if (!blob_box.major_overlap(box))
+ break;
+ if (word_res->correct_text[i + blob_count].length() > 0)
+ break; // Blob is claimed already.
+ double current_box_miss_metric = BoxMissMetric(blob_box, box);
+ double next_box_miss_metric = BoxMissMetric(blob_box, next_box);
+ if (applybox_debug > 2) {
+ tprintf("Checking blob:");
+ blob_box.print();
+ tprintf("Current miss metric = %g, next = %g\n",
+ current_box_miss_metric, next_box_miss_metric);
+ }
+ if (current_box_miss_metric > next_box_miss_metric)
+ break; // Blob is a better match for next box.
+ }
+ if (blob_count > 0) {
+ // We refine just the box_word, best_state and correct_text here.
+ // The rebuild_word is made in TidyUp.
+ // blob_count blobs are put together to match the box. Merge the
+ // box_word boxes, save the blob_count in the state and the text.
+ word_res->box_word->MergeBoxes(i, i + blob_count);
+ word_res->best_state[i] = blob_count;
+ word_res->correct_text[i] = correct_text;
+ if (applybox_debug > 2) {
+ tprintf("%d Blobs match: blob box:", blob_count);
+ word_res->box_word->BlobBox(i).print();
+ tprintf("Matches box:");
+ box.print();
+ tprintf("With next box:");
+ next_box.print();
+ }
+ // Eliminated best_state and correct_text entries for the consumed
+ // blobs.
+ for (int j = 1; j < blob_count; ++j) {
+ word_res->best_state.remove(i + 1);
+ word_res->correct_text.remove(i + 1);
+ }
+ // Assume that no box spans multiple source words, so we are done with
+ // this box.
+ if (applybox_debug > 1) {
+ tprintf("Best state = ");
+ for (int j = 0; j < word_res->best_state.size(); ++j) {
+ tprintf("%d ", word_res->best_state[j]);
}
+ tprintf("\n");
}
+ return true;
}
}
}
- return row_to_process;
+ return false; // Failure.
}
-
-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) {
- WERD_LIST new_word_list;
- WERD_IT word_it;
- WERD_IT new_word_it(&new_word_list);
- WERD *word = NULL;
- WERD *new_word = NULL;
- BOOL8 polyg = false;
- PBLOB_IT blob_it;
- PBLOB_IT new_blob_it;
- PBLOB *blob;
- PBLOB *new_blob;
- OUTLINE_IT outline_it;
- OUTLINE_LIST dummy; // Just to initialize new_outline_it.
- OUTLINE_IT new_outline_it = &dummy;
- OUTLINE *outline;
- TBOX new_word_box;
- TBOX curr_outline_box;
- TBOX prev_outline_box;
- float word_x_centre;
- float baseline;
- inT16 error_count = 0; //number of chars lost
- STRING label;
- UNICHAR_ID fragment_uch_id;
- int fragment_index;
- int new_word_it_len;
-
- if (learning && applybox_debug > 6) {
- tprintf("\nAPPLY_BOX: in resegment_box() for %s(%d)\n",
- unicharset_boxes.id_to_unichar(uch_id), uch_id);
+// Consume all source blobs that strongly overlap the given box,
+// putting them into a new word, with the correct_text label.
+// Fights over which box owns which blobs are settled by
+// applying the blobs to box or next_box with the least non-overlap.
+// Returns false if the box was in error, which can only be caused by
+// failing to find an overlapping blob for a box.
+bool Tesseract::ResegmentWordBox(BLOCK_LIST *block_list,
+ const TBOX& box, const TBOX& next_box,
+ const char* correct_text) {
+ if (applybox_debug > 1) {
+ tprintf("\nAPPLY_BOX: in ResegmentWordBox() for %s\n", correct_text);
}
- 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 ();
- polyg = word->flag (W_POLYGON);
- if (word->bounding_box ().overlap (box)) {
- blob_it.set_to_list (word->gblob_list ());
- prev_outline_box = TBOX(); // clear prev_outline_box
- curr_outline_box = TBOX(); // clear curr_outline_box
- for (blob_it.mark_cycle_pt ();
- !blob_it.cycled_list (); blob_it.forward ()) {
- blob = blob_it.data ();
- if (gblob_bounding_box (blob, polyg).overlap (box)) {
- outline_it.set_to_list (gblob_out_list (blob, polyg));
- for (outline_it.mark_cycle_pt ();
- !outline_it.cycled_list (); outline_it.forward ()) {
- outline = outline_it.data ();
- prev_outline_box += curr_outline_box;
- curr_outline_box = goutline_bounding_box(outline, polyg);
- if (curr_outline_box.major_overlap (box)) {
- if (strlen (word->text ()) > 0) {
- if (error_count == 0) {
- error_count = 1;
- if (learning && applybox_debug > 4)
- report_failed_box (boxfile_lineno,
- boxfile_charno,
- box, unicharset_boxes.id_to_unichar(uch_id),
- "FAILURE! box overlaps blob in labelled word");
- }
- if (learning && applybox_debug > 4)
- tprintf ("APPLY_BOXES: ALSO ignoring corrupted char"
- " blk:%d row:%d \"%s\"\n",
- block_id, row_id, word_it.data()->text());
- word_it.data ()->set_text (""); // UN label it
- error_count++;
- }
- // Do not learn from fragments of characters that are broken
- // into very small pieces to avoid picking up noise.
- if ((learn_char_fragments || learn_chars_and_char_frags_mode) &&
- ((C_OUTLINE *)outline)->area() < kMinFragmentOutlineArea) {
- if (applybox_debug > 6) {
- tprintf("APPLY_BOX: fragment outline area %d is too small"
- " - not recording fragments of this character\n",
- ((C_OUTLINE *)outline)->area());
- }
- error_count++;
- }
-
- if (error_count == 0) {
- if (applybox_debug > 6 ) {
- tprintf("APPLY_BOX: Previous ");
- prev_outline_box.print();
- tprintf("APPLY_BOX: Current area: %d ",
- ((C_OUTLINE *)outline)->area());
- curr_outline_box.print();
- }
- // When learning character fragments is enabled, we put
- // outlines that do not overlap on x axis in separate WERDs.
- bool start_new_word =
- (learn_char_fragments || learn_chars_and_char_frags_mode) &&
- !curr_outline_box.major_x_overlap(prev_outline_box);
- if (new_word == NULL || start_new_word) {
- if (new_word != NULL) { // add prev new_word to new_word_list
- new_word_it.add_to_end(new_word);
- }
- // Make a new word with a single blob.
- new_word = word->shallow_copy();
- new_word->set_flag(W_FUZZY_NON, false);
- new_word->set_flag(W_FUZZY_SP, false);
- if (polyg){
- new_blob = new PBLOB;
- } else {
- new_blob = (PBLOB *) new C_BLOB;
- }
- new_blob_it.set_to_list(new_word->gblob_list());
- new_blob_it.add_to_end(new_blob);
- new_outline_it.set_to_list(
- gblob_out_list(new_blob, polyg));
- }
- new_outline_it.add_to_end(outline_it.extract()); // move blob
- }
- }
+ WERD* new_word = NULL;
+ BLOCK_IT b_it(block_list);
+ for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
+ BLOCK* block = b_it.data();
+ if (!box.major_overlap(block->bounding_box()))
+ continue;
+ ROW_IT r_it(block->row_list());
+ for (r_it.mark_cycle_pt(); !r_it.cycled_list(); r_it.forward ()) {
+ ROW* row = r_it.data();
+ if (!box.major_overlap(row->bounding_box()))
+ continue;
+ WERD_IT w_it(row->word_list());
+ for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) {
+ WERD* word = w_it.data();
+ if (applybox_debug > 2) {
+ tprintf("Checking word:");
+ word->bounding_box().print();
+ }
+ if (word->text() != NULL && word->text()[0] != '\0')
+ continue; // Ignore words that are already done.
+ if (!box.major_overlap(word->bounding_box()))
+ continue;
+ 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();
+ TBOX blob_box = blob->bounding_box();
+ if (!blob_box.major_overlap(box))
+ continue;
+ double current_box_miss_metric = BoxMissMetric(blob_box, box);
+ double next_box_miss_metric = BoxMissMetric(blob_box, next_box);
+ if (applybox_debug > 2) {
+ tprintf("Checking blob:");
+ blob_box.print();
+ tprintf("Current miss metric = %g, next = %g\n",
+ current_box_miss_metric, next_box_miss_metric);
}
- if (outline_it.empty()) // no outlines in blob
- delete blob_it.extract(); // so delete blob
+ if (current_box_miss_metric > next_box_miss_metric)
+ continue; // Blob is a better match for next box.
+ if (applybox_debug > 2) {
+ tprintf("Blob match: blob:");
+ blob_box.print();
+ tprintf("Matches box:");
+ box.print();
+ tprintf("With next box:");
+ next_box.print();
+ }
+ if (new_word == NULL) {
+ // Make a new word with a single blob.
+ new_word = word->shallow_copy();
+ new_word->set_text(correct_text);
+ w_it.add_to_end(new_word);
+ }
+ C_BLOB_IT new_blob_it(new_word->cblob_list());
+ new_blob_it.add_to_end(blob_it.extract());
}
}
- if (blob_it.empty()) // no blobs in word
- delete word_it.extract(); // so delete word
}
}
- if (new_word != NULL) { // add prev new_word to new_word_list
- new_word_it.add_to_end(new_word);
- }
- new_word_it_len = new_word_it.length();
+ return new_word != NULL;
+}
- // Check for failures.
- if (error_count > 0)
- return error_count;
- if (learning && new_word_it_len <= 0) {
- report_failed_box(boxfile_lineno, boxfile_charno, box,
- unicharset_boxes.id_to_unichar(uch_id),
- "FAILURE! Couldn't find any blobs");
- return 1; // failure
+// Resegments the words by running the classifier in an attempt to find the
+// correct segmentation that produces the required string.
+void Tesseract::ReSegmentByClassification(PAGE_RES* page_res) {
+ PAGE_RES_IT pr_it(page_res);
+ WERD_RES* word_res;
+ for (; (word_res = pr_it.word()) != NULL; pr_it.forward()) {
+ WERD* word = word_res->word;
+ if (word->text() == NULL || word->text()[0] == '\0')
+ continue; // Ignore words that have no text.
+ // Convert the correct text to a vector of UNICHAR_ID
+ GenericVector target_text;
+ if (!ConvertStringToUnichars(word->text(), &target_text)) {
+ tprintf("APPLY_BOX: FAILURE: can't find class_id for '%s'\n",
+ word->text());
+ pr_it.DeleteCurrentWord();
+ continue;
+ }
+ if (!FindSegmentation(target_text, word_res)) {
+ tprintf("APPLY_BOX: FAILURE: can't find segmentation for '%s'\n",
+ word->text());
+ pr_it.DeleteCurrentWord();
+ continue;
+ }
}
+}
- if (learning && new_word_it_len > CHAR_FRAGMENT::kMaxChunks) {
- tprintf("APPLY_BOXES: too many fragments (%d) for char %s\n",
- new_word_it_len, unicharset_boxes.id_to_unichar(uch_id));
- return 1; // failure
+// Converts the space-delimited string of utf8 text to a vector of UNICHAR_ID.
+// Returns false if an invalid UNICHAR_ID is encountered.
+bool Tesseract::ConvertStringToUnichars(const char* utf8,
+ GenericVector* class_ids) {
+ for (int step = 0; *utf8 != '\0'; utf8 += step) {
+ const char* next_space = strchr(utf8, ' ');
+ if (next_space == NULL)
+ next_space = utf8 + strlen(utf8);
+ step = next_space - utf8;
+ UNICHAR_ID class_id = unicharset.unichar_to_id(utf8, step);
+ if (class_id == INVALID_UNICHAR_ID) {
+ return false;
+ }
+ while (utf8[step] == ' ')
+ ++step;
+ class_ids->push_back(class_id);
}
+ return true;
+}
- // Add labelled character or character fragments to the word list.
- fragment_index = 0;
- new_word_it.move_to_first();
- for (new_word_it.mark_cycle_pt(); !new_word_it.cycled_list();
- new_word_it.forward()) {
- new_word = new_word_it.extract();
- if (new_word_it_len > 1) { // deal with a fragment
- if (learning) {
- label = CHAR_FRAGMENT::to_string(unicharset_boxes.id_to_unichar(uch_id),
- fragment_index, new_word_it_len);
- fragment_uch_id = register_char(label.string());
- new_word->set_text(label.string());
- ++fragment_index;
- // For now we cheat by setting the expected number of char fragments
- // to the number of char fragments actually parsed and labelled.
- // TODO(daria): find out whether this can be improved.
- tgt_char_counts[fragment_uch_id]++;
- } else {
- // No learning involved, Just stick a place-holder string
- new_word->set_text("*");
- }
- if (applybox_debug > 5) {
- tprintf("APPLY_BOX: adding char fragment %s\n", label.string());
- }
- } else { // deal with a regular character
- if (learning) {
- if (!learn_char_fragments || learn_chars_and_char_frags_mode) {
- new_word->set_text(unicharset_boxes.id_to_unichar(uch_id));
- } else {
- // not interested in non-fragmented chars if learning fragments, so
- // unlabel it.
- new_word->set_text("");
- }
- } else {
- // No learning involved here. Just stick a place holder string
- new_word->set_text("*");
+// Resegments the word to achieve the target_text from the classifier.
+// Returns false if the re-segmentation fails.
+// Uses brute-force combination of up to kMaxGroupSize adjacent blobs, and
+// applies a full search on the classifier results to find the best classified
+// segmentation. As a compromise to obtain better recall, 1-1 ambigiguity
+// substitutions ARE used.
+bool Tesseract::FindSegmentation(const GenericVector& target_text,
+ WERD_RES* word_res) {
+ blob_match_table.init_match_table();
+ // Classify all required combinations of blobs and save results in choices.
+ int word_length = word_res->box_word->length();
+ GenericVector* choices =
+ new GenericVector[word_length];
+ for (int i = 0; i < word_length; ++i) {
+ for (int j = 1; j <= kMaxGroupSize && i + j <= word_length; ++j) {
+ BLOB_CHOICE_LIST* match_result = classify_piece(
+ word_res->chopped_word->blobs, word_res->seam_array,
+ i, i + j - 1);
+ if (applybox_debug > 2) {
+ tprintf("%d+%d:", i, j);
+ print_ratings_list("Segment:", match_result, unicharset);
}
+ choices[i].push_back(match_result);
}
- gblob_sort_list(new_word->gblob_list(), polyg);
- word_it.add_to_end(new_word);
- new_word_box = new_word->bounding_box();
- word_x_centre = (new_word_box.left() + new_word_box.right()) / 2.0f;
- baseline = row->base_line(word_x_centre);
}
-
- // All done. Now check if the EOL, BOL flags are set correctly.
- word_it.move_to_first();
- for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
- word = word_it.data();
- word->set_flag(W_BOL, false);
- word->set_flag(W_EOL, false);
+ // Search the segmentation graph for the target text. Must be an exact
+ // match. Using wildcards makes it difficult to find the correct
+ // segmentation even when it is there.
+ word_res->best_state.clear();
+ GenericVector search_segmentation;
+ float best_rating = 0.0f;
+ SearchForText(choices, 0, word_length, target_text, 0, 0.0f,
+ &search_segmentation, &best_rating, &word_res->best_state);
+ blob_match_table.end_match_table();
+ for (int i = 0; i < word_length; ++i)
+ choices[i].delete_data_pointers();
+ delete [] choices;
+ if (word_res->best_state.empty())
+ return false;
+ word_res->correct_text.clear();
+ for (int i = 0; i < target_text.size(); ++i) {
+ word_res->correct_text.push_back(
+ STRING(unicharset.id_to_unichar(target_text[i])));
}
- word->set_flag(W_EOL, true);
- word_it.move_to_first();
- word_it.data()->set_flag(W_BOL, true);
- return 0; //success
+ return true;
+}
-#if 0
- if (strlen(unicharset_boxes.id_to_unichar(uch_id)) == 1) {
- if (STRING (chs_caps_ht).contains (unicharset_boxes.id_to_unichar(uch_id)[0]) &&
- (new_word_box.top () <
- baseline + (1 + applybox_error_band) * row->x_height ())) {
- report_failed_box (boxfile_lineno, boxfile_charno, box,
- unicharset_boxes.id_to_unichar(uch_id),
- "FAILURE! caps-ht char didn't ascend");
- new_word->set_text ("");
- return 1;
- }
- if (STRING (chs_odd_top).contains (unicharset_boxes.id_to_unichar(uch_id)[0]) &&
- (new_word_box.top () <
- baseline + (1 - applybox_error_band) * row->x_height ())) {
- report_failed_box (boxfile_lineno, boxfile_charno, box,
- unicharset_boxes.id_to_unichar(uch_id),
- "FAILURE! Odd top char below xht");
- new_word->set_text ("");
- return 1;
- }
- if (STRING (chs_x_ht).contains (unicharset_boxes.id_to_unichar(uch_id)[0]) &&
- ((new_word_box.top () >
- baseline + (1 + applybox_error_band) * row->x_height ()) ||
- (new_word_box.top () <
- baseline + (1 - applybox_error_band) * row->x_height ()))) {
- report_failed_box (boxfile_lineno, boxfile_charno, box,
- unicharset_boxes.id_to_unichar(uch_id),
- "FAILURE! x-ht char didn't have top near xht");
- new_word->set_text ("");
- return 1;
- }
- if (STRING (chs_non_ambig_bl).contains
- (unicharset_boxes.id_to_unichar(uch_id)[0]) &&
- ((new_word_box.bottom () <
- baseline - applybox_error_band * row->x_height ()) ||
- (new_word_box.bottom () >
- baseline + applybox_error_band * row->x_height ()))) {
- report_failed_box (boxfile_lineno, boxfile_charno, box,
- unicharset_boxes.id_to_unichar(uch_id),
- "FAILURE! non ambig BL char didnt have bottom near baseline");
- new_word->set_text ("");
- return 1;
- }
- if (STRING (chs_odd_bot).contains (unicharset_boxes.id_to_unichar(uch_id)[0]) &&
- (new_word_box.bottom () >
- baseline + applybox_error_band * row->x_height ())) {
- report_failed_box (boxfile_lineno, boxfile_charno, box,
- unicharset_boxes.id_to_unichar(uch_id),
- "FAILURE! Odd bottom char above baseline");
- new_word->set_text ("");
- return 1;
+// Recursive helper to find a match to the target_text (from text_index
+// position) in the choices (from choices_pos position).
+// Choices is an array of GenericVectors, of length choices_length, with each
+// element representing a starting position in the word, and the
+// GenericVector holding classification results for a sequence of consecutive
+// blobs, with index 0 being a single blob, index 1 being 2 blobs etc.
+void Tesseract::SearchForText(const GenericVector* choices,
+ int choices_pos, int choices_length,
+ const GenericVector& target_text,
+ int text_index,
+ float rating, GenericVector* segmentation,
+ float* best_rating,
+ GenericVector* best_segmentation) {
+ const UnicharAmbigsVector& table = getDict().getUnicharAmbigs().dang_ambigs();
+ for (int length = 1; length <= choices[choices_pos].size(); ++length) {
+ // Rating of matching choice or worst choice if no match.
+ float choice_rating = 0.0f;
+ // Find the corresponding best BLOB_CHOICE.
+ BLOB_CHOICE_IT choice_it(choices[choices_pos][length - 1]);
+ for (choice_it.mark_cycle_pt(); !choice_it.cycled_list();
+ choice_it.forward()) {
+ BLOB_CHOICE* choice = choice_it.data();
+ choice_rating = choice->rating();
+ UNICHAR_ID class_id = choice->unichar_id();
+ if (class_id == target_text[text_index]) {
+ break;
}
- if (STRING (chs_desc).contains (unicharset_boxes.id_to_unichar(uch_id)[0]) &&
- (new_word_box.bottom () >
- baseline - applybox_error_band * row->x_height ())) {
- report_failed_box (boxfile_lineno, boxfile_charno, box,
- unicharset_boxes.id_to_unichar(uch_id),
- "FAILURE! Descender doesn't descend");
- new_word->set_text ("");
- return 1;
+ // Search ambigs table.
+ if (class_id < table.size() && table[class_id] != NULL) {
+ AmbigSpec_IT spec_it(table[class_id]);
+ for (spec_it.mark_cycle_pt(); !spec_it.cycled_list();
+ spec_it.forward()) {
+ const AmbigSpec *ambig_spec = spec_it.data();
+ // We'll only do 1-1.
+ if (ambig_spec->wrong_ngram[1] == INVALID_UNICHAR_ID &&
+ ambig_spec->correct_ngram_id == target_text[text_index])
+ break;
+ }
+ if (!spec_it.cycled_list())
+ break; // Found an ambig.
}
}
-#endif
-}
-
-
-/*************************************************************************
- * tidy_up()
- * - report >1 block
- * - sort the words in each row.
- * - report any rows with no labelled words.
- * - report any remaining unlabelled words
- * - report total labelled words
- *
- *************************************************************************/
-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) {
- BLOCK_IT block_it(block_list);
- ROW_IT row_it;
- ROW *row;
- WERD_IT word_it;
- WERD *word;
- WERD *duplicate_word;
- inT16 block_idx = 0;
- inT16 row_idx;
- inT16 all_row_idx = 0;
- BOOL8 row_ok;
- BOOL8 rebalance_needed = FALSE;
- inT16 *labelled_char_counts = NULL; // num unique labelled samples
- inT16 i;
- UNICHAR_ID uch_id;
- UNICHAR_ID prev_uch_id = -1;
- BOOL8 at_dupe_of_prev_word;
- ROW *prev_row = NULL;
- inT16 left;
- inT16 prev_left = -1;
-
- labelled_char_counts = new inT16[MAX_NUM_CLASSES];
- for (i = 0; i < MAX_NUM_CLASSES; i++)
- labelled_char_counts[i] = 0;
-
- ok_char_count = 0;
- ok_row_count = 0;
- unlabelled_words = 0;
- if (learning && (applybox_debug > 4) && (block_it.length () != 1)) {
- if (block_it.length() > 1) {
- tprintf("APPLY_BOXES: More than one block??\n");
- } else {
- tprintf("APPLY_BOXES: No blocks identified.\n");
- }
- }
-
- for (block_it.mark_cycle_pt ();
- !block_it.cycled_list (); block_it.forward ()) {
- block_idx++;
- row_idx = 0;
- row_ok = FALSE;
- row_it.set_to_list (block_it.data ()->row_list ());
- for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
- row_idx++;
- all_row_idx++;
- row = row_it.data ();
- word_it.set_to_list (row->word_list ());
- word_it.sort (word_comparator);
- for (word_it.mark_cycle_pt ();
- !word_it.cycled_list (); word_it.forward ()) {
- word = word_it.data ();
- if (strlen (word->text ()) == 0 ||
- unicharset_boxes.unichar_to_id(word->text()) < 0) {
- unlabelled_words++;
- if (learning && applybox_debug > 4 && !learn_character_fragments) {
- tprintf("APPLY_BOXES: Unlabelled word blk:%d row:%d allrows:%d\n",
- block_idx, row_idx, all_row_idx);
- }
- } else {
- if (word->gblob_list ()->length () != 1)
- tprintf ("APPLY_BOXES: FATALITY - MULTIBLOB Labelled word blk:%d"
- " row:%d allrows:%d\n", block_idx, row_idx, all_row_idx);
-
- ok_char_count++;
- ++labelled_char_counts[unicharset_boxes.unichar_to_id(word->text())];
- row_ok = TRUE;
- }
+ if (choice_it.cycled_list())
+ continue; // No match.
+ segmentation->push_back(length);
+ if (choices_pos + length == choices_length &&
+ text_index + 1 == target_text.size()) {
+ // This is a complete match. If the rating is good record a new best.
+ if (applybox_debug > 2) {
+ tprintf("Complete match, rating = %g, best=%g, seglength=%d, best=%d\n",
+ rating + choice_rating, *best_rating, segmentation->size(),
+ best_segmentation->size());
}
- if ((applybox_debug > 6) && (!row_ok)) {
- tprintf("APPLY_BOXES: Row with no labelled words blk:%d row:%d"
- " allrows:%d\n", block_idx, row_idx, all_row_idx);
+ if (best_segmentation->empty() || rating + choice_rating < *best_rating) {
+ *best_segmentation = *segmentation;
+ *best_rating = rating + choice_rating;
}
- else
- ok_row_count++;
- }
- }
-
- min_samples = 9999;
- for (i = 0; i < unicharset_boxes.size(); i++) {
- if (tgt_char_counts[i] > labelled_char_counts[i]) {
- if (labelled_char_counts[i] <= 1) {
- tprintf("APPLY_BOXES: FATALITY - %d labelled samples of \"%s\" -"
- " target is %d:\n",
- labelled_char_counts[i], unicharset_boxes.debug_str(i).string(),
- tgt_char_counts[i]);
+ } else if (choices_pos + length < choices_length &&
+ text_index + 1 < target_text.size()) {
+ if (applybox_debug > 3) {
+ tprintf("Match found for %d=%s:%s, at %d+%d, recursing...\n",
+ target_text[text_index],
+ unicharset.id_to_unichar(target_text[text_index]),
+ choice_it.data()->unichar_id() == target_text[text_index]
+ ? "Match" : "Ambig",
+ choices_pos, length);
}
- else {
- rebalance_needed = TRUE;
- if (applybox_debug > 0)
- tprintf("APPLY_BOXES: REBALANCE REQD \"%s\" - target of"
- " %d from %d labelled samples\n",
- unicharset_boxes.debug_str(i).string(), tgt_char_counts[i],
- labelled_char_counts[i]);
+ SearchForText(choices, choices_pos + length, choices_length, target_text,
+ text_index + 1, rating + choice_rating, segmentation,
+ best_rating, best_segmentation);
+ if (applybox_debug > 3) {
+ tprintf("End recursion for %d=%s\n", target_text[text_index],
+ unicharset.id_to_unichar(target_text[text_index]));
}
}
- if ((min_samples > labelled_char_counts[i]) && (tgt_char_counts[i] > 0)) {
- min_samples = labelled_char_counts[i];
- *min_uch_id = i;
- }
+ segmentation->truncate(segmentation->size() - 1);
}
+}
- while (applybox_rebalance && rebalance_needed) {
- block_it.set_to_list (block_list);
- for (block_it.mark_cycle_pt ();
- !block_it.cycled_list (); block_it.forward ()) {
- row_it.set_to_list (block_it.data ()->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 ();
- left = word->bounding_box ().left ();
- if (*word->text () != '\0')
- uch_id = unicharset_boxes.unichar_to_id(word->text ());
- else
- uch_id = -1;
- at_dupe_of_prev_word = ((row == prev_row) &&
- (left = prev_left) &&
- (uch_id == prev_uch_id));
- if ((uch_id != -1) &&
- (labelled_char_counts[uch_id] > 1) &&
- (tgt_char_counts[uch_id] > labelled_char_counts[uch_id]) &&
- (!at_dupe_of_prev_word)) {
- /* Duplicate the word to rebalance the labelled samples */
- if (applybox_debug > 9) {
- tprintf ("Duping \"%s\" from ", unicharset_boxes.id_to_unichar(uch_id));
- word->bounding_box ().print ();
- }
- duplicate_word = new WERD;
- *duplicate_word = *word;
- word_it.add_after_then_move (duplicate_word);
- rebalance_count++;
- labelled_char_counts[uch_id]++;
- }
- prev_row = row;
- prev_left = left;
- prev_uch_id = uch_id;
- }
+// Counts up the labelled words and the blobs within.
+// Deletes all unused or emptied words, counting the unused ones.
+// Resets W_BOL and W_EOL flags correctly.
+// Builds the rebuild_word and rebuilds the box_word.
+void Tesseract::TidyUp(PAGE_RES* page_res) {
+ int ok_blob_count = 0;
+ int bad_blob_count = 0;
+ int ok_word_count = 0;
+ int unlabelled_words = 0;
+ PAGE_RES_IT pr_it(page_res);
+ WERD_RES* word_res;
+ for (; (word_res = pr_it.word()) != NULL; pr_it.forward()) {
+ int ok_in_word = 0;
+ for (int i = 0; i < word_res->correct_text.size(); ++i) {
+ if (word_res->correct_text[i].length() > 0) {
+ ++ok_in_word;
}
}
- rebalance_needed = FALSE;
- for (i = 0; i < unicharset_boxes.size(); i++) {
- if ((tgt_char_counts[i] > labelled_char_counts[i]) &&
- (labelled_char_counts[i] > 1)) {
- rebalance_needed = TRUE;
- break;
+ if (ok_in_word > 0) {
+ ok_blob_count += ok_in_word;
+ bad_blob_count += word_res->correct_text.size() - ok_in_word;
+ } else {
+ ++unlabelled_words;
+ if (applybox_debug > 0) {
+ tprintf("APPLY_BOXES: Unlabelled word at :");
+ word_res->word->bounding_box().print();
}
+ pr_it.DeleteCurrentWord();
}
}
-
- /* Now final check - count labeled blobs */
- final_labelled_blob_count = 0;
- block_it.set_to_list (block_list);
- for (block_it.mark_cycle_pt ();
- !block_it.cycled_list (); block_it.forward ()) {
- row_it.set_to_list (block_it.data ()->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 ());
- word_it.sort (word_comparator);
- for (word_it.mark_cycle_pt ();
- !word_it.cycled_list (); word_it.forward ()) {
- word = word_it.data ();
- if ((strlen(word->text ()) > 0) &&
- (word->gblob_list()->length() == 1)) {
- final_labelled_blob_count++;
- } else {
- delete word_it.extract();
- }
- }
- // delete the row if empty
- if (row->word_list()->empty()) {
- delete row_it.extract();
- }
- }
+ pr_it.restart_page();
+ for (; (word_res = pr_it.word()) != NULL; pr_it.forward()) {
+ // Denormalize back to a BoxWord.
+ word_res->RebuildBestState();
+ word_res->SetupBoxWord();
+ word_res->word->set_flag(W_BOL, pr_it.prev_row() != pr_it.row());
+ word_res->word->set_flag(W_EOL, pr_it.next_row() != pr_it.row());
+ }
+ if (applybox_debug > 0) {
+ tprintf(" Found %d good blobs and %d unlabelled blobs in %d words.\n",
+ ok_blob_count, bad_blob_count, ok_word_count);
+ tprintf(" %d remaining unlabelled words deleted.\n", unlabelled_words);
}
-
- // Clean up.
- delete[] labelled_char_counts;
}
-
-void report_failed_box(inT16 boxfile_lineno,
- inT16 boxfile_charno,
- TBOX box,
- const char *box_ch,
- const char *err_msg) {
- if (applybox_debug > 4)
- tprintf ("APPLY_BOXES: boxfile %1d/%1d/%s ((%1d,%1d),(%1d,%1d)): %s\n",
- boxfile_lineno,
- boxfile_charno,
- box_ch,
- box.left (), box.bottom (), box.right (), box.top (), err_msg);
+// Logs a bad box by line in the box file and box coords.
+void Tesseract::ReportFailedBox(int boxfile_lineno, TBOX box,
+ const char *box_ch, const char *err_msg) {
+ tprintf("APPLY_BOXES: boxfile line %d/%s ((%d,%d),(%d,%d)): %s\n",
+ boxfile_lineno, box_ch,
+ box.left(), box.bottom(), box.right(), box.top(), err_msg);
}
-
-void apply_box_training(const STRING& filename, BLOCK_LIST *block_list) {
- BLOCK_IT block_it(block_list);
- ROW_IT row_it;
- ROW *row;
- WERD_IT word_it;
- WERD *word;
- WERD *bln_word;
- WERD copy_outword; // copy to denorm
- PBLOB_IT blob_it;
- DENORM denorm;
- inT16 count = 0;
- char unichar[UNICHAR_LEN + 1];
-
- unichar[UNICHAR_LEN] = '\0';
- tprintf ("Generating training data\n");
- for (block_it.mark_cycle_pt ();
- !block_it.cycled_list (); block_it.forward ()) {
- row_it.set_to_list (block_it.data ()->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 ((strlen (word->text ()) > 0) &&
- (word->gblob_list ()->length () == 1)) {
- // Here is a word with a single unichar label and a single blob so train on it.
- bln_word = make_bln_copy(word, row, NULL, row->x_height (), &denorm);
- blob_it.set_to_list (bln_word->blob_list ());
- strncpy(unichar, word->text (), UNICHAR_LEN);
- tess_training_tester (filename,
- blob_it.data (), //single blob
- &denorm, TRUE, //correct
- unichar, //correct character
- strlen(unichar), //character length
- NULL);
- copy_outword = *(bln_word);
- copy_outword.baseline_denormalise (&denorm);
- blob_it.set_to_list (copy_outword.blob_list ());
- delete bln_word;
- count++;
- }
- }
+// Creates a fake best_choice entry in each WERD_RES with the correct text.
+void Tesseract::CorrectClassifyWords(PAGE_RES* page_res) {
+ PAGE_RES_IT pr_it(page_res);
+ for (WERD_RES *word_res = pr_it.word(); word_res != NULL;
+ word_res = pr_it.forward()) {
+ WERD_CHOICE* choice = new WERD_CHOICE(word_res->correct_text.size());
+ for (int i = 0; i < word_res->correct_text.size(); ++i) {
+ UNICHAR_ID char_id = unicharset.unichar_to_id(
+ word_res->correct_text[i].string());
+ choice->append_unichar_id_space_allocated(char_id, 1, 0.0f, 0.0f);
}
+ choice->populate_unichars(unicharset);
+ if (word_res->best_choice != NULL)
+ delete word_res->best_choice;
+ word_res->best_choice = choice;
}
- tprintf ("Generated training data for %d blobs\n", count);
}
-namespace tesseract {
-void Tesseract::apply_box_testing(BLOCK_LIST *block_list) {
- BLOCK_IT block_it(block_list);
- ROW_IT row_it;
- ROW *row;
- inT16 row_count = 0;
- WERD_IT word_it;
- WERD *word;
- WERD *bln_word;
- inT16 word_count = 0;
- PBLOB_IT blob_it;
- DENORM denorm;
- inT16 count = 0;
- char ch[2];
- WERD *outword; //bln best choice
- //segmentation
- WERD_CHOICE *best_choice; //tess output
- WERD_CHOICE *raw_choice; //top choice permuter
- //detailed results
- BLOB_CHOICE_LIST_CLIST blob_choices;
- inT16 char_count = 0;
- inT16 correct_count = 0;
- inT16 err_count = 0;
- inT16 rej_count = 0;
- #ifndef SECURE_NAMES
- WERDSTATS wordstats; //As from newdiff
- #endif
- char tess_rej_str[3];
- char tess_long_str[3];
-
- ch[1] = '\0';
- strcpy (tess_rej_str, "|A");
- strcpy (tess_long_str, "|B");
-
- for (block_it.mark_cycle_pt ();
- !block_it.cycled_list (); block_it.forward ()) {
- row_it.set_to_list (block_it.data ()->row_list ());
- for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
- row = row_it.data ();
- row_count++;
- word_count = 0;
- 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 ();
- word_count++;
- if ((strlen (word->text ()) == 1) &&
- !STRING (applybox_test_exclusions).contains (*word->text ())
- && (word->gblob_list ()->length () == 1)) {
- // Here is a word with a single char label and a single blob so test it.
- bln_word = make_bln_copy(word, row, NULL, row->x_height (), &denorm);
- blob_it.set_to_list (bln_word->blob_list ());
- ch[0] = *word->text ();
- char_count++;
- best_choice = tess_segment_pass1 (bln_word,
- &denorm,
- &Tesseract::tess_default_matcher,
- raw_choice,
- &blob_choices, outword);
-
- /*
- Test for TESS screw up on word. Recog_word has already ensured that the
- choice list, outword blob lists and best_choice string are the same
- length. A TESS screw up is indicated by a blank filled or 0 length string.
- */
- if ((best_choice->length() == 0) ||
- (strspn(best_choice->unichar_string().string(), " ") ==
- best_choice->unichar_string().length())) {
- rej_count++;
- tprintf ("%d:%d: \"%s\" -> TESS FAILED\n",
- row_count, word_count, ch);
- #ifndef SECURE_NAMES
- wordstats.word (tess_rej_str, 2, ch, 1);
- #endif
- }
- else {
- if ((best_choice->length() != outword->blob_list()->length()) ||
- (best_choice->length() != blob_choices.length())) {
- tprintf
- ("ASSERT FAIL String:\"%s\"; Strlen=%d; #Blobs=%d; #Choices=%d\n",
- best_choice->unichar_string().string(),
- best_choice->length(),
- outword->blob_list ()->length(),
- blob_choices.length());
- }
- ASSERT_HOST(best_choice->length() ==
- outword->blob_list()->length());
- ASSERT_HOST(best_choice->length() == blob_choices.length());
- fix_quotes (best_choice,
- //turn to double
- outword, &blob_choices);
- if (strcmp (best_choice->unichar_string().string(), ch) != 0) {
- err_count++;
- tprintf ("%d:%d: \"%s\" -> \"%s\"\n",
- row_count, word_count, ch,
- best_choice->unichar_string().string());
- }
- else
- correct_count++;
- #ifndef SECURE_NAMES
- if (best_choice->unichar_string().length() > 2)
- wordstats.word(tess_long_str, 2, ch, 1);
- else
- wordstats.word(best_choice->unichar_string().string(),
- best_choice->unichar_string().length(),
- ch, 1);
- #endif
- }
- delete bln_word;
- delete outword;
- delete best_choice;
- delete raw_choice;
- blob_choices.deep_clear ();
- count++;
- }
- }
- }
+// Calls LearnWord to extract features for labelled blobs within each word.
+// Features are written to the given filename.
+void Tesseract::ApplyBoxTraining(const STRING& filename, PAGE_RES* page_res) {
+ PAGE_RES_IT pr_it(page_res);
+ int word_count = 0;
+ for (WERD_RES *word_res = pr_it.word(); word_res != NULL;
+ word_res = pr_it.forward()) {
+ LearnWord(filename.string(), NULL, word_res);
+ ++word_count;
}
- #ifndef SECURE_NAMES
- wordstats.print (1, 100.0);
- wordstats.conf_matrix ();
- tprintf ("Tested %d chars: %d correct; %d rejected by tess; %d errs\n",
- char_count, correct_count, rej_count, err_count);
- #endif
+ tprintf ("Generated training data for %d words\n", word_count);
}
+
} // namespace tesseract
diff --git a/ccmain/applybox.h b/ccmain/applybox.h
deleted file mode 100644
index 66099cf5c7..0000000000
--- a/ccmain/applybox.h
+++ /dev/null
@@ -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
diff --git a/ccmain/blobcmp.cpp b/ccmain/blobcmp.cpp
deleted file mode 100644
index 8d365358c5..0000000000
--- a/ccmain/blobcmp.cpp
+++ /dev/null
@@ -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
diff --git a/ccmain/callnet.cpp b/ccmain/callnet.cpp
deleted file mode 100644
index 506ed57520..0000000000
--- a/ccmain/callnet.cpp
+++ /dev/null
@@ -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
-}
-
-
-//};
diff --git a/ccmain/charcut.cpp b/ccmain/charcut.cpp
index 1e48d7caff..bba7822109 100644
--- a/ccmain/charcut.cpp
+++ b/ccmain/charcut.cpp
@@ -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(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(y_coord, y_coord + 1, &min_y, &max_y);
+ UpdateRange(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));
diff --git a/ccmain/charcut.h b/ccmain/charcut.h
index 0b791bd14a..838ce86920 100644
--- a/ccmain/charcut.h
+++ b/ccmain/charcut.h
@@ -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
diff --git a/ccmain/charsample.cpp b/ccmain/charsample.cpp
deleted file mode 100644
index 1afd506128..0000000000
--- a/ccmain/charsample.cpp
+++ /dev/null
@@ -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
-#include
-#include
-#ifdef __UNIX__
-#include
-#include
-#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;
-}
diff --git a/ccmain/charsample.h b/ccmain/charsample.h
deleted file mode 100644
index 5e53cc1827..0000000000
--- a/ccmain/charsample.h
+++ /dev/null
@@ -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
diff --git a/ccmain/control.cpp b/ccmain/control.cpp
index 8ca9b4f6e4..f35235b92c 100644
--- a/ccmain/control.cpp
+++ b/ccmain/control.cpp
@@ -18,40 +18,35 @@
*
**********************************************************************/
-#include "mfcpch.h"
+#include "mfcpch.h"
-#include "mainblk.h"
-#include
-#include
+#include
+#include
#ifdef __UNIX__
-#include
-#include
-#include
+#include
+#include
+#include
#endif
-#include
-#include "ocrclass.h"
-#include "werdit.h"
-#include "drawfx.h"
-#include "tfacep.h"
-#include "tessbox.h"
-#include "tessvars.h"
-//#include "fxtop.h"
-#include "pgedit.h"
-#include "reject.h"
-#include "adaptions.h"
-#include "charcut.h"
-#include "fixxht.h"
-#include "fixspace.h"
-#include "genblob.h"
-#include "docqual.h"
-#include "control.h"
-#include "secname.h"
-#include "output.h"
-#include "callcpp.h"
-#include "notdll.h"
-#include "tordvars.h"
-#include "adaptmatch.h"
+#include
+#include "ocrclass.h"
+#include "werdit.h"
+#include "drawfx.h"
+#include "tfacep.h"
+#include "tessbox.h"
+#include "tessvars.h"
+#include "pgedit.h"
+#include "reject.h"
+#include "charcut.h"
+#include "fixspace.h"
+#include "genblob.h"
+#include "docqual.h"
+#include "control.h"
+#include "secname.h"
+#include "output.h"
+#include "callcpp.h"
+#include "notdll.h"
#include "globals.h"
+#include "sorthelper.h"
#include "tesseractclass.h"
// Include automatically generated configuration file if running autoconf.
@@ -62,105 +57,9 @@
#define MIN_FONT_ROW_COUNT 8
#define MAX_XHEIGHT_DIFF 3
-#define EXTERN
-//extern "C" {
-//EXTERN BOOL_VAR(tessedit_small_match,FALSE,"Use small matrix matcher");
-
-//extern FILE* matcher_fp;
-//extern FILE* correct_fp;
-//};
-BOOL_VAR (tessedit_small_match, FALSE, "Use small matrix matcher");
-EXTERN BOOL_VAR (tessedit_print_text, FALSE, "Write text to stdout");
-EXTERN BOOL_VAR (tessedit_draw_words, FALSE, "Draw source words");
-EXTERN BOOL_VAR (tessedit_draw_outwords, FALSE, "Draw output words");
-EXTERN BOOL_VAR (tessedit_training_wiseowl, FALSE, "Call WO to learn blobs");
-EXTERN BOOL_VAR (tessedit_training_tess, FALSE, "Call Tess to learn blobs");
-EXTERN BOOL_VAR (tessedit_matcher_is_wiseowl, FALSE, "Call WO to classify");
-EXTERN BOOL_VAR (tessedit_dump_choices, FALSE, "Dump char choices");
-EXTERN BOOL_VAR (tessedit_fix_fuzzy_spaces, TRUE,
-"Try to improve fuzzy spaces");
-EXTERN BOOL_VAR (tessedit_unrej_any_wd, FALSE,
-"Dont bother with word plausibility");
-EXTERN BOOL_VAR (tessedit_fix_hyphens, TRUE, "Crunch double hyphens?");
-
-EXTERN BOOL_VAR (tessedit_reject_fullstops, FALSE, "Reject all fullstops");
-EXTERN BOOL_VAR (tessedit_reject_suspect_fullstops, FALSE,
-"Reject suspect fullstops");
-EXTERN BOOL_VAR (tessedit_redo_xheight, TRUE, "Check/Correct x-height");
-EXTERN BOOL_VAR (tessedit_cluster_adaption_on, TRUE,
-"Do our own adaption - ems only");
-EXTERN BOOL_VAR (tessedit_enable_doc_dict, TRUE,
-"Add words to the document dictionary");
-EXTERN BOOL_VAR (word_occ_first, FALSE, "Do word occ before re-est xht");
-EXTERN BOOL_VAR (tessedit_debug_fonts, FALSE, "Output font info per char");
-EXTERN BOOL_VAR (tessedit_xht_fiddles_on_done_wds, TRUE,
-"Apply xht fix up even if done");
-EXTERN BOOL_VAR (tessedit_xht_fiddles_on_no_rej_wds, TRUE,
-"Apply xht fix up even in no rejects");
-EXTERN INT_VAR (x_ht_check_word_occ, 2, "Check Char Block occupancy");
-EXTERN INT_VAR (x_ht_stringency, 1, "How many confirmed a/n to accept?");
-EXTERN BOOL_VAR (x_ht_quality_check, TRUE, "Dont allow worse quality");
-EXTERN BOOL_VAR (tessedit_debug_block_rejection, FALSE,
-"Block and Row stats");
-EXTERN INT_VAR (debug_x_ht_level, 0, "Reestimate debug");
-EXTERN BOOL_VAR (rej_use_xht, TRUE, "Individual rejection control");
-EXTERN BOOL_VAR (debug_acceptable_wds, FALSE, "Dump word pass/fail chk");
-
-EXTERN STRING_VAR (chs_leading_punct, "('`\"", "Leading punctuation");
-EXTERN
-STRING_VAR (chs_trailing_punct1, ").,;:?!", "1st Trailing punctuation");
-EXTERN STRING_VAR (chs_trailing_punct2, ")'`\"",
-"2nd Trailing punctuation");
-
-EXTERN double_VAR (quality_rej_pc, 0.08,
-"good_quality_doc lte rejection limit");
-EXTERN double_VAR (quality_blob_pc, 0.0,
-"good_quality_doc gte good blobs limit");
-EXTERN double_VAR (quality_outline_pc, 1.0,
-"good_quality_doc lte outline error limit");
-EXTERN double_VAR (quality_char_pc, 0.95,
-"good_quality_doc gte good char limit");
-EXTERN INT_VAR (quality_min_initial_alphas_reqd, 2,
-"alphas in a good word");
-
-EXTERN BOOL_VAR (tessedit_tess_adapt_to_rejmap, FALSE,
-"Use reject map to control Tesseract adaption");
-EXTERN INT_VAR (tessedit_tess_adaption_mode, 0x27,
-"Adaptation decision algorithm for tess");
-EXTERN INT_VAR (tessedit_em_adaption_mode, 0,
-"Adaptation decision algorithm for ems matrix matcher");
-EXTERN BOOL_VAR (tessedit_cluster_adapt_after_pass1, FALSE,
-"Adapt using clusterer after pass 1");
-EXTERN BOOL_VAR (tessedit_cluster_adapt_after_pass2, FALSE,
-"Adapt using clusterer after pass 1");
-EXTERN BOOL_VAR (tessedit_cluster_adapt_after_pass3, FALSE,
-"Adapt using clusterer after pass 1");
-EXTERN BOOL_VAR (tessedit_cluster_adapt_before_pass1, FALSE,
-"Adapt using clusterer before Tess adaping during pass 1");
-EXTERN INT_VAR (tessedit_cluster_adaption_mode, 0,
-"Adaptation decision algorithm for matrix matcher");
-EXTERN BOOL_VAR (tessedit_adaption_debug, FALSE,
-"Generate and print debug information for adaption");
-EXTERN BOOL_VAR (tessedit_minimal_rej_pass1, FALSE,
-"Do minimal rejection on pass 1 output");
-EXTERN BOOL_VAR (tessedit_test_adaption, FALSE,
-"Test adaption criteria");
-EXTERN BOOL_VAR (tessedit_global_adaption, FALSE,
-"Adapt to all docs over time");
-EXTERN BOOL_VAR (tessedit_matcher_log, FALSE, "Log matcher activity");
-EXTERN INT_VAR (tessedit_test_adaption_mode, 3,
-"Adaptation decision algorithm for tess");
-EXTERN BOOL_VAR(save_best_choices, FALSE,
- "Save the results of the recognition step"
- " (blob_choices) within the corresponding WERD_CHOICE");
-
-EXTERN BOOL_VAR (test_pt, FALSE, "Test for point");
-EXTERN double_VAR (test_pt_x, 99999.99, "xcoord");
-EXTERN double_VAR (test_pt_y, 99999.99, "ycoord");
-
-extern int display_ratings;
-extern int number_debug;
-FILE *choice_file = NULL; // Choice file ptr
+const char* const kBackUpConfigFile = "tempconfigdata.config";
+// Multiple of x-height to make a repeated word have spaces in it.
+const double kRepcharGapThreshold = 0.5;
CLISTIZEH (PBLOB) CLISTIZE (PBLOB)
/* DEBUGGING */
@@ -174,20 +73,21 @@ inT16 blob_count(WERD *w) {
*
* Make a word from the selected blobs and run Tess on them.
*
- * @param block_list recognise blobs
+ * @param page_res recognise blobs
* @param selection_box within this box
*/
namespace tesseract {
-void Tesseract::recog_pseudo_word(BLOCK_LIST *block_list,
+void Tesseract::recog_pseudo_word(PAGE_RES* page_res,
TBOX &selection_box) {
WERD *word;
ROW *pseudo_row; // row of word
BLOCK *pseudo_block; // block of word
- word = make_pseudo_word (block_list, selection_box,
- pseudo_block, pseudo_row);
+ word = make_pseudo_word(page_res, selection_box,
+ pseudo_block, pseudo_row);
if (word != NULL) {
- recog_interactive(pseudo_block, pseudo_row, word);
+ WERD_RES word_res(word);
+ recog_interactive(pseudo_block, pseudo_row, &word_res);
delete word;
}
}
@@ -202,32 +102,70 @@ void Tesseract::recog_pseudo_word(BLOCK_LIST *block_list,
* @param row row of word
* @param word word to recognise
*/
-BOOL8 Tesseract::recog_interactive(BLOCK *block,
- ROW *row,
- WERD *word) {
- WERD_RES word_res(word);
+BOOL8 Tesseract::recog_interactive(BLOCK* block, ROW* row, WERD_RES* word_res) {
inT16 char_qual;
inT16 good_char_qual;
- classify_word_pass2(&word_res, block, row);
- #ifndef SECURE_NAMES
+ classify_word_pass2(word_res, block, row);
if (tessedit_debug_quality_metrics) {
- word_char_quality(&word_res, row, &char_qual, &good_char_qual);
+ word_char_quality(word_res, row, &char_qual, &good_char_qual);
tprintf
("\n%d chars; word_blob_quality: %d; outline_errs: %d; char_quality: %d; good_char_quality: %d\n",
- word_res.reject_map.length (), word_blob_quality (&word_res, row),
- word_outline_errs (&word_res), char_qual, good_char_qual);
+ word_res->reject_map.length(), word_blob_quality(word_res, row),
+ word_outline_errs(word_res), char_qual, good_char_qual);
}
- #endif
return TRUE;
}
+// Helper function to check for a target word and handle it appropriately.
+// Inspired by Jetsoft's requirement to process only single words on pass2
+// and beyond.
+// If word_config is not null:
+// If the word_box and target_word_box overlap, read the word_config file
+// else reset to previous config data.
+// return true.
+// else
+// If the word_box and target_word_box overlap or pass <= 1, return true.
+// Note that this function uses a fixed temporary file for storing the previous
+// configs, so it is neither thread-safe, nor process-safe, but the assumption
+// is that it will only be used for one debug window at a time.
+bool Tesseract::ProcessTargetWord(const TBOX& word_box,
+ const TBOX& target_word_box,
+ const char* word_config,
+ int pass) {
+ if (word_config != NULL) {
+ if (word_box.major_overlap(target_word_box)) {
+ if (backup_config_file_ == NULL) {
+ backup_config_file_ = kBackUpConfigFile;
+ FILE* config_fp = fopen(backup_config_file_, "wb");
+ ParamUtils::PrintParams(config_fp, params());
+ fclose(config_fp);
+ ParamUtils::ReadParamsFile(word_config, false, params());
+ }
+ } else {
+ if (backup_config_file_ != NULL) {
+ ParamUtils::ReadParamsFile(backup_config_file_, false, params());
+ backup_config_file_ = NULL;
+ }
+ }
+ } else if (pass > 1 && !word_box.major_overlap(target_word_box)) {
+ return false;
+ }
+ return true;
+}
/**
* recog_all_words()
*
- * Walk the current block list applying the specified word processor function
- * to all words
+ * Walk the page_res, recognizing all the words.
+ * If monitor is not null, it is used as a progress monitor/timeout/cancel.
+ * If dopasses is 0, all recognition passes are run,
+ * 1 just pass 1, 2 passes2 and higher.
+ * If target_word_box is not null, special things are done to words that
+ * overlap the target_word_box:
+ * if word_config is not null, the word config file is read for just the
+ * target word(s), otherwise, on pass 2 and beyond ONLY the target words
+ * are processed (Jetsoft modification.)
*
* @param page_res page structure
* @param monitor progress monitor
@@ -235,350 +173,276 @@ BOOL8 Tesseract::recog_interactive(BLOCK *block,
* @param dopasses 0 - all, 1 just pass 1, 2 passes 2 and higher
*/
-void Tesseract::recog_all_words(PAGE_RES *page_res,
- volatile ETEXT_DESC *monitor,
- TBOX *target_word_box,
- inT16 dopasses) {
- // reset page iterator
- static PAGE_RES_IT page_res_it;
+void Tesseract::recog_all_words(PAGE_RES* page_res,
+ ETEXT_DESC* monitor,
+ const TBOX* target_word_box,
+ const char* word_config,
+ int dopasses) {
+ // reset page iterator
+ // If we only intend to run cube - run it and return.
+ if (tessedit_ocr_engine_mode == OEM_CUBE_ONLY) {
+ run_cube(page_res);
+ return;
+ }
+ // Return if we do not want to run Tesseract.
+ if (tessedit_ocr_engine_mode != OEM_TESSERACT_ONLY &&
+ tessedit_ocr_engine_mode != OEM_TESSERACT_CUBE_COMBINED) return;
+
+ PAGE_RES_IT page_res_it;
inT16 chars_in_word;
inT16 rejects_in_word;
- static CHAR_SAMPLES_LIST em_clusters;
- static CHAR_SAMPLE_LIST ems_waiting;
- static CHAR_SAMPLES_LIST char_clusters;
- static CHAR_SAMPLE_LIST chars_waiting;
inT16 blob_quality = 0;
inT16 outline_errs = 0;
- static inT16 doc_blob_quality = 0;
- static inT16 doc_outline_errs = 0;
- static inT16 doc_char_quality = 0;
inT16 all_char_quality;
inT16 accepted_all_char_quality;
- static inT16 good_char_count = 0;
- static inT16 doc_good_char_quality = 0;
- int i;
-
-
- inT32 tess_adapt_mode = 0;
- static inT32 word_count; // count of words in doc
inT32 word_index; // current word
- static int dict_words;
+ int i;
if (tessedit_minimal_rej_pass1) {
tessedit_test_adaption.set_value (TRUE);
tessedit_minimal_rejection.set_value (TRUE);
}
- if (tessedit_cluster_adapt_before_pass1) {
- tess_adapt_mode = tessedit_tess_adaption_mode;
- tessedit_tess_adaption_mode.set_value (0);
- tessedit_tess_adapt_to_rejmap.set_value (TRUE);
- }
-
-
-if (dopasses==0 || dopasses==1)
-{
- page_res_it.page_res=page_res;
- page_res_it.restart_page();
-
- /* Pass 1 */
- word_count = 0;
- if (monitor != NULL) {
- monitor->ocr_alive = TRUE;
- while (page_res_it.word () != NULL) {
- word_count++;
- page_res_it.forward ();
- }
- page_res_it.restart_page ();
- }
- else
- word_count = 1;
+ if (dopasses==0 || dopasses==1) {
+ page_res_it.page_res=page_res;
+ page_res_it.restart_page();
- word_index = 0;
+ // ****************** Pass 1 *******************
- em_clusters.clear();
- ems_waiting.clear();
- char_clusters.clear();
- chars_waiting.clear();
- dict_words = 0;
- doc_blob_quality = 0;
- doc_outline_errs = 0;
- doc_char_quality = 0;
- good_char_count = 0;
- doc_good_char_quality = 0;
+ // Clear adaptive classifier at the beginning of the page if it is full.
+ // This is done only at the beginning of the page to ensure that the
+ // classifier is not reset at an arbitraty point while processing the page,
+ // which would cripple Passes 2+ if the reset happens towards the end of
+ // Pass 1 on a page with very difficul text.
+ // TODO(daria): preemptively clear the classifier if it is almost full.
+ if (AdaptiveClassifierIsFull()) ResetAdaptiveClassifier();
- while (page_res_it.word () != NULL) {
- set_global_loc_code(LOC_PASS1);
- word_index++;
+ stats_.word_count = 0;
if (monitor != NULL) {
monitor->ocr_alive = TRUE;
- monitor->progress = 30 + 50 * word_index / word_count;
- if ((monitor->end_time != 0 && clock() > monitor->end_time) ||
- (monitor->cancel != NULL && (*monitor->cancel)(monitor->cancel_this,
- dict_words)))
- return;
- }
- classify_word_pass1(page_res_it.word(), page_res_it.row()->row,
- page_res_it.block()->block, FALSE, NULL, NULL);
- if (tessedit_dump_choices) {
- word_dumper(NULL, page_res_it.row()->row, page_res_it.word()->word);
- tprintf("Pass1: %s [%s]\n",
- page_res_it.word()->best_choice->unichar_string().string(),
- page_res_it.word()->best_choice->
- debug_string(unicharset).string());
+ while (page_res_it.word() != NULL) {
+ stats_.word_count++;
+ page_res_it.forward();
+ }
+ page_res_it.restart_page();
+ } else {
+ stats_.word_count = 1;
}
- if (tessedit_test_adaption && !tessedit_minimal_rejection) {
- if (!word_adaptable (page_res_it.word (),
- tessedit_test_adaption_mode)) {
- page_res_it.word ()->reject_map.rej_word_tess_failure();
- // FAKE PERM REJ
- } else {
- // Override rejection mechanisms for this word.
- UNICHAR_ID space = unicharset.unichar_to_id(" ");
- for (i = 0; i < page_res_it.word()->best_choice->length(); i++) {
- if ((page_res_it.word()->best_choice->unichar_id(i) != space) &&
- page_res_it.word()->reject_map[i].rejected())
- page_res_it.word ()->reject_map[i].setrej_minimal_rej_accept();
+ word_index = 0;
+
+ stats_.dict_words = 0;
+ stats_.doc_blob_quality = 0;
+ stats_.doc_outline_errs = 0;
+ stats_.doc_char_quality = 0;
+ stats_.good_char_count = 0;
+ stats_.doc_good_char_quality = 0;
+
+ while (page_res_it.word() != NULL) {
+ set_global_loc_code(LOC_PASS1);
+ word_index++;
+ if (monitor != NULL) {
+ monitor->ocr_alive = TRUE;
+ monitor->progress = 30 + 50 * word_index / stats_.word_count;
+ if (monitor->deadline_exceeded() ||
+ (monitor->cancel != NULL && (*monitor->cancel)(monitor->cancel_this,
+ stats_.dict_words)))
+ return;
+ }
+ if (target_word_box &&
+ !ProcessTargetWord(page_res_it.word()->word->bounding_box(),
+ *target_word_box, word_config, 1)) {
+ page_res_it.forward();
+ continue;
+ }
+ classify_word_pass1(page_res_it.word(), page_res_it.row()->row,
+ page_res_it.block()->block);
+ if (page_res_it.word()->word->flag(W_REP_CHAR)) {
+ fix_rep_char(&page_res_it);
+ page_res_it.forward();
+ continue;
+ }
+ if (tessedit_dump_choices) {
+ word_dumper(NULL, page_res_it.row()->row, page_res_it.word());
+ tprintf("Pass1: %s [%s]\n",
+ page_res_it.word()->best_choice->unichar_string().string(),
+ page_res_it.word()->best_choice->
+ debug_string(unicharset).string());
+ }
+
+ // tessedit_test_adaption enables testing of the accuracy of the
+ // input to the adaptive classifier.
+ if (tessedit_test_adaption && !tessedit_minimal_rejection) {
+ if (!word_adaptable (page_res_it.word(),
+ tessedit_test_adaption_mode)) {
+ page_res_it.word()->reject_map.rej_word_tess_failure();
+ // FAKE PERM REJ
+ } else {
+ // Override rejection mechanisms for this word.
+ UNICHAR_ID space = unicharset.unichar_to_id(" ");
+ for (i = 0; i < page_res_it.word()->best_choice->length(); i++) {
+ if ((page_res_it.word()->best_choice->unichar_id(i) != space) &&
+ page_res_it.word()->reject_map[i].rejected())
+ page_res_it.word()->reject_map[i].setrej_minimal_rej_accept();
+ }
}
}
- }
- if ((tessedit_cluster_adapt_after_pass1
- || tessedit_cluster_adapt_after_pass3
- || tessedit_cluster_adapt_before_pass1)
- && tessedit_cluster_adaption_mode != 0) {
- collect_characters_for_adaption (page_res_it.word (),
- &char_clusters, &chars_waiting);
+ // Count dict words.
+ if (page_res_it.word()->best_choice->permuter() == USER_DAWG_PERM)
+ ++(stats_.dict_words);
+ page_res_it.forward();
}
- // Count dict words.
- if (page_res_it.word()->best_choice->permuter() == USER_DAWG_PERM)
- ++dict_words;
- page_res_it.forward ();
}
- if (tessedit_cluster_adapt_before_pass1)
- tessedit_tess_adaption_mode.set_value (tess_adapt_mode);
-
- page_res_it.restart_page ();
- while ((tessedit_cluster_adapt_after_pass1
- || tessedit_cluster_adapt_before_pass1)
- && page_res_it.word () != NULL) {
- if (monitor != NULL)
- monitor->ocr_alive = TRUE;
- if (tessedit_cluster_adapt_after_pass1)
- adapt_to_good_samples (page_res_it.word (),
- &char_clusters, &chars_waiting);
- else
- classify_word_pass1(page_res_it.word(),
- page_res_it.row()->row,
- page_res_it.block()->block,
- TRUE, &char_clusters, &chars_waiting);
-
- page_res_it.forward ();
- }
-
- //
-
-
- }
+ if (dopasses == 1) return;
-if (dopasses==1) return;
-
- /* Pass 2 */
- page_res_it.restart_page ();
+ // ****************** Pass 2 *******************
+ page_res_it.restart_page();
word_index = 0;
- while (!tessedit_test_adaption && page_res_it.word () != NULL) {
+ while (!tessedit_test_adaption && page_res_it.word() != NULL) {
set_global_loc_code(LOC_PASS2);
word_index++;
if (monitor != NULL) {
monitor->ocr_alive = TRUE;
- monitor->progress = 80 + 10 * word_index / word_count;
- if ((monitor->end_time != 0 && clock() > monitor->end_time) ||
+ monitor->progress = 80 + 10 * word_index / stats_.word_count;
+ if (monitor->deadline_exceeded() ||
(monitor->cancel != NULL && (*monitor->cancel)(monitor->cancel_this,
- dict_words)))
+ stats_.dict_words)))
return;
}
-//changed by jetsoft
-//specific to its needs to extract one word when need
-
- if (target_word_box)
- {
-
- TBOX current_word_box=page_res_it.word ()->word->bounding_box();
- FCOORD center_pt((current_word_box.right()+current_word_box.left())/2,(current_word_box.bottom()+current_word_box.top())/2);
- if (!target_word_box->contains(center_pt))
- {
- page_res_it.forward ();
- continue;
- }
+ // changed by jetsoft
+ // specific to its needs to extract one word when need
+ if (target_word_box &&
+ !ProcessTargetWord(page_res_it.word()->word->bounding_box(),
+ *target_word_box, word_config, 2)) {
+ page_res_it.forward();
+ continue;
}
-//end jetsoft
+ // end jetsoft
classify_word_pass2(page_res_it.word(), page_res_it.block()->block,
page_res_it.row()->row);
+ if (page_res_it.word()->word->flag(W_REP_CHAR) &&
+ !page_res_it.word()->done) {
+ fix_rep_char(&page_res_it);
+ page_res_it.forward();
+ continue;
+ }
if (tessedit_dump_choices) {
- word_dumper(NULL, page_res_it.row()->row, page_res_it.word()->word);
+ word_dumper(NULL, page_res_it.row()->row, page_res_it.word());
tprintf("Pass2: %s [%s]\n",
page_res_it.word()->best_choice->unichar_string().string(),
page_res_it.word()->best_choice->
debug_string(unicharset).string());
}
-
- if (tessedit_em_adaption_mode > 0)
- collect_ems_for_adaption (page_res_it.word (),
- &em_clusters, &ems_waiting);
-
- if (tessedit_cluster_adapt_after_pass2
- && tessedit_cluster_adaption_mode != 0)
- collect_characters_for_adaption (page_res_it.word (),
- &char_clusters, &chars_waiting);
- page_res_it.forward ();
+ page_res_it.forward();
}
- /* Another pass */
+ // ****************** Pass 3 *******************
+ // Fix fuzzy spaces.
set_global_loc_code(LOC_FUZZY_SPACE);
if (!tessedit_test_adaption && tessedit_fix_fuzzy_spaces
- && !tessedit_word_for_word)
- fix_fuzzy_spaces(monitor, word_count, page_res);
-
- if (!tessedit_test_adaption && tessedit_em_adaption_mode != 0)
- // Initially ems only
- print_em_stats(&em_clusters, &ems_waiting);
+ && !tessedit_word_for_word)
+ fix_fuzzy_spaces(monitor, stats_.word_count, page_res);
- /* Pass 3 - used for checking confusion sets */
- page_res_it.restart_page ();
+ // ****************** Pass 4 *******************
+ // Gather statistics on rejects.
+ page_res_it.restart_page();
word_index = 0;
- while (!tessedit_test_adaption && page_res_it.word () != NULL) {
+ while (!tessedit_test_adaption && page_res_it.word() != NULL) {
set_global_loc_code(LOC_MM_ADAPT);
word_index++;
if (monitor != NULL) {
monitor->ocr_alive = TRUE;
- monitor->progress = 95 + 5 * word_index / word_count;
- }
- check_debug_pt (page_res_it.word (), 70);
- /* Use good matches to sort out confusions */
-
-
-//changed by jetsoft
-//specific to its needs to extract one word when need
-
- if (target_word_box)
- {
- TBOX current_word_box=page_res_it.word ()->word->bounding_box();
- FCOORD center_pt((current_word_box.right()+current_word_box.left())/2,(current_word_box.bottom()+current_word_box.top())/2);
- if (!target_word_box->contains(center_pt))
- {
- page_res_it.forward ();
- continue;
- }
+ monitor->progress = 95 + 5 * word_index / stats_.word_count;
}
-// end jetsoft
-
- if (tessedit_em_adaption_mode != 0)
- adapt_to_good_ems (page_res_it.word (), &em_clusters, &ems_waiting);
-
- if (tessedit_cluster_adapt_after_pass2
- && tessedit_cluster_adaption_mode != 0)
- adapt_to_good_samples (page_res_it.word (),
- &char_clusters, &chars_waiting);
-
- UNICHAR_ID dot = unicharset.unichar_to_id(".");
- if (tessedit_reject_fullstops &&
- page_res_it.word()->best_choice->contains_unichar_id(dot)) {
- reject_all_fullstops (page_res_it.word ());
- } else if (tessedit_reject_suspect_fullstops &&
- page_res_it.word()->best_choice->contains_unichar_id(dot)) {
- reject_suspect_fullstops (page_res_it.word ());
+ check_debug_pt(page_res_it.word(), 70);
+
+ // changed by jetsoft
+ // specific to its needs to extract one word when need
+ if (target_word_box &&
+ !ProcessTargetWord(page_res_it.word()->word->bounding_box(),
+ *target_word_box, word_config, 4)) {
+ page_res_it.forward();
+ continue;
}
-
- page_res_it.rej_stat_word ();
- chars_in_word = page_res_it.word ()->reject_map.length ();
- rejects_in_word = page_res_it.word ()->reject_map.reject_count ();
-
- blob_quality = word_blob_quality (page_res_it.word (),
- page_res_it.row ()->row);
- doc_blob_quality += blob_quality;
- outline_errs = word_outline_errs (page_res_it.word ());
- doc_outline_errs += outline_errs;
- word_char_quality (page_res_it.word (),
- page_res_it.row ()->row,
- &all_char_quality, &accepted_all_char_quality);
- doc_char_quality += all_char_quality;
- uinT8 permuter_type = page_res_it.word ()->best_choice->permuter ();
+ // end jetsoft
+
+ page_res_it.rej_stat_word();
+ chars_in_word = page_res_it.word()->reject_map.length();
+ rejects_in_word = page_res_it.word()->reject_map.reject_count();
+
+ blob_quality = word_blob_quality(page_res_it.word(),
+ page_res_it.row()->row);
+ stats_.doc_blob_quality += blob_quality;
+ outline_errs = word_outline_errs(page_res_it.word());
+ stats_.doc_outline_errs += outline_errs;
+ word_char_quality(page_res_it.word(),
+ page_res_it.row()->row,
+ &all_char_quality, &accepted_all_char_quality);
+ stats_.doc_char_quality += all_char_quality;
+ uinT8 permuter_type = page_res_it.word()->best_choice->permuter();
if ((permuter_type == SYSTEM_DAWG_PERM) ||
- (permuter_type == FREQ_DAWG_PERM) ||
- (permuter_type == USER_DAWG_PERM)) {
- good_char_count += chars_in_word - rejects_in_word;
- doc_good_char_quality += accepted_all_char_quality;
+ (permuter_type == FREQ_DAWG_PERM) ||
+ (permuter_type == USER_DAWG_PERM)) {
+ stats_.good_char_count += chars_in_word - rejects_in_word;
+ stats_.doc_good_char_quality += accepted_all_char_quality;
}
- check_debug_pt (page_res_it.word (), 80);
+ check_debug_pt(page_res_it.word(), 80);
if (tessedit_reject_bad_qual_wds &&
- (blob_quality == 0) && (outline_errs >= chars_in_word))
- page_res_it.word ()->reject_map.rej_word_bad_quality ();
- check_debug_pt (page_res_it.word (), 90);
- page_res_it.forward ();
+ (blob_quality == 0) && (outline_errs >= chars_in_word))
+ page_res_it.word()->reject_map.rej_word_bad_quality();
+ check_debug_pt(page_res_it.word(), 90);
+ page_res_it.forward();
}
- page_res_it.restart_page ();
- while (!tessedit_test_adaption
- && tessedit_cluster_adapt_after_pass3 && page_res_it.word () != NULL) {
- if (monitor != NULL)
- monitor->ocr_alive = TRUE;
-
-//changed by jetsoft
-//specific to its needs to extract one word when need
-
- if (target_word_box)
- {
- TBOX current_word_box=page_res_it.word ()->word->bounding_box();
- FCOORD center_pt((current_word_box.right()+current_word_box.left())/2,(current_word_box.bottom()+current_word_box.top())/2);
- if (!target_word_box->contains(center_pt))
- {
- page_res_it.forward ();
- continue;
- }
- }
-
-//end jetsoft
- if (tessedit_cluster_adaption_mode != 0)
- adapt_to_good_samples (page_res_it.word (),
- &char_clusters, &chars_waiting);
- page_res_it.forward ();
+ // ****************** Pass 5 *******************
+ // If cube is loaded and its combiner is present, run it.
+ if (tessedit_ocr_engine_mode == OEM_TESSERACT_CUBE_COMBINED) {
+ run_cube(page_res);
}
- #ifndef SECURE_NAMES
if (tessedit_debug_quality_metrics) {
tprintf
- ("QUALITY: num_chs= %d num_rejs= %d %5.3f blob_qual= %d %5.3f outline_errs= %d %5.3f char_qual= %d %5.3f good_ch_qual= %d %5.3f\n",
+ ("QUALITY: num_chs= %d num_rejs= %d %5.3f blob_qual= %d %5.3f"
+ " outline_errs= %d %5.3f char_qual= %d %5.3f good_ch_qual= %d %5.3f\n",
page_res->char_count, page_res->rej_count,
- page_res->rej_count / (float) page_res->char_count, doc_blob_quality,
- doc_blob_quality / (float) page_res->char_count, doc_outline_errs,
- doc_outline_errs / (float) page_res->char_count, doc_char_quality,
- doc_char_quality / (float) page_res->char_count,
- doc_good_char_quality,
- good_char_count >
- 0 ? doc_good_char_quality / (float) good_char_count : 0.0);
+ page_res->rej_count / static_cast(page_res->char_count),
+ stats_.doc_blob_quality,
+ stats_.doc_blob_quality / static_cast(page_res->char_count),
+ stats_.doc_outline_errs,
+ stats_.doc_outline_errs / static_cast(page_res->char_count),
+ stats_.doc_char_quality,
+ stats_.doc_char_quality / static_cast(page_res->char_count),
+ stats_.doc_good_char_quality,
+ (stats_.good_char_count > 0) ?
+ (stats_.doc_good_char_quality /
+ static_cast(stats_.good_char_count)) : 0.0);
}
- #endif
BOOL8 good_quality_doc =
- (page_res->rej_count / (float) page_res->char_count <= quality_rej_pc)
- &&
- (doc_blob_quality / (float) page_res->char_count >= quality_blob_pc) &&
- (doc_outline_errs / (float) page_res->char_count <= quality_outline_pc) &&
- (doc_char_quality / (float) page_res->char_count >= quality_char_pc);
-
- /* Do whole document or whole block rejection pass*/
-
+ ((page_res->rej_count / static_cast(page_res->char_count)) <=
+ quality_rej_pc) &&
+ (stats_.doc_blob_quality / static_cast(page_res->char_count) >=
+ quality_blob_pc) &&
+ (stats_.doc_outline_errs / static_cast(page_res->char_count) <=
+ quality_outline_pc) &&
+ (stats_.doc_char_quality / static_cast(page_res->char_count) >=
+ quality_char_pc);
+
+ // ****************** Pass 6 *******************
+ // Do whole document or whole block rejection pass
if (!tessedit_test_adaption) {
set_global_loc_code(LOC_DOC_BLK_REJ);
quality_based_rejection(page_res_it, good_quality_doc);
}
+
+ // ****************** Pass 7 *******************
font_recognition_pass(page_res_it);
- /* Write results pass */
+ // Write results pass.
set_global_loc_code(LOC_WRITE_RESULTS);
// This is now redundant, but retained commented so show how to obtain
// bounding boxes and style information.
@@ -586,8 +450,11 @@ if (dopasses==1) return;
// changed by jetsoft
// needed for dll to output memory structure
if ((dopasses == 0 || dopasses == 2) && (monitor || tessedit_write_unlv))
- output_pass(page_res_it, ocr_char_space() > 0, target_word_box);
+ output_pass(page_res_it, target_word_box);
// end jetsoft
+ PageSegMode pageseg_mode = static_cast(
+ static_cast(tessedit_pageseg_mode));
+ textord_.CleanupSingleRowResult(pageseg_mode, page_res);
}
@@ -597,114 +464,49 @@ if (dopasses==1) return;
* Baseline normalize the word and pass it to Tess.
*/
-void Tesseract::classify_word_pass1( //recog one word
- WERD_RES *word, //word to do
+void Tesseract::classify_word_pass1(WERD_RES *word, // word to do
ROW *row,
- BLOCK* block,
- BOOL8 cluster_adapt,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting) {
- WERD *bln_word; //baseline norm copy
- //detailed results
- BLOB_CHOICE_LIST_CLIST local_blob_choices;
- BLOB_CHOICE_LIST_CLIST *blob_choices;
+ BLOCK* block) {
+ BLOB_CHOICE_LIST_CLIST *blob_choices = new BLOB_CHOICE_LIST_CLIST();
BOOL8 adapt_ok;
const char *rejmap;
inT16 index;
STRING mapstr = "";
- char *match_string;
- char word_string[1024];
-
- if (save_best_choices)
- blob_choices = new BLOB_CHOICE_LIST_CLIST();
- else
- blob_choices = &local_blob_choices;
-
- if (matcher_fp != NULL) {
- fgets (word_string, 1023, correct_fp);
- if ((match_string = strchr (word_string, '\r')) != NULL)
- *match_string = '\0';
- if ((match_string = strchr (word_string, '\n')) != NULL)
- *match_string = '\0';
- if (word_string[0] != '\0') {
- word->word->set_text (word_string);
- word_answer = (char *) word->word->text ();
- }
- else
- word_answer = NULL;
- }
-
- check_debug_pt (word, 0);
- bln_word = make_bln_copy(word->word, row, block, word->x_height,
- &word->denorm);
-
- word->best_choice = tess_segment_pass1 (bln_word, &word->denorm,
- &Tesseract::tess_default_matcher,
- word->raw_choice, blob_choices,
- word->outword);
- /*
- Test for TESS screw up on word. Recog_word has already ensured that the
- choice list, outword blob lists and best_choice string are the same
- length. A TESS screw up is indicated by a blank filled or 0 length string.
- */
- if ((word->best_choice->length() == 0) ||
- (strspn (word->best_choice->unichar_string().string(), " ") ==
- word->best_choice->length())) {
- word->done = FALSE; // Try again on pass2 - adaption may help.
- word->tess_failed = TRUE;
- word->reject_map.initialise(word->best_choice->length());
- word->reject_map.rej_word_tess_failure ();
- } else {
- word->tess_failed = FALSE;
- if ((word->best_choice->length() !=
- word->outword->blob_list()->length()) ||
- (word->best_choice->length() != blob_choices->length())) {
- tprintf
- ("ASSERT FAIL String:\"%s\"; Strlen=%d; #Blobs=%d; #Choices=%d\n",
- word->best_choice->debug_string(unicharset).string(),
- word->best_choice->length(),
- word->outword->blob_list()->length(),
- blob_choices->length());
- }
- ASSERT_HOST(word->best_choice->length() ==
- word->outword->blob_list()->length());
- ASSERT_HOST(word->best_choice->length() == blob_choices->length());
+ check_debug_pt(word, 0);
+ if (word->SetupForRecognition(unicharset, classify_bln_numeric_mode,
+ row, block))
+ tess_segment_pass1(word, blob_choices);
+ if (!word->tess_failed) {
/*
The adaption step used to be here. It has been moved to after
make_reject_map so that we know whether the word will be accepted in the
first pass or not. This move will PREVENT adaption to words containing
double quotes because the word will not be identical to what tess thinks
its best choice is. (See CurrentBestChoiceIs in
- danj/microfeatures/stopper.c which is used by AdaptableWord in
- danj/microfeatures/adaptmatch.c)
+ stopper.cpp which is used by AdaptableWord in
+ adaptmatch.cpp)
*/
- if (word->word->flag(W_REP_CHAR)) {
- fix_rep_char(word);
- } else {
+ if (!word->word->flag(W_REP_CHAR)) {
// TODO(daria) delete these hacks when replaced by more generic code.
// Convert '' (double single) to " (single double).
- fix_quotes(word->best_choice, word->outword, blob_choices);
+ fix_quotes(word, blob_choices);
if (tessedit_fix_hyphens) // turn -- to -
- fix_hyphens(word->best_choice, word->outword, blob_choices);
- record_certainty(word->best_choice->certainty(), 1);
- // accounting.
+ fix_hyphens(word, blob_choices);
word->tess_accepted = tess_acceptable_word(word->best_choice,
word->raw_choice);
- word->tess_would_adapt = tess_adaptable_word(word->outword,
- word->best_choice,
- word->raw_choice);
+ word->tess_would_adapt = word->best_choice && word->raw_choice &&
+ AdaptableWord(word->rebuild_word,
+ *word->best_choice,
+ *word->raw_choice);
// Also sets word->done flag
make_reject_map(word, blob_choices, row, 1);
adapt_ok = word_adaptable(word, tessedit_tess_adaption_mode);
- if (cluster_adapt)
- adapt_to_good_samples(word, char_clusters, chars_waiting);
-
if (adapt_ok || tessedit_tess_adapt_to_rejmap) {
if (!tessedit_tess_adapt_to_rejmap) {
rejmap = NULL;
@@ -720,32 +522,115 @@ void Tesseract::classify_word_pass1( //recog one word
}
rejmap = mapstr.string();
}
-
- // adapt to it.
- tess_adapter(word->outword, &word->denorm,
- *word->best_choice,
- *word->raw_choice, rejmap);
+ // Send word to adaptive classifier for training.
+ word->BestChoiceToCorrectText(unicharset);
+ LearnWord(NULL, rejmap, word);
}
if (tessedit_enable_doc_dict)
tess_add_doc_word(word->best_choice);
- set_word_fonts(word, blob_choices);
}
}
-#if 0
- if (tessedit_print_text) {
- write_cooked_text(bln_word, word->best_choice->string(),
- word->done, FALSE, stdout);
- }
-#endif
- delete bln_word;
// Save best choices in the WERD_CHOICE if needed
- if (blob_choices != &local_blob_choices) {
- word->best_choice->set_blob_choices(blob_choices);
+ word->best_choice->set_blob_choices(blob_choices);
+}
+
+// Helper to switch between the original and new xht word or to discard
+// the new xht word, according to accept_new_word.
+static void SwitchWordOrDiscard(bool accept_new_word, WERD_RES* word,
+ WERD_RES* new_word) {
+ if (accept_new_word) {
+ // The new_word is deemed superior so put the final results in the real
+ // word and destroy the old results.
+ word->denorm = new_word->denorm;
+ delete word->chopped_word;
+ word->chopped_word = new_word->chopped_word;
+ new_word->chopped_word = NULL;
+ delete word->rebuild_word;
+ word->rebuild_word = new_word->rebuild_word;
+ new_word->rebuild_word = NULL;
+ delete word->box_word;
+ word->box_word = new_word->box_word;
+ new_word->box_word = NULL;
+ free_seam_list(word->seam_array);
+ word->seam_array = new_word->seam_array;
+ new_word->seam_array = NULL;
+ word->best_state.move(&new_word->best_state);
+ word->correct_text.move(&new_word->correct_text);
+ delete word->best_choice;
+ word->best_choice = new_word->best_choice;
+ new_word->best_choice = NULL;
+ delete word->raw_choice;
+ word->raw_choice = new_word->raw_choice;
+ new_word->raw_choice = NULL;
+ word->reject_map = new_word->reject_map;
+ word->done = new_word->done;
} else {
- blob_choices->deep_clear();
+ // The new_word is no better, so destroy it and cleanup.
+ new_word->ClearResults();
+ }
+}
+
+// Helper to report the result of the xheight fix.
+void Tesseract::ReportXhtFixResult(bool accept_new_word, float new_x_ht,
+ WERD_RES* word, WERD_RES* new_word) {
+ tprintf("New XHT Match:%s = %s ",
+ word->best_choice->unichar_string().string(),
+ word->best_choice->debug_string(unicharset).string());
+ word->reject_map.print(debug_fp);
+ tprintf(" -> %s = %s ",
+ new_word->best_choice->unichar_string().string(),
+ new_word->best_choice->debug_string(unicharset).string());
+ new_word->reject_map.print(debug_fp);
+ tprintf(" %s->%s %s %s\n",
+ word->guessed_x_ht ? "GUESS" : "CERT",
+ new_word->guessed_x_ht ? "GUESS" : "CERT",
+ new_x_ht > 0.1 ? "STILL DOUBT" : "OK",
+ accept_new_word ? "ACCEPTED" : "");
+}
+
+// Run the x-height fix-up, based on min/max top/bottom information in
+// unicharset.
+// Returns true if the word was changed.
+// See the comment in fixxht.cpp for a description of the overall process.
+bool Tesseract::TrainedXheightFix(WERD_RES *word, BLOCK* block, ROW *row) {
+ bool accept_new_x_ht = false;
+ int original_misfits = CountMisfitTops(word);
+ if (original_misfits == 0)
+ return false;
+ float new_x_ht = ComputeCompatibleXheight(word);
+ if (new_x_ht > 0.0f) {
+ WERD_RES new_x_ht_word(word->word);
+ new_x_ht_word.x_height = new_x_ht;
+ new_x_ht_word.caps_height = 0.0;
+ match_word_pass2(&new_x_ht_word, row, block);
+ if (!new_x_ht_word.tess_failed) {
+ int new_misfits = CountMisfitTops(&new_x_ht_word);
+ if (debug_x_ht_level >= 1) {
+ tprintf("Old misfits=%d with x-height %f, new=%d with x-height %f\n",
+ original_misfits, word->x_height,
+ new_misfits, new_x_ht);
+ tprintf("Old rating= %f, certainty=%f, new=%f, %f\n",
+ word->best_choice->rating(), word->best_choice->certainty(),
+ new_x_ht_word.best_choice->rating(),
+ new_x_ht_word.best_choice->certainty());
+ }
+ // The misfits must improve and either the rating or certainty.
+ accept_new_x_ht = new_misfits < original_misfits &&
+ (new_x_ht_word.best_choice->certainty() >
+ word->best_choice->certainty() ||
+ new_x_ht_word.best_choice->rating() <
+ word->best_choice->rating());
+ if (debug_x_ht_level >= 1) {
+ ReportXhtFixResult(accept_new_x_ht, new_x_ht, word, &new_x_ht_word);
+ }
+ }
+ SwitchWordOrDiscard(accept_new_x_ht, word, &new_x_ht_word);
+ if (accept_new_x_ht)
+ return true;
}
+ return false;
}
/**
@@ -755,189 +640,44 @@ void Tesseract::classify_word_pass1( //recog one word
*/
void Tesseract::classify_word_pass2(WERD_RES *word, BLOCK* block, ROW *row) {
- BOOL8 done_this_pass = FALSE;
- WERD_RES new_x_ht_word(word->word);
- float new_x_ht = 0.0;
- inT16 old_xht_reject_count;
- inT16 new_xht_reject_count;
- inT16 old_xht_accept_count;
- inT16 new_xht_accept_count;
- BOOL8 accept_new_x_ht = FALSE;
- inT16 old_chs_in_wd;
- inT16 new_chs_in_wd;
- inT16 old_word_quality;
- inT16 new_word_quality;
- inT16 dummy;
-
+ bool done_this_pass = false;
set_global_subloc_code(SUBLOC_NORM);
check_debug_pt(word, 30);
- if (!word->done ||
- tessedit_training_tess ||
- tessedit_training_wiseowl) {
+ if (!word->done || tessedit_training_tess) {
word->caps_height = 0.0;
if (word->x_height == 0.0f)
word->x_height = row->x_height();
- if (word->outword != NULL) {
- delete word->outword; // get rid of junk
- delete word->best_choice;
- delete word->raw_choice;
- }
- match_word_pass2 (word, row, block, word->x_height);
+ match_word_pass2(word, row, block);
done_this_pass = TRUE;
- check_debug_pt (word, 40);
+ check_debug_pt(word, 40);
}
- if (!word->tess_failed && !word->word->flag (W_REP_CHAR)) {
- set_global_subloc_code(SUBLOC_FIX_XHT);
- if ((tessedit_xht_fiddles_on_done_wds || !word->done) &&
- (tessedit_xht_fiddles_on_no_rej_wds ||
- (word->reject_map.reject_count () > 0))) {
- if ((x_ht_check_word_occ >= 2) && word_occ_first)
- check_block_occ(word);
-
- if (tessedit_redo_xheight)
- re_estimate_x_ht(word, &new_x_ht);
-
- if (((x_ht_check_word_occ >= 2) && !word_occ_first) ||
- ((x_ht_check_word_occ >= 1) && (new_x_ht > 0)))
- check_block_occ(word);
- }
- if (new_x_ht > 0) {
- old_chs_in_wd = word->reject_map.length ();
-
- /* Re-estimated x_ht error suggests a rematch is worthwhile. */
- new_x_ht_word.x_height = new_x_ht;
- new_x_ht_word.caps_height = 0.0;
- match_word_pass2(&new_x_ht_word, row, block, new_x_ht_word.x_height);
- if (!new_x_ht_word.tess_failed) {
- if ((x_ht_check_word_occ >= 1) && word_occ_first)
- check_block_occ(&new_x_ht_word);
-
- re_estimate_x_ht(&new_x_ht_word, &new_x_ht);
-
- if ((x_ht_check_word_occ >= 1) && !word_occ_first)
- check_block_occ(&new_x_ht_word);
-
- old_xht_reject_count = word->reject_map.reject_count ();
- old_xht_accept_count = old_chs_in_wd - old_xht_reject_count;
- new_xht_reject_count = new_x_ht_word.reject_map.reject_count ();
- new_chs_in_wd = new_x_ht_word.reject_map.length ();
- new_xht_accept_count = new_chs_in_wd - new_xht_reject_count;
- accept_new_x_ht =
- ((new_xht_accept_count > old_xht_accept_count) ||
- ((new_xht_accept_count == old_xht_accept_count) &&
- (new_xht_accept_count > 0))) &&
- (!new_x_ht_word.guessed_x_ht ||
- !new_x_ht_word.guessed_caps_ht);
-
- if (accept_new_x_ht && x_ht_quality_check) {
- word_char_quality(word, row, &old_word_quality, &dummy);
- word_char_quality(&new_x_ht_word, row, &new_word_quality, &dummy);
- if (old_word_quality > new_word_quality)
- accept_new_x_ht = FALSE;
- }
-
- if (accept_new_x_ht && (x_ht_stringency > 0)) {
- accept_new_x_ht =
- (count_alphanums (&new_x_ht_word) > x_ht_stringency);
- if (!accept_new_x_ht && rej_use_xht) {
- if (debug_x_ht_level >= 1)
- tprintf
- ("Failed stringency test so reject original word\n");
- word->reject_map.rej_word_xht_fixup ();
- }
- }
-
- #ifndef SECURE_NAMES
- if (debug_x_ht_level >= 1) {
- tprintf ("New XHT Match:: %s ",
- word->best_choice->debug_string(unicharset).string());
- word->reject_map.print (debug_fp);
- tprintf (" -> %s ",
- new_x_ht_word.best_choice->debug_string(
- unicharset).string());
- new_x_ht_word.reject_map.print (debug_fp);
- tprintf (" %s->%s %s %s\n",
- word->guessed_x_ht ? "GUESS" : "CERT",
- new_x_ht_word.guessed_x_ht ? "GUESS" : "CERT",
- new_x_ht > 0.1 ? "STILL DOUBT" : "OK",
- accept_new_x_ht ? "ACCEPTED" : "");
- }
- #endif
- }
- if (accept_new_x_ht) {
- /*
- The new x_ht is deemed superior so put the final results in the real
- word and destroy the old results
- */
- delete word->outword; //get rid of junk
- word->outword = new_x_ht_word.outword;
- word->denorm = new_x_ht_word.denorm;
- delete word->best_choice;
- word->best_choice = new_x_ht_word.best_choice;
- delete word->raw_choice;
- word->raw_choice = new_x_ht_word.raw_choice;
- word->reject_map = new_x_ht_word.reject_map;
- word->done = new_x_ht_word.done;
- done_this_pass = TRUE;
- }
- else {
- /*
- The new x_ht is no better, so destroy the copy word and put any
- uncertain x or cap ht estimate back to default. (I.e. dont blame
- me if its bad!) Conditionally, use any ammended block occ chars.
- */
- //get rid of junk
- delete new_x_ht_word.outword;
- delete new_x_ht_word.best_choice;
- delete new_x_ht_word.raw_choice;
- }
- //to keep new destructor happy
- new_x_ht_word.outword = NULL;
- //to keep new destructor happy
- new_x_ht_word.best_choice = NULL;
- //to keep new destructor happy
- new_x_ht_word.raw_choice = NULL;
-
- if (rej_mostly_reject_mode == 2) {
- reject_mostly_rejects(word);
- tprintf("Rejecting mostly rejects on %s ",
- word->best_choice->debug_string(unicharset).string());
- }
+ if (!word->tess_failed && !word->word->flag(W_REP_CHAR)) {
+ bool accept_new_xht = false;
+ if (unicharset.top_bottom_useful() && unicharset.script_has_xheight()) {
+ // Use the tops and bottoms since they are available.
+ accept_new_xht = TrainedXheightFix(word, block, row);
}
+ if (accept_new_xht)
+ done_this_pass = true;
set_global_subloc_code(SUBLOC_NORM);
-
- if (done_this_pass && !word->done && tessedit_save_stats) {
- STRING word_str;
- word->best_choice->string_and_lengths(unicharset, &word_str, NULL);
- SaveBadWord(word_str.string(), word->best_choice->certainty());
- }
- record_certainty (word->best_choice->certainty(), 2);
- //accounting
}
#ifndef GRAPHICS_DISABLED
if (tessedit_draw_outwords) {
if (fx_win == NULL)
create_fx_win();
clear_fx_win();
- word->outword->plot (fx_win);
- TBOX wbox = word->outword->bounding_box();
+ word->rebuild_word->plot(fx_win);
+ TBOX wbox = word->rebuild_word->bounding_box();
fx_win->ZoomToRectangle(wbox.left(), wbox.top(),
wbox.right(), wbox.bottom());
- //make_picture_current(fx_win);
ScrollView::Update();
}
#endif
set_global_subloc_code(SUBLOC_NORM);
-#if 0
- if (tessedit_print_text) {
- write_cooked_text (word->outword, word->best_choice->string (),
- word->done, done_this_pass, stdout);
- }
-#endif
- check_debug_pt (word, 50);
+ check_debug_pt(word, 50);
}
@@ -947,97 +687,30 @@ void Tesseract::classify_word_pass2(WERD_RES *word, BLOCK* block, ROW *row) {
* Baseline normalize the word and pass it to Tess.
*/
-void Tesseract::match_word_pass2( //recog one word
- WERD_RES *word, //word to do
+void Tesseract::match_word_pass2(WERD_RES *word, //word to do
ROW *row,
- BLOCK* block,
- float x_height) {
- WERD *bln_word; //baseline norm copy
- //detailed results
- BLOB_CHOICE_LIST_CLIST local_blob_choices;
- BLOB_CHOICE_LIST_CLIST *blob_choices;
-
- if (save_best_choices)
- blob_choices = new BLOB_CHOICE_LIST_CLIST();
- else
- blob_choices = &local_blob_choices;
+ BLOCK* block) {
+ BLOB_CHOICE_LIST_CLIST *blob_choices = new BLOB_CHOICE_LIST_CLIST();
- set_global_subsubloc_code(SUBSUBLOC_OTHER);
- if (matcher_fp != NULL) {
- word_answer = (char *) word->word->text ();
- if (word_answer != NULL && word_answer[0] == '\0')
- word_answer = NULL;
- }
- bln_word = make_bln_copy (word->word, row, block, x_height, &word->denorm);
- set_global_subsubloc_code(SUBSUBLOC_TESS);
- if (tessedit_training_tess)
- word->best_choice = correct_segment_pass2 (bln_word,
- &word->denorm,
- &Tesseract::tess_default_matcher,
- tess_training_tester,
- word->raw_choice,
- blob_choices, word->outword);
- else {
- word->best_choice = tess_segment_pass2 (bln_word, &word->denorm,
- &Tesseract::tess_default_matcher,
- word->raw_choice, blob_choices,
- word->outword);
- }
- set_global_subsubloc_code(SUBSUBLOC_OTHER);
- /*
- Test for TESS screw up on word. Recog_word has already ensured that the
- choice list, outword blob lists and best_choice string are the same
- length. A TESS screw up is indicated by a blank filled or 0 length string.
- */
- if ((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->best_choice->length());
- word->reject_map.rej_word_tess_failure ();
- // tprintf("Empty word produced\n");
- }
- else {
- if ((word->best_choice->length() !=
- word->outword->blob_list()->length ()) ||
- (word->best_choice->length() != blob_choices->length())) {
- tprintf
- ("ASSERT FAIL String:\"%s\"; Strlen=%d; #Blobs=%d; #Choices=%d\n",
- word->best_choice->debug_string(unicharset).string(),
- word->best_choice->length(),
- word->outword->blob_list()->length(), blob_choices->length());
- }
- ASSERT_HOST (word->best_choice->length() ==
- word->outword->blob_list()->length());
- ASSERT_HOST (word->best_choice->length() == blob_choices->length());
+ if (word->SetupForRecognition(unicharset, classify_bln_numeric_mode,
+ row, block))
+ tess_segment_pass2(word, blob_choices);
- word->tess_failed = FALSE;
- if (word->word->flag (W_REP_CHAR)) {
- fix_rep_char(word);
- }
- else {
- fix_quotes (word->best_choice,
- word->outword, blob_choices);
+ if (!word->tess_failed) {
+ if (!word->word->flag (W_REP_CHAR)) {
+ fix_quotes(word, blob_choices);
if (tessedit_fix_hyphens)
- fix_hyphens (word->best_choice,
- word->outword, blob_choices);
+ fix_hyphens(word, blob_choices);
/* Dont trust fix_quotes! - though I think I've fixed the bug */
- if ((word->best_choice->length() !=
- word->outword->blob_list()->length()) ||
- (word->best_choice->length() != blob_choices->length())) {
- #ifndef SECURE_NAMES
- tprintf
- ("POST FIX_QUOTES FAIL String:\"%s\"; Strlen=%d; #Blobs=%d; #Choices=%d\n",
- word->best_choice->debug_string(unicharset).string(),
- word->best_choice->length(),
- word->outword->blob_list()->length(), blob_choices->length());
- #endif
+ if (word->best_choice->length() != word->box_word->length() ||
+ word->best_choice->length() != blob_choices->length()) {
+ tprintf("POST FIX_QUOTES 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());
}
- ASSERT_HOST (word->best_choice->length() ==
- word->outword->blob_list()->length());
- ASSERT_HOST (word->best_choice->length() == blob_choices->length());
-
word->tess_accepted = tess_acceptable_word(word->best_choice,
word->raw_choice);
@@ -1046,69 +719,148 @@ void Tesseract::match_word_pass2( //recog one word
}
// Save best choices in the WERD_CHOICE if needed
- if (blob_choices != &local_blob_choices)
- word->best_choice->set_blob_choices(blob_choices);
- else
- blob_choices->deep_clear();
+ word->best_choice->set_blob_choices(blob_choices);
- delete bln_word;
assert (word->raw_choice != NULL);
}
-} // namespace tesseract
+// Helper to find the BLOB_CHOICE in the bc_list that matches the given
+// unichar_id, or NULL if there is no match.
+static BLOB_CHOICE* FindMatchingChoice(UNICHAR_ID char_id,
+ BLOB_CHOICE_LIST* bc_list) {
+ // Find the corresponding best BLOB_CHOICE.
+ BLOB_CHOICE_IT choice_it(bc_list);
+ for (choice_it.mark_cycle_pt(); !choice_it.cycled_list();
+ choice_it.forward()) {
+ BLOB_CHOICE* choice = choice_it.data();
+ if (choice->unichar_id() == char_id) {
+ return choice;
+ }
+ }
+ return NULL;
+}
+
+// Helper to return the best rated BLOB_CHOICE in the whole word that matches
+// the given char_id, or NULL if none can be found.
+static BLOB_CHOICE* FindBestMatchingChoice(UNICHAR_ID char_id,
+ WERD_RES* word_res) {
+ // Find the corresponding best BLOB_CHOICE from any position in the word_res.
+ BLOB_CHOICE* best_choice = NULL;
+ BLOB_CHOICE_LIST_C_IT bc_it(word_res->best_choice->blob_choices());
+ for (bc_it.mark_cycle_pt(); !bc_it.cycled_list(); bc_it.forward()) {
+ BLOB_CHOICE* choice = FindMatchingChoice(char_id, bc_it.data());
+ if (choice != NULL) {
+ if (best_choice == NULL || choice->rating() < best_choice->rating())
+ best_choice = choice;
+ }
+ }
+ return best_choice;
+}
+
+// Helper to insert blob_choice in each location in the leader word if there is
+// no matching BLOB_CHOICE there already, and correct any incorrect results
+// in the best_choice.
+static void CorrectRepcharChoices(BLOB_CHOICE* blob_choice,
+ WERD_RES* word_res) {
+ WERD_CHOICE* word = word_res->best_choice;
+ BLOB_CHOICE_LIST_C_IT bc_it(word->blob_choices());
+ for (bc_it.mark_cycle_pt(); !bc_it.cycled_list(); bc_it.forward()) {
+ BLOB_CHOICE* choice = FindMatchingChoice(blob_choice->unichar_id(),
+ bc_it.data());
+ if (choice == NULL) {
+ BLOB_CHOICE_IT choice_it(bc_it.data());
+ choice_it.add_before_stay_put(new BLOB_CHOICE(*blob_choice));
+ }
+ }
+ // Correct any incorrect results in word.
+ for (int i = 0; i < word->length(); ++i) {
+ if (word->unichar_id(i) != blob_choice->unichar_id())
+ word->set_unichar_id(blob_choice->unichar_id(), i);
+ }
+}
-namespace tesseract {
/**
* fix_rep_char()
- * The word is a repeated char. Find the repeated char character. Make a reject
- * string which rejects any char other than the voted char. Set the word to done
- * to stop rematching it.
- *
+ * The word is a repeated char. (Leader.) Find the repeated char character.
+ * Create the appropriate single-word or multi-word sequence according to
+ * the size of spaces in between blobs, and correct the classifications
+ * where some of the characters disagree with the majority.
*/
-void Tesseract::fix_rep_char(WERD_RES *word_res) {
- struct REP_CH {
- UNICHAR_ID unichar_id;
- int count;
- };
+void Tesseract::fix_rep_char(PAGE_RES_IT* page_res_it) {
+ WERD_RES *word_res = page_res_it->word();
const WERD_CHOICE &word = *(word_res->best_choice);
- REP_CH *rep_ch; // array of char counts
- int rep_ch_count = 0; // how many unique chs
- int i, j;
- int total = 0;
- int max = 0;
- UNICHAR_ID maxch_id = INVALID_UNICHAR_ID; // most common char
+
+ // Find the frequency of each unique character in the word.
UNICHAR_ID space = unicharset.unichar_to_id(" ");
+ SortHelper rep_ch(word.length());
+ for (int i = 0; i < word.length(); ++i) {
+ if (word.unichar_id(i) != space)
+ rep_ch.Add(word.unichar_id(i), 1);
+ }
- rep_ch = new REP_CH[word.length()];
- for (i = 0; i < word.length(); ++i) {
- for (j = 0; j < rep_ch_count &&
- rep_ch[j].unichar_id != word.unichar_id(i); ++j);
- if (j < rep_ch_count) {
- rep_ch[j].count++;
- } else {
- rep_ch[rep_ch_count].unichar_id = word.unichar_id(i);
- rep_ch[rep_ch_count].count = 1;
- rep_ch_count++;
- }
+ // Find the most frequent result.
+ UNICHAR_ID maxch_id = INVALID_UNICHAR_ID; // most common char
+ int max_count = rep_ch.MaxCount(&maxch_id);
+ // Find the best exemplar of a classifier result for maxch_id.
+ BLOB_CHOICE* best_choice = FindBestMatchingChoice(maxch_id, word_res);
+ if (best_choice == NULL) {
+ tprintf("Failed to find a choice for %s, occurring %d times\n",
+ unicharset.debug_str(maxch_id).string(), max_count);
+ return;
}
+ word_res->done = TRUE;
- for (j = 0; j < rep_ch_count; j++) {
- total += rep_ch[j].count;
- if ((rep_ch[j].count > max) && (rep_ch[j].unichar_id != space)) {
- max = rep_ch[j].count;
- maxch_id = rep_ch[j].unichar_id;
- }
+ // Measure the mean space.
+ int total_gap = 0;
+ int gap_count = 0;
+ WERD* werd = word_res->word;
+ C_BLOB_IT blob_it(werd->cblob_list());
+ C_BLOB* prev_blob = blob_it.data();
+ for (blob_it.forward(); !blob_it.at_first(); blob_it.forward()) {
+ C_BLOB* blob = blob_it.data();
+ int gap = blob->bounding_box().left();
+ gap -= prev_blob->bounding_box().right();
+ total_gap += gap;
+ ++gap_count;
+ prev_blob = blob;
}
- // tprintf( "REPEATED CHAR %s len=%d total=%d choice=%c\n",
- // word_str, word_len, total, maxch );
- delete[] rep_ch;
-
- word_res->reject_map.initialise(word.length());
- for (i = 0; i < word.length(); ++i) {
- if (word.unichar_id(i) != maxch_id)
- word_res->reject_map[i].setrej_bad_repetition(); // rej unrecognised blobs
+ if (total_gap > word_res->x_height * gap_count * kRepcharGapThreshold) {
+ // Needs spaces between.
+ ExplodeRepeatedWord(best_choice, page_res_it);
+ } else {
+ // Just correct existing classification.
+ CorrectRepcharChoices(best_choice, word_res);
+ word_res->best_choice->populate_unichars(unicharset);
+ word_res->reject_map.initialise(word.length());
}
- word_res->done = TRUE;
+}
+
+// Explode the word at the given iterator location into individual words
+// of a single given unichar_id defined by best_choice.
+// The original word is deleted, and the replacements copy most of their
+// fields from the original.
+void Tesseract::ExplodeRepeatedWord(BLOB_CHOICE* best_choice,
+ PAGE_RES_IT* page_res_it) {
+ WERD_RES *word_res = page_res_it->word();
+ ASSERT_HOST(best_choice != NULL);
+
+ // Make a new word for each blob in the original.
+ WERD* werd = word_res->word;
+ C_BLOB_IT blob_it(werd->cblob_list());
+ for (; !blob_it.empty(); blob_it.forward()) {
+ bool first_blob = blob_it.at_first();
+ bool last_blob = blob_it.at_last();
+ WERD* blob_word = werd->ConstructFromSingleBlob(first_blob, last_blob,
+ blob_it.extract());
+ WERD_RES* rep_word = page_res_it->InsertCloneWord(*word_res, blob_word);
+ // Setup the single char WERD_RES
+ rep_word->SetupForRecognition(unicharset, false, page_res_it->row()->row,
+ page_res_it->block()->block);
+ rep_word->CloneChoppedToRebuild();
+ BLOB_CHOICE* blob_choice = new BLOB_CHOICE(*best_choice);
+ rep_word->FakeClassifyWord(unicharset, 1, &blob_choice);
+ }
+ page_res_it->DeleteCurrentWord();
}
// TODO(tkielbus) Decide between keeping this behavior here or modifying the
@@ -1131,54 +883,51 @@ static int is_simple_quote(const char* signed_str, int length) {
*(str + 2) == 0x99)));
}
+// Callback helper for fix_quotes returns a double quote if both
+// arguments are quote, otherwise INVALID_UNICHAR_ID.
+UNICHAR_ID Tesseract::BothQuotes(UNICHAR_ID id1, UNICHAR_ID id2) {
+ const char *ch = unicharset.id_to_unichar(id1);
+ const char *next_ch = unicharset.id_to_unichar(id2);
+ if (is_simple_quote(ch, strlen(ch)) &&
+ is_simple_quote(next_ch, strlen(next_ch)))
+ return unicharset.unichar_to_id("\"");
+ return INVALID_UNICHAR_ID;
+}
+
/**
* fix_quotes
*
* Change pairs of quotes to double quotes.
*/
-void Tesseract::fix_quotes(WERD_CHOICE *choice, //choice to fix
- WERD *word, //word to do //char choices
- BLOB_CHOICE_LIST_CLIST *blob_choices) {
+void Tesseract::fix_quotes(WERD_RES* word_res,
+ BLOB_CHOICE_LIST_CLIST* blob_choices) {
if (!unicharset.contains_unichar("\"") ||
!unicharset.get_enabled(unicharset.unichar_to_id("\"")))
return; // Don't create it if it is disallowed.
- PBLOB_IT blob_it = word->blob_list(); // blobs
- BLOB_CHOICE_LIST_C_IT blob_choices_it = blob_choices; // choices
- BLOB_CHOICE_IT it1; // first choices
- BLOB_CHOICE_IT it2; // second choices
+ word_res->ConditionalBlobMerge(
+ unicharset,
+ NewPermanentTessCallback(this, &Tesseract::BothQuotes),
+ NULL,
+ blob_choices);
+}
- int i;
- int modified = false;
- for (i = 0; i < choice->length()-1;
- ++i, blob_it.forward(), blob_choices_it.forward()) {
- const char *ch = unicharset.id_to_unichar(choice->unichar_id(i));
- const char *next_ch = unicharset.id_to_unichar(choice->unichar_id(i+1));
- if (is_simple_quote(ch, strlen(ch)) &&
- is_simple_quote(next_ch, strlen(next_ch))) {
- choice->set_unichar_id(unicharset.unichar_to_id("\""), i);
- choice->remove_unichar_id(i+1);
- modified = true;
- merge_blobs(blob_it.data(), blob_it.data_relative(1));
- blob_it.forward();
- delete blob_it.extract(); // get rid of spare
-
- it1.set_to_list(blob_choices_it.data());
- it2.set_to_list(blob_choices_it.data_relative(1));
- if (it1.data()->certainty() < it2.data()->certainty()) {
- blob_choices_it.forward();
- delete blob_choices_it.extract(); // get rid of spare
- } else {
- delete blob_choices_it.extract(); // get rid of spare
- blob_choices_it.forward();
- }
- }
- }
- if (modified) {
- choice->populate_unichars(unicharset);
- }
+// Callback helper for fix_hyphens returns UNICHAR_ID of - if both
+// arguments are hyphen, otherwise INVALID_UNICHAR_ID.
+UNICHAR_ID Tesseract::BothHyphens(UNICHAR_ID id1, UNICHAR_ID id2) {
+ const char *ch = unicharset.id_to_unichar(id1);
+ const char *next_ch = unicharset.id_to_unichar(id2);
+ if (strlen(ch) == 1 && strlen(next_ch) == 1 &&
+ (*ch == '-' || *ch == '~') && (*next_ch == '-' || *next_ch == '~'))
+ return unicharset.unichar_to_id("-");
+ return INVALID_UNICHAR_ID;
}
+// Callback helper for fix_hyphens returns true if box1 and box2 overlap
+// (assuming both on the same textline, are in order and a chopped em dash.)
+bool Tesseract::HyphenBoxesOverlap(const TBOX& box1, const TBOX& box2) {
+ return box1.right() >= box2.left();
+}
/**
* fix_hyphens
@@ -1186,50 +935,17 @@ void Tesseract::fix_quotes(WERD_CHOICE *choice, //choice to fix
* Change pairs of hyphens to a single hyphen if the bounding boxes touch
* Typically a long dash which has been segmented.
*/
-void Tesseract::fix_hyphens( //crunch double hyphens
- WERD_CHOICE *choice, //choice to fix
- WERD *word, //word to do //char choices
+void Tesseract::fix_hyphens(WERD_RES *word_res,
BLOB_CHOICE_LIST_CLIST *blob_choices) {
if (!unicharset.contains_unichar("-") ||
!unicharset.get_enabled(unicharset.unichar_to_id("-")))
return; // Don't create it if it is disallowed.
- PBLOB_IT blob_it = word->blob_list();
- BLOB_CHOICE_LIST_C_IT blob_choices_it = blob_choices;
- BLOB_CHOICE_IT it1; // first choices
- BLOB_CHOICE_IT it2; // second choices
-
- bool modified = false;
- for (int i = 0; i+1 < choice->length();
- ++i, blob_it.forward (), blob_choices_it.forward ()) {
- const char *ch = unicharset.id_to_unichar(choice->unichar_id(i));
- const char *next_ch = unicharset.id_to_unichar(choice->unichar_id(i+1));
- if (strlen(ch) != 1 || strlen(next_ch) != 1) continue;
- if ((*ch == '-' || *ch == '~') &&
- (*next_ch == '-' || *next_ch == '~') &&
- (blob_it.data()->bounding_box().right() >=
- blob_it.data_relative(1)->bounding_box().left ())) {
- choice->set_unichar_id(unicharset.unichar_to_id("-"), i);
- choice->remove_unichar_id(i+1);
- modified = true;
- merge_blobs(blob_it.data(), blob_it.data_relative(1));
- blob_it.forward();
- delete blob_it.extract(); // get rid of spare
-
- it1.set_to_list(blob_choices_it.data());
- it2.set_to_list(blob_choices_it.data_relative(1));
- if (it1.data()->certainty() < it2.data()->certainty()) {
- blob_choices_it.forward();
- delete blob_choices_it.extract(); // get rid of spare
- } else {
- delete blob_choices_it.extract(); // get rid of spare
- blob_choices_it.forward();
- }
- }
- }
- if (modified) {
- choice->populate_unichars(unicharset);
- }
+ word_res->ConditionalBlobMerge(
+ unicharset,
+ NewPermanentTessCallback(this, &Tesseract::BothHyphens),
+ NewPermanentTessCallback(this, &Tesseract::HyphenBoxesOverlap),
+ blob_choices);
}
} // namespace tesseract
@@ -1252,85 +968,8 @@ void merge_blobs( //combine 2 blobs
outline_it.add_list_after (blob2->out_list ());
}
-
-/**********************************************************************
- * choice_dump_tester
- *
- * Matcher tester function which generates .chc file entries.
- * Called via test_segment_pass2 for every blob tested by tess in a word.
- * (But only for words for which a correct segmentation could be found.)
- **********************************************************************/
-/* DEADCODE
-void choice_dump_tester( //dump chars in word
- PBLOB *, //blob
- DENORM *, //de-normaliser
- BOOL8 correct, //ly segmented
- char *text, //correct text
- inT32 count, //chars in text
- BLOB_CHOICE_LIST *ratings //list of results
- ) {
- STRING choice_file_name;
- BLOB_CHOICE *blob_choice;
- BLOB_CHOICE_IT it;
- char source_chars[20];
- char correct_char[3];
-
- if (choice_file == NULL) {
- choice_file_name = imagebasename + ".chc";
- if (!(choice_file = fopen (choice_file_name.string (), "w"))) {
- CANTOPENFILE.error ("choice_dump_tester", EXIT, "%s %d",
- choice_file_name.string (), errno);
- }
- }
-
- if ((count == 0) || (text == NULL) || (text[0] == '\0')) {
- strcpy (source_chars, "$$");
- strcpy (correct_char, "$$");
- }
- else {
- strncpy(source_chars, text, count);
- source_chars[count] = '\0';
- if (correct) {
- correct_char[0] = text[0];
- correct_char[1] = '\0';
- }
- else {
- strcpy (correct_char, "$$");
- }
- }
- fprintf (choice_file, "%s\t%s", source_chars, correct_char);
-
- it.set_to_list (ratings);
- for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
- blob_choice = it.data ();
- fprintf (choice_file, "\t%s\t%f\t%f",
- blob_choice->unichar (),
- blob_choice->rating (), blob_choice->certainty ());
- }
- fprintf (choice_file, "\n");
-}
-*/
-
-/**
- * make_bln_copy()
- *
- * Generate a baseline normalised copy of the source word. The copy is done so
- * that whatever format the original word is in, a polygonal bln version is
- * generated as output.
- */
-
-WERD *make_bln_copy(WERD *src_word, ROW *row, BLOCK* block,
- float x_height, DENORM *denorm) {
- WERD *result = src_word->poly_copy(row->x_height());
-
- result->baseline_normalise_x(row, x_height, denorm);
- if (block != NULL)
- denorm->set_block(block);
- return result;
-}
-
-
namespace tesseract {
+
ACCEPTABLE_WERD_TYPE Tesseract::acceptable_word_string(const char *s,
const char *lengths) {
int i = 0;
@@ -1438,11 +1077,7 @@ ACCEPTABLE_WERD_TYPE Tesseract::acceptable_word_string(const char *s,
return word_type;
}
-} // namespace tesseract
-
-/* DEBUGGING ROUTINE */
-
-BOOL8 check_debug_pt(WERD_RES *word, int location) {
+BOOL8 Tesseract::check_debug_pt(WERD_RES *word, int location) {
BOOL8 show_map_detail = FALSE;
inT16 i;
@@ -1452,23 +1087,17 @@ BOOL8 check_debug_pt(WERD_RES *word, int location) {
tessedit_rejection_debug.set_value (FALSE);
debug_x_ht_level.set_value (0);
- tessedit_cluster_debug.set_value (FALSE);
- nn_debug.set_value (FALSE);
- nn_reject_debug.set_value (FALSE);
if (word->word->bounding_box ().contains (FCOORD (test_pt_x, test_pt_y))) {
if (location < 0)
- return TRUE; //For breakpoint use
+ return TRUE; // For breakpoint use
tessedit_rejection_debug.set_value (TRUE);
debug_x_ht_level.set_value (20);
- tessedit_cluster_debug.set_value (TRUE);
- nn_debug.set_value (TRUE);
- nn_reject_debug.set_value (TRUE);
tprintf ("\n\nTESTWD::");
switch (location) {
case 0:
tprintf ("classify_word_pass1 start\n");
- word->word->print (debug_fp);
+ word->word->print();
break;
case 10:
tprintf ("make_reject_map: initial map");
@@ -1520,7 +1149,6 @@ BOOL8 check_debug_pt(WERD_RES *word, int location) {
word->reject_map[i].full_print(debug_fp);
}
}
-
tprintf ("Tess Accepted: %s\n", word->tess_accepted ? "TRUE" : "FALSE");
tprintf ("Done flag: %s\n\n", word->done ? "TRUE" : "FALSE");
return TRUE;
@@ -1530,47 +1158,77 @@ BOOL8 check_debug_pt(WERD_RES *word, int location) {
return FALSE;
}
+/**
+ * find_modal_font
+ *
+ * Find the modal font and remove from the stats.
+ */
+static void find_modal_font( //good chars in word
+ STATS *fonts, //font stats
+ inT8 *font_out, //output font
+ inT8 *font_count //output count
+ ) {
+ inT8 font; //font index
+ inT32 count; //pile couat
+
+ if (fonts->get_total () > 0) {
+ font = (inT8) fonts->mode ();
+ *font_out = font;
+ count = fonts->pile_count (font);
+ *font_count = count < MAX_INT8 ? count : MAX_INT8;
+ fonts->add (font, -*font_count);
+ }
+ else {
+ *font_out = -1;
+ *font_count = 0;
+ }
+}
/**
* set_word_fonts
*
* Get the fonts for the word.
*/
-namespace tesseract {
-void Tesseract::set_word_fonts(
- WERD_RES *word, // word to adapt to
- BLOB_CHOICE_LIST_CLIST *blob_choices // detailed results
- ) {
+void Tesseract::set_word_fonts(WERD_RES *word,
+ BLOB_CHOICE_LIST_CLIST *blob_choices) {
+ if (blob_choices == NULL) return;
+ // Don't try to set the word fonts for a cube word, as the configs
+ // will be meaningless.
+ if (word->chopped_word == NULL) return;
+
inT32 index; // char id index
- UNICHAR_ID choice_char_id; // char id from word
- inT8 config; // font of char
// character iterator
BLOB_CHOICE_LIST_C_IT char_it = blob_choices;
BLOB_CHOICE_IT choice_it; // choice iterator
int fontinfo_size = get_fontinfo_table().size();
int fontset_size = get_fontset_table().size();
- if (fontinfo_size == 0 || fontset_size == 0)
- return;
+ if (fontinfo_size == 0 || fontset_size == 0) return;
STATS fonts(0, fontinfo_size); // font counters
word->italic = 0;
word->bold = 0;
+ if (!word->best_choice_fontinfo_ids.empty()) {
+ word->best_choice_fontinfo_ids.clear();
+ }
+ // Compute the modal font for the word
for (char_it.mark_cycle_pt(), index = 0;
!char_it.cycled_list(); ++index, char_it.forward()) {
- choice_char_id = word->best_choice->unichar_id(index);
+ UNICHAR_ID word_ch_id = word->best_choice->unichar_id(index);
+ if (word_ch_id >= PreTrainedTemplates->NumClasses)
+ return; // This must be a cube word.
choice_it.set_to_list(char_it.data());
for (choice_it.mark_cycle_pt(); !choice_it.cycled_list();
choice_it.forward()) {
- if (choice_it.data()->unichar_id() == choice_char_id) {
- config = choice_it.data()->config();
- int class_id = choice_it.data()->unichar_id();
- int font_set_id = PreTrainedTemplates->Class[class_id]->font_set_id;
+ UNICHAR_ID blob_ch_id = choice_it.data()->unichar_id();
+ if (blob_ch_id == word_ch_id) {
+ int config = choice_it.data()->config();
+ int config2 = choice_it.data()->config2();
+ int font_set_id = PreTrainedTemplates->Class[blob_ch_id]->font_set_id;
if (font_set_id >= 0 && config >= 0 && font_set_id < fontset_size) {
FontSet font_set = get_fontset_table().get(font_set_id);
if (tessedit_debug_fonts) {
- tprintf("%s(%d=%d%c%c)", unicharset.id_to_unichar(choice_char_id),
- config, (config & 31) >> 2,
- config & 2 ? 'N' : 'B', config & 1 ? 'N' : 'I');
+ tprintf("%s(%d/%d)", unicharset.id_to_unichar(blob_ch_id),
+ config, config2);
const char* fontname;
if (config >= font_set.size) {
fontname = "Unknown";
@@ -1582,12 +1240,16 @@ void Tesseract::set_word_fonts(
unicharset.id_to_unichar(choice_it.data()->unichar_id()),
font_set_id, config, fontname);
}
+ // 1st choice config gets 2 pts, 2nd choice 1 pt.
if (config < font_set.size) {
int fontinfo_id = font_set.configs[config];
if (fontinfo_id < fontinfo_size) {
- FontInfo fi = get_fontinfo_table().get(fontinfo_id);
- word->italic += fi.is_italic();
- word->bold += fi.is_bold();
+ fonts.add(fontinfo_id, 2);
+ }
+ }
+ if (config2 >= 0 && config2 < font_set.size) {
+ int fontinfo_id = font_set.configs[config2];
+ if (fontinfo_id < fontinfo_size) {
fonts.add(fontinfo_id, 1);
}
}
@@ -1598,33 +1260,25 @@ void Tesseract::set_word_fonts(
}
find_modal_font(&fonts, &word->font1, &word->font1_count);
find_modal_font(&fonts, &word->font2, &word->font2_count);
- if (tessedit_debug_fonts)
- tprintf("\n");
+ // All the blobs get the word's best choice font.
+ for (int i = 0; i < word->best_choice->length(); ++i) {
+ word->best_choice_fontinfo_ids.push_back(word->font1);
+ }
if (word->font1_count > 0) {
- word->italic = word->bold = 0;
- for (char_it.mark_cycle_pt(), index = 0;
- !char_it.cycled_list(); char_it.forward(), ++index) {
- choice_char_id = word->best_choice->unichar_id(index);
- choice_it.set_to_list(char_it.data());
- for (choice_it.mark_cycle_pt(); !choice_it.cycled_list();
- choice_it.forward()) {
- if (choice_it.data()->unichar_id() == choice_char_id) {
- config = choice_it.data()->config();
- int class_id = choice_it.data()->unichar_id();
- int font_set_id = PreTrainedTemplates->Class[class_id]->font_set_id;
- if (font_set_id >= 0 && config >= 0 && font_set_id < fontset_size) {
- int fontinfo_id = get_fontset_table().get(font_set_id).
- configs[config];
- if (fontinfo_id == word->font1 && fontinfo_id < fontinfo_size) {
- FontInfo fi = fontinfo_table_.get(fontinfo_id);
- word->italic += fi.is_italic();
- word->bold += fi.is_bold();
- }
- }
- break;
- }
+ FontInfo fi = fontinfo_table_.get(word->font1);
+ if (tessedit_debug_fonts) {
+ if (word->font2_count > 0) {
+ tprintf("Word modal font=%s, score=%d, 2nd choice %s/%d\n",
+ fi.name, word->font1_count,
+ fontinfo_table_.get(word->font2).name, word->font2_count);
+ } else {
+ tprintf("Word modal font=%s, score=%d. No 2nd choice\n",
+ fi.name, word->font1_count);
}
}
+ // 1st choices got 2 pts, so we need to halve the score for the mode.
+ word->italic = (fi.is_italic() ? 1 : -1) * (word->font1_count + 1) / 2;
+ word->bold = (fi.is_bold() ? 1 : -1) * (word->font1_count + 1) / 2;
}
}
@@ -1641,173 +1295,42 @@ void Tesseract::font_recognition_pass( //good chars in word
inT32 count; //of a feature
inT8 doc_font; //modal font
inT8 doc_font_count; //modal font
- inT32 doc_italic; //total italics
- inT32 doc_bold; //total bolds
- ROW_RES *row = NULL; //current row
WERD_RES *word; //current word
- STATS fonts (0, get_fontinfo_table().size() ?
- get_fontinfo_table().size() : 32); // font counters
STATS doc_fonts (0, get_fontinfo_table().size() ?
get_fontinfo_table().size() : 32); // font counters
- doc_italic = 0;
- doc_bold = 0;
- page_res_it.restart_page ();
- while (page_res_it.word () != NULL) {
- if (row != page_res_it.row ()) {
- if (row != NULL) {
- find_modal_font (&fonts, &row->font1, &row->font1_count);
- find_modal_font (&fonts, &row->font2, &row->font2_count);
- }
- row = page_res_it.row (); //current row
- fonts.clear (); //clear counters
- row->italic = 0;
- row->bold = 0;
+ page_res_it.restart_page();
+ while (page_res_it.word() != NULL) {
+ word = page_res_it.word();
+ set_word_fonts(word, word->best_choice->blob_choices());
+ if (!save_best_choices) { // set_blob_choices() does a deep clear
+ word->best_choice->set_blob_choices(NULL);
}
- word = page_res_it.word ();
- row->italic += word->italic;
- row->bold += word->bold;
- fonts.add (word->font1, word->font1_count);
- fonts.add (word->font2, word->font2_count);
- doc_italic += word->italic;
- doc_bold += word->bold;
- doc_fonts.add (word->font1, word->font1_count);
- doc_fonts.add (word->font2, word->font2_count);
- page_res_it.forward ();
- }
- if (row != NULL) {
- find_modal_font (&fonts, &row->font1, &row->font1_count);
- find_modal_font (&fonts, &row->font2, &row->font2_count);
+ doc_fonts.add(word->font1, word->font1_count);
+ doc_fonts.add(word->font2, word->font2_count);
+ page_res_it.forward();
}
find_modal_font(&doc_fonts, &doc_font, &doc_font_count);
- /*
- row=NULL;
- page_res_it.restart_page();
- while (page_res_it.word() != NULL)
- {
- if (row!=page_res_it.row())
- {
- row2=row;
- row=page_res_it.row();
- if (row->font1_countrow->x_height()-row2->row->x_height();
- if (hdiff<0)
- hdiff=-hdiff;
- if (hdiffrow->x_height()-row2->row->x_height();
- if (hdiff<0)
- hdiff=-hdiff;
- if (hdiffitalic=italic;
- row->bold=bold;
- find_modal_font(&fonts,&row->font1,&row->font1_count);
- find_modal_font(&fonts,&row->font2,&row->font2_count);
- }
- else
- page_res_it.forward();
- }
- else
- page_res_it.forward();
- }*/
+ if (doc_font_count == 0)
+ return;
+ FontInfo fi = fontinfo_table_.get(doc_font);
page_res_it.restart_page ();
while (page_res_it.word () != NULL) {
- row = page_res_it.row (); //current row
word = page_res_it.word ();
length = word->best_choice->length();
- count = word->italic;
- if (count < 0)
- count = -count;
- if (!(count == length || (length > 3 && count >= length * 3 / 4)))
- word->italic = doc_italic > 0 ? 1 : -1;
-
- count = word->bold;
- if (count < 0)
- count = -count;
- if (!(count == length || (length > 3 && count >= length * 3 / 4)))
- word->bold = doc_bold > 0 ? 1 : -1;
-
- count = word->font1_count;
+ // 1st choices got 2 pts, so we need to halve the score for the mode.
+ count = (word->font1_count + 1) / 2;
if (!(count == length || (length > 3 && count >= length * 3 / 4))) {
word->font1 = doc_font;
- word->font1_count = doc_font_count;
+ // Counts only get 1 as it came from the doc.
+ word->font1_count = 1;
+ word->italic = fi.is_italic() ? 1 : -1;
+ word->bold = fi.is_bold() ? 1 : -1;
}
-
- page_res_it.forward ();
+ page_res_it.forward();
}
}
-} // namespace tesseract
-
-
-/**
- * add_in_one_row
- *
- * Add into the stats for one row.
- */
-//dead code?
-void add_in_one_row( //good chars in word
- ROW_RES *row, //current row
- STATS *fonts, //font stats
- inT8 *italic, //output count
- inT8 *bold //output count
- ) {
- WERD_RES *word; //current word
- WERD_RES_IT word_it = &row->word_res_list;
-
- for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
- word = word_it.data ();
- *italic += word->italic;
- *bold += word->bold;
- if (word->font1_count > 0)
- fonts->add (word->font1, word->font1_count);
- if (word->font2_count > 0)
- fonts->add (word->font2, word->font2_count);
- }
-}
-
-
-/**
- * find_modal_font
- *
- * Find the modal font and remove from the stats.
- */
-//make static?
-void find_modal_font( //good chars in word
- STATS *fonts, //font stats
- inT8 *font_out, //output font
- inT8 *font_count //output count
- ) {
- inT8 font; //font index
- inT32 count; //pile couat
-
- if (fonts->get_total () > 0) {
- font = (inT8) fonts->mode ();
- *font_out = font;
- count = fonts->pile_count (font);
- *font_count = count < MAX_INT8 ? count : MAX_INT8;
- fonts->add (font, -*font_count);
- }
- else {
- *font_out = -1;
- *font_count = 0;
- }
-}
+} // namespace tesseract
diff --git a/ccmain/control.h b/ccmain/control.h
index 61e8e1d188..5a16154366 100644
--- a/ccmain/control.h
+++ b/ccmain/control.h
@@ -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
diff --git a/ccmain/cube_control.cpp b/ccmain/cube_control.cpp
new file mode 100644
index 0000000000..dcaa581a0b
--- /dev/null
+++ b/ccmain/cube_control.cpp
@@ -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
diff --git a/ccmain/cube_reco_context.cpp b/ccmain/cube_reco_context.cpp
new file mode 100644
index 0000000000..0f2ff63df4
--- /dev/null
+++ b/ccmain/cube_reco_context.cpp
@@ -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
+#include
+
+#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}
diff --git a/ccmain/cube_reco_context.h b/ccmain/cube_reco_context.h
new file mode 100644
index 0000000000..822ef62ce7
--- /dev/null
+++ b/ccmain/cube_reco_context.h
@@ -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
+#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
diff --git a/ccmain/docqual.cpp b/ccmain/docqual.cpp
index 892afdf0d1..a97157e5ad 100644
--- a/ccmain/docqual.cpp
+++ b/ccmain/docqual.cpp
@@ -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
-
-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");
-
-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?");
-
-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");
-
-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");
-
-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);
+namespace tesseract{
-/*************************************************************************
- * 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
- * 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 ())
- 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();
+// 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) {}
- // 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--;
- }
+ void CountMatchingBlobs(int index) {
+ ++match_count;
+ }
- /* See if current outword blob matches any initial blob with the same left
- coord. (Normally only one but possibly more - in unknown order) */
+ void CountAcceptedBlobs(int index) {
+ if (word->reject_map[index].accepted())
+ ++accepted_match_count;
+ ++match_count;
+ }
- 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());
+ void AcceptIfGoodQuality(int index) {
+ if (word->reject_map[index].accept_if_good_quality())
+ word->reject_map[index].setrej_quality_accept();
}
- delete init_word;
- return match_count;
-}
+ WERD_RES* word;
+ inT16 match_count;
+ inT16 accepted_match_count;
+};
/*************************************************************************
- * crude_match_blobs()
- * Check bounding boxes are the same and the number of outlines are the same.
+ * word_blob_quality()
+ * 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.
*************************************************************************/
-static BOOL8 crude_match_blobs(PBLOB *blob1, PBLOB *blob2) {
- TBOX box1 = blob1->bounding_box();
- TBOX box2 = blob2->bounding_box();
+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;
- if (box1.contains(box2) &&
- box2.contains(box1) &&
- (blob1->out_list()->length() == blob1->out_list()->length()))
- return TRUE;
- else
- return FALSE;
+ DocQualCallbacks cb(word);
+ word->bln_boxes->ProcessMatchedBlobs(
+ *word->rebuild_word,
+ NewPermanentTessCallback(&cb, &DocQualCallbacks::CountMatchingBlobs));
+ return cb.match_count;
}
-
-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 ())
- 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)
+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;
- 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
diff --git a/ccmain/docqual.h b/ccmain/docqual.h
index 402cd51946..61fa6f46ff 100644
--- a/ccmain/docqual.h
+++ b/ccmain/docqual.h
@@ -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
diff --git a/ccmain/expandblob.cpp b/ccmain/expandblob.cpp
deleted file mode 100644
index f80236a89d..0000000000
--- a/ccmain/expandblob.cpp
+++ /dev/null
@@ -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);
-}
diff --git a/ccmain/expandblob.h b/ccmain/expandblob.h
deleted file mode 100644
index 6d80c288db..0000000000
--- a/ccmain/expandblob.h
+++ /dev/null
@@ -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
diff --git a/ccmain/fixspace.cpp b/ccmain/fixspace.cpp
index b2b970cbc1..a7076164f9 100644
--- a/ccmain/fixspace.cpp
+++ b/ccmain/fixspace.cpp
@@ -1,8 +1,8 @@
/******************************************************************
* File: fixspace.cpp (Formerly fixspace.c)
* Description: Implements a pass over the page res, exploring the alternative
- * spacing possibilities, trying to use context to improve the
- word spacing
+ * spacing possibilities, trying to use context to improve the
+ * word spacing
* Author: Phil Cheatle
* Created: Thu Oct 21 11:38:43 BST 1993
*
@@ -32,28 +32,6 @@
#include "globals.h"
#include "tesseractclass.h"
-#define EXTERN
-
-EXTERN BOOL_VAR (fixsp_check_for_fp_noise_space, TRUE,
-"Try turning noise to space in fixed pitch");
-EXTERN BOOL_VAR (fixsp_fp_eval, TRUE, "Use alternate evaluation for fp");
-EXTERN BOOL_VAR (fixsp_noise_score_fixing, TRUE, "More sophisticated?");
-EXTERN INT_VAR (fixsp_non_noise_limit, 1,
-"How many non-noise blbs either side?");
-EXTERN double_VAR (fixsp_small_outlines_size, 0.28, "Small if lt xht x this");
-
-EXTERN BOOL_VAR (fixsp_ignore_punct, TRUE, "In uniform spacing calc");
-EXTERN BOOL_VAR (fixsp_numeric_fix, TRUE, "Try to deal with numeric punct");
-EXTERN BOOL_VAR (fixsp_prefer_joined_1s, TRUE, "Arbitrary boost");
-EXTERN BOOL_VAR (tessedit_test_uniform_wd_spacing, FALSE,
-"Limit context word spacing");
-EXTERN BOOL_VAR (tessedit_prefer_joined_punct, FALSE,
-"Reward punctation joins");
-EXTERN INT_VAR (fixsp_done_mode, 1, "What constitues done for spacing");
-EXTERN INT_VAR (debug_fix_space_level, 0, "Contextual fixspace debug");
-EXTERN STRING_VAR (numeric_punctuation, ".,",
-"Punct. chs expected WITHIN numbers");
-
#define PERFECT_WERDS 999
#define MAXSPACING 128 /*max expected spacing in pix */
@@ -68,67 +46,75 @@ namespace tesseract {
* @param word_count count of words in doc
* @param[out] page_res
*/
-void Tesseract::fix_fuzzy_spaces(volatile ETEXT_DESC *monitor,
+void Tesseract::fix_fuzzy_spaces(ETEXT_DESC *monitor,
inT32 word_count,
PAGE_RES *page_res) {
- BLOCK_RES_IT block_res_it; //iterators
+ BLOCK_RES_IT block_res_it;
ROW_RES_IT row_res_it;
WERD_RES_IT word_res_it_from;
WERD_RES_IT word_res_it_to;
WERD_RES *word_res;
WERD_RES_LIST fuzzy_space_words;
inT16 new_length;
- BOOL8 prevent_null_wd_fixsp; //DONT process blobless wds
- inT32 word_index; //current word
+ BOOL8 prevent_null_wd_fixsp; // DONT process blobless wds
+ inT32 word_index; // current word
- block_res_it.set_to_list (&page_res->block_res_list);
+ block_res_it.set_to_list(&page_res->block_res_list);
word_index = 0;
- for (block_res_it.mark_cycle_pt ();
- !block_res_it.cycled_list (); block_res_it.forward ()) {
- row_res_it.set_to_list (&block_res_it.data ()->row_res_list);
- for (row_res_it.mark_cycle_pt ();
- !row_res_it.cycled_list (); row_res_it.forward ()) {
- word_res_it_from.set_to_list (&row_res_it.data ()->word_res_list);
- while (!word_res_it_from.at_last ()) {
- word_res = word_res_it_from.data ();
- while (!word_res_it_from.at_last () &&
+ for (block_res_it.mark_cycle_pt(); !block_res_it.cycled_list();
+ block_res_it.forward()) {
+ row_res_it.set_to_list(&block_res_it.data()->row_res_list);
+ for (row_res_it.mark_cycle_pt(); !row_res_it.cycled_list();
+ row_res_it.forward()) {
+ word_res_it_from.set_to_list(&row_res_it.data()->word_res_list);
+ while (!word_res_it_from.at_last()) {
+ word_res = word_res_it_from.data();
+ while (!word_res_it_from.at_last() &&
!(word_res->combination ||
- word_res_it_from.data_relative (1)->word->flag (W_FUZZY_NON) ||
- word_res_it_from.data_relative (1)->word->flag (W_FUZZY_SP))) {
+ word_res_it_from.data_relative(1)->word->flag(W_FUZZY_NON) ||
+ word_res_it_from.data_relative(1)->word->flag(W_FUZZY_SP))) {
fix_sp_fp_word(word_res_it_from, row_res_it.data()->row,
block_res_it.data()->block);
- word_res = word_res_it_from.forward ();
+ word_res = word_res_it_from.forward();
word_index++;
if (monitor != NULL) {
monitor->ocr_alive = TRUE;
monitor->progress = 90 + 5 * word_index / word_count;
+ if (monitor->deadline_exceeded() ||
+ (monitor->cancel != NULL &&
+ (*monitor->cancel)(monitor->cancel_this, stats_.dict_words)))
+ return;
}
}
- if (!word_res_it_from.at_last ()) {
+ if (!word_res_it_from.at_last()) {
word_res_it_to = word_res_it_from;
prevent_null_wd_fixsp =
- word_res->word->gblob_list ()->empty ();
- if (check_debug_pt (word_res, 60))
- debug_fix_space_level.set_value (10);
- word_res_it_to.forward ();
+ word_res->word->gblob_list()->empty();
+ if (check_debug_pt(word_res, 60))
+ debug_fix_space_level.set_value(10);
+ word_res_it_to.forward();
word_index++;
if (monitor != NULL) {
monitor->ocr_alive = TRUE;
monitor->progress = 90 + 5 * word_index / word_count;
+ if (monitor->deadline_exceeded() ||
+ (monitor->cancel != NULL &&
+ (*monitor->cancel)(monitor->cancel_this, stats_.dict_words)))
+ return;
}
while (!word_res_it_to.at_last () &&
- (word_res_it_to.data_relative (1)->word->flag (W_FUZZY_NON) ||
- word_res_it_to.data_relative (1)->word->flag (W_FUZZY_SP))) {
- if (check_debug_pt (word_res, 60))
- debug_fix_space_level.set_value (10);
- if (word_res->word->gblob_list ()->empty ())
+ (word_res_it_to.data_relative(1)->word->flag(W_FUZZY_NON) ||
+ word_res_it_to.data_relative(1)->word->flag(W_FUZZY_SP))) {
+ if (check_debug_pt(word_res, 60))
+ debug_fix_space_level.set_value(10);
+ if (word_res->word->gblob_list()->empty())
prevent_null_wd_fixsp = TRUE;
- word_res = word_res_it_to.forward ();
+ word_res = word_res_it_to.forward();
}
- if (check_debug_pt (word_res, 60))
- debug_fix_space_level.set_value (10);
- if (word_res->word->gblob_list ()->empty ())
+ if (check_debug_pt(word_res, 60))
+ debug_fix_space_level.set_value(10);
+ if (word_res->word->gblob_list()->empty())
prevent_null_wd_fixsp = TRUE;
if (prevent_null_wd_fixsp) {
word_res_it_from = word_res_it_to;
@@ -138,18 +124,20 @@ void Tesseract::fix_fuzzy_spaces(volatile ETEXT_DESC *monitor,
fix_fuzzy_space_list(fuzzy_space_words,
row_res_it.data()->row,
block_res_it.data()->block);
- new_length = fuzzy_space_words.length ();
- word_res_it_from.add_list_before (&fuzzy_space_words);
- for (; (!word_res_it_from.at_last () && (new_length > 0)); new_length--) {
- word_res_it_from.forward ();
+ new_length = fuzzy_space_words.length();
+ word_res_it_from.add_list_before(&fuzzy_space_words);
+ for (;
+ !word_res_it_from.at_last() && new_length > 0;
+ new_length--) {
+ word_res_it_from.forward();
}
}
if (test_pt)
- debug_fix_space_level.set_value (0);
+ debug_fix_space_level.set_value(0);
}
- fix_sp_fp_word(word_res_it_from, row_res_it.data ()->row,
+ fix_sp_fp_word(word_res_it_from, row_res_it.data()->row,
block_res_it.data()->block);
- //Last word in row
+ // Last word in row
}
}
}
@@ -164,15 +152,15 @@ void Tesseract::fix_fuzzy_space_list(WERD_RES_LIST &best_perm,
BOOL8 improved = FALSE;
best_score = eval_word_spacing(best_perm); // default score
- dump_words (best_perm, best_score, 1, improved);
+ dump_words(best_perm, best_score, 1, improved);
if (best_score != PERFECT_WERDS)
initialise_search(best_perm, current_perm);
- while ((best_score != PERFECT_WERDS) && !current_perm.empty ()) {
+ while ((best_score != PERFECT_WERDS) && !current_perm.empty()) {
match_current_words(current_perm, row, block);
- current_score = eval_word_spacing (current_perm);
- dump_words (current_perm, current_score, 2, improved);
+ current_score = eval_word_spacing(current_perm);
+ dump_words(current_perm, current_score, 2, improved);
if (current_score > best_score) {
best_perm.clear();
best_perm.deep_copy(¤t_perm, &WERD_RES::deep_copy);
@@ -182,7 +170,7 @@ void Tesseract::fix_fuzzy_space_list(WERD_RES_LIST &best_perm,
if (current_score < PERFECT_WERDS)
transform_to_next_perm(current_perm);
}
- dump_words (best_perm, best_score, 3, improved);
+ dump_words(best_perm, best_score, 3, improved);
}
} // namespace tesseract
@@ -193,13 +181,13 @@ void initialise_search(WERD_RES_LIST &src_list, WERD_RES_LIST &new_list) {
WERD_RES *src_wd;
WERD_RES *new_wd;
- for (src_it.mark_cycle_pt (); !src_it.cycled_list (); src_it.forward ()) {
- src_wd = src_it.data ();
+ for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
+ src_wd = src_it.data();
if (!src_wd->combination) {
- new_wd = new WERD_RES (*src_wd);
+ new_wd = new WERD_RES(*src_wd);
new_wd->combination = FALSE;
new_wd->part_of_combo = FALSE;
- new_it.add_after_then_move (new_wd);
+ new_it.add_after_then_move(new_wd);
}
}
}
@@ -210,11 +198,15 @@ void Tesseract::match_current_words(WERD_RES_LIST &words, ROW *row,
BLOCK* block) {
WERD_RES_IT word_it(&words);
WERD_RES *word;
-
- for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
- word = word_it.data ();
- if ((!word->part_of_combo) && (word->outword == NULL))
+ // Since we are not using PAGE_RES to iterate over words, we need to update
+ // prev_word_best_choice_ before calling classify_word_pass2().
+ prev_word_best_choice_ = NULL;
+ for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
+ word = word_it.data();
+ if ((!word->part_of_combo) && (word->box_word == NULL)) {
classify_word_pass2(word, block, row);
+ }
+ prev_word_best_choice_ = word->best_choice;
}
}
@@ -252,11 +244,11 @@ inT16 Tesseract::eval_word_spacing(WERD_RES_LIST &word_res_list) {
inT16 word_len;
inT16 i;
inT16 offset;
- WERD_RES *word; //current word
+ WERD_RES *word; // current word
inT16 prev_word_score = 0;
BOOL8 prev_word_done = FALSE;
- BOOL8 prev_char_1 = FALSE; //prev ch a "1/I/l"?
- BOOL8 prev_char_digit = FALSE; //prev ch 2..9 or 0
+ BOOL8 prev_char_1 = FALSE; // prev ch a "1/I/l"?
+ BOOL8 prev_char_digit = FALSE; // prev ch 2..9 or 0
BOOL8 current_char_1 = FALSE;
BOOL8 current_word_ok_so_far;
STRING punct_chars = "!\"`',.:;";
@@ -265,8 +257,8 @@ inT16 Tesseract::eval_word_spacing(WERD_RES_LIST &word_res_list) {
BOOL8 word_done = FALSE;
do {
- word = word_res_it.data ();
- word_done = fixspace_thinks_word_done (word);
+ word = word_res_it.data();
+ word_done = fixspace_thinks_word_done(word);
word_count++;
if (word->tess_failed) {
total_score += prev_word_score;
@@ -276,51 +268,42 @@ inT16 Tesseract::eval_word_spacing(WERD_RES_LIST &word_res_list) {
prev_char_1 = FALSE;
prev_char_digit = FALSE;
prev_word_done = FALSE;
- }
- else {
+ } else {
/*
Can we add the prev word score and potentially count this word?
Yes IF it didnt end in a 1 when the first char of this word is a digit
AND it didnt end in a digit when the first char of this word is a 1
*/
- word_len = word->reject_map.length ();
+ word_len = word->reject_map.length();
current_word_ok_so_far = FALSE;
- if (!((prev_char_1 &&
- digit_or_numeric_punct (word, 0)) ||
- (prev_char_digit &&
- ((word_done &&
- (word->best_choice->unichar_lengths().string()[0] == 1 &&
- word->best_choice->unichar_string()[0] == '1')) ||
- (!word_done &&
- STRING(conflict_set_I_l_1).contains(word->best_choice->unichar_string ()[0])))))) {
+ if (!((prev_char_1 && digit_or_numeric_punct(word, 0)) ||
+ (prev_char_digit && (
+ (word_done &&
+ word->best_choice->unichar_lengths().string()[0] == 1 &&
+ word->best_choice->unichar_string()[0] == '1') ||
+ (!word_done && STRING(conflict_set_I_l_1).contains(
+ word->best_choice->unichar_string()[0])))))) {
total_score += prev_word_score;
if (prev_word_done)
done_word_count++;
current_word_ok_so_far = word_done;
}
- if ((current_word_ok_so_far) &&
- (!tessedit_test_uniform_wd_spacing ||
- ((word->best_choice->permuter () == NUMBER_PERM) ||
- uniformly_spaced (word)))) {
+ if (current_word_ok_so_far) {
prev_word_done = TRUE;
prev_word_score = word_len;
- }
- else {
+ } else {
prev_word_done = FALSE;
prev_word_score = 0;
}
- if (fixsp_prefer_joined_1s) {
- /* Add 1 to total score for every joined 1 regardless of context and
- rejtn */
-
- for (i = 0, prev_char_1 = FALSE; i < word_len; i++) {
- current_char_1 = word->best_choice->unichar_string()[i] == '1';
- if (prev_char_1 || (current_char_1 && (i > 0)))
- total_score++;
- prev_char_1 = current_char_1;
- }
+ /* Add 1 to total score for every joined 1 regardless of context and
+ rejtn */
+ for (i = 0, prev_char_1 = FALSE; i < word_len; i++) {
+ current_char_1 = word->best_choice->unichar_string()[i] == '1';
+ if (prev_char_1 || (current_char_1 && (i > 0)))
+ total_score++;
+ prev_char_1 = current_char_1;
}
/* Add 1 to total score for every joined punctuation regardless of context
@@ -329,28 +312,25 @@ inT16 Tesseract::eval_word_spacing(WERD_RES_LIST &word_res_list) {
for (i = 0, offset = 0, prev_char_punct = FALSE; i < word_len;
offset += word->best_choice->unichar_lengths()[i++]) {
current_char_punct =
- punct_chars.contains (word->best_choice->unichar_string()[offset]);
- if (prev_char_punct || (current_char_punct && (i > 0)))
+ punct_chars.contains(word->best_choice->unichar_string()[offset]);
+ if (prev_char_punct || (current_char_punct && i > 0))
total_score++;
prev_char_punct = current_char_punct;
}
}
- prev_char_digit = digit_or_numeric_punct (word, word_len - 1);
+ prev_char_digit = digit_or_numeric_punct(word, word_len - 1);
for (i = 0, offset = 0; i < word_len - 1;
offset += word->best_choice->unichar_lengths()[i++]);
prev_char_1 =
- ((word_done
- && (word->best_choice->unichar_string()[offset] == '1'))
- || (!word_done
- && STRING(conflict_set_I_l_1).contains(
- word->best_choice->unichar_string()[offset])));
+ ((word_done && (word->best_choice->unichar_string()[offset] == '1'))
+ || (!word_done && STRING(conflict_set_I_l_1).contains(
+ word->best_choice->unichar_string()[offset])));
}
/* Find next word */
- do
- word_res_it.forward ();
- while (word_res_it.data ()->part_of_combo);
- }
- while (!word_res_it.at_first ());
+ do {
+ word_res_it.forward();
+ } while (word_res_it.data()->part_of_combo);
+ } while (!word_res_it.at_first());
total_score += prev_word_score;
if (prev_word_done)
done_word_count++;
@@ -360,20 +340,21 @@ inT16 Tesseract::eval_word_spacing(WERD_RES_LIST &word_res_list) {
return total_score;
}
-
BOOL8 Tesseract::digit_or_numeric_punct(WERD_RES *word, int char_position) {
int i;
int offset;
for (i = 0, offset = 0; i < char_position;
offset += word->best_choice->unichar_lengths()[i++]);
- return (unicharset.get_isdigit(word->best_choice->unichar_string().string() + offset,
- word->best_choice->unichar_lengths()[i]) ||
- (fixsp_numeric_fix &&
- (word->best_choice->permuter () == NUMBER_PERM) &&
- STRING (numeric_punctuation).contains
- (word->best_choice->unichar_string().string()[offset])));
+ return (
+ unicharset.get_isdigit(
+ word->best_choice->unichar_string().string() + offset,
+ word->best_choice->unichar_lengths()[i]) ||
+ (word->best_choice->permuter() == NUMBER_PERM &&
+ STRING(numeric_punctuation).contains(
+ word->best_choice->unichar_string().string()[offset])));
}
+
} // namespace tesseract
@@ -395,95 +376,89 @@ void transform_to_next_perm(WERD_RES_LIST &words) {
WERD_RES *prev_word;
WERD_RES *combo;
WERD *copy_word;
- inT16 prev_right = -1;
+ inT16 prev_right = -MAX_INT16;
TBOX box;
inT16 gap;
inT16 min_gap = MAX_INT16;
- for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
- word = word_it.data ();
+ for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
+ word = word_it.data();
if (!word->part_of_combo) {
- box = word->word->bounding_box ();
- if (prev_right >= 0) {
- gap = box.left () - prev_right;
+ box = word->word->bounding_box();
+ if (prev_right > -MAX_INT16) {
+ gap = box.left() - prev_right;
if (gap < min_gap)
min_gap = gap;
}
- prev_right = box.right ();
+ prev_right = box.right();
}
}
if (min_gap < MAX_INT16) {
- prev_right = -1; //back to start
- word_it.set_to_list (&words);
- //cant use cycle pt due to inserted combos at start of list
- for (; (prev_right < 0) || !word_it.at_first (); word_it.forward ()) {
- word = word_it.data ();
+ prev_right = -MAX_INT16; // back to start
+ word_it.set_to_list(&words);
+ // Note: we can't use cycle_pt due to inserted combos at start of list.
+ for (; (prev_right == -MAX_INT16) || !word_it.at_first();
+ word_it.forward()) {
+ word = word_it.data();
if (!word->part_of_combo) {
- box = word->word->bounding_box ();
- if (prev_right >= 0) {
- gap = box.left () - prev_right;
+ box = word->word->bounding_box();
+ if (prev_right > -MAX_INT16) {
+ gap = box.left() - prev_right;
if (gap <= min_gap) {
- prev_word = prev_word_it.data ();
- if (prev_word->combination)
+ prev_word = prev_word_it.data();
+ if (prev_word->combination) {
combo = prev_word;
- else {
- /* Make a new combination and insert before the first word being joined */
+ } else {
+ /* Make a new combination and insert before
+ * the first word being joined. */
copy_word = new WERD;
*copy_word = *(prev_word->word);
- //deep copy
- combo = new WERD_RES (copy_word);
+ // deep copy
+ combo = new WERD_RES(copy_word);
combo->combination = TRUE;
combo->x_height = prev_word->x_height;
prev_word->part_of_combo = TRUE;
- prev_word_it.add_before_then_move (combo);
+ prev_word_it.add_before_then_move(combo);
}
- combo->word->set_flag (W_EOL, word->word->flag (W_EOL));
+ combo->word->set_flag(W_EOL, word->word->flag(W_EOL));
if (word->combination) {
- combo->word->join_on (word->word);
- //Move blbs to combo
- //old combo no longer needed
- delete word_it.extract ();
- }
- else {
- //Cpy current wd to combo
- combo->copy_on (word);
+ combo->word->join_on(word->word);
+ // Move blobs to combo
+ // old combo no longer needed
+ delete word_it.extract();
+ } else {
+ // Copy current wd to combo
+ combo->copy_on(word);
word->part_of_combo = TRUE;
}
combo->done = FALSE;
- if (combo->outword != NULL) {
- delete combo->outword;
- delete combo->best_choice;
- delete combo->raw_choice;
- combo->outword = NULL;
- combo->best_choice = NULL;
- combo->raw_choice = NULL;
- }
+ combo->ClearResults();
+ } else {
+ prev_word_it = word_it; // catch up
}
- else
- //catch up
- prev_word_it = word_it;
}
- prev_right = box.right ();
+ prev_right = box.right();
}
}
+ } else {
+ words.clear(); // signal termination
}
- else
- words.clear (); //signal termination
}
-
-void dump_words(WERD_RES_LIST &perm, inT16 score, inT16 mode, BOOL8 improved) {
+namespace tesseract {
+void Tesseract::dump_words(WERD_RES_LIST &perm, inT16 score,
+ inT16 mode, BOOL8 improved) {
WERD_RES_IT word_res_it(&perm);
- static STRING initial_str;
if (debug_fix_space_level > 0) {
if (mode == 1) {
- initial_str = "";
- for (word_res_it.mark_cycle_pt ();
- !word_res_it.cycled_list (); word_res_it.forward ()) {
- if (!word_res_it.data ()->part_of_combo) {
- initial_str += word_res_it.data()->best_choice->unichar_string();
- initial_str += ' ';
+ stats_.dump_words_str = "";
+ for (word_res_it.mark_cycle_pt(); !word_res_it.cycled_list();
+ word_res_it.forward()) {
+ if (!word_res_it.data()->part_of_combo) {
+ stats_.dump_words_str +=
+ word_res_it.data()->best_choice->unichar_string();
+ stats_.dump_words_str += ' ';
}
}
}
@@ -492,35 +467,36 @@ void dump_words(WERD_RES_LIST &perm, inT16 score, inT16 mode, BOOL8 improved) {
if (debug_fix_space_level > 1) {
switch (mode) {
case 1:
- tprintf ("EXTRACTED (%d): \"", score);
+ tprintf("EXTRACTED (%d): \"", score);
break;
case 2:
- tprintf ("TESTED (%d): \"", score);
+ tprintf("TESTED (%d): \"", score);
break;
case 3:
- tprintf ("RETURNED (%d): \"", score);
+ tprintf("RETURNED (%d): \"", score);
break;
}
- for (word_res_it.mark_cycle_pt ();
- !word_res_it.cycled_list (); word_res_it.forward ()) {
- if (!word_res_it.data ()->part_of_combo)
+ for (word_res_it.mark_cycle_pt(); !word_res_it.cycled_list();
+ word_res_it.forward()) {
+ if (!word_res_it.data()->part_of_combo) {
tprintf("%s/%1d ",
word_res_it.data()->best_choice->unichar_string().string(),
(int)word_res_it.data()->best_choice->permuter());
+ }
}
- tprintf ("\"\n");
- }
- else if (improved) {
- tprintf ("FIX SPACING \"%s\" => \"", initial_str.string ());
- for (word_res_it.mark_cycle_pt ();
- !word_res_it.cycled_list (); word_res_it.forward ()) {
- if (!word_res_it.data ()->part_of_combo)
- tprintf ("%s/%1d ",
- word_res_it.data()->best_choice->unichar_string().string(),
- (int)word_res_it.data()->best_choice->permuter());
+ tprintf("\"\n");
+ } else if (improved) {
+ tprintf("FIX SPACING \"%s\" => \"", stats_.dump_words_str.string());
+ for (word_res_it.mark_cycle_pt(); !word_res_it.cycled_list();
+ word_res_it.forward()) {
+ if (!word_res_it.data()->part_of_combo) {
+ tprintf("%s/%1d ",
+ word_res_it.data()->best_choice->unichar_string().string(),
+ (int)word_res_it.data()->best_choice->permuter());
+ }
}
- tprintf ("\"\n");
+ tprintf("\"\n");
}
#endif
}
@@ -532,81 +508,81 @@ void dump_words(WERD_RES_LIST &perm, inT16 score, inT16 mode, BOOL8 improved) {
* Return true if one of the following are true:
* - All inter-char gaps are the same width
* - The largest gap is no larger than twice the mean/median of the others
- * - The largest gap is < 64/5 = 13 and all others are <= 0
+ * - The largest gap is < normalised_max_nonspace
* **** REMEMBER - WE'RE NOW WORKING WITH A BLN WERD !!!
*/
-BOOL8 uniformly_spaced(WERD_RES *word) {
- PBLOB_IT blob_it;
+BOOL8 Tesseract::uniformly_spaced(WERD_RES *word) {
TBOX box;
inT16 prev_right = -MAX_INT16;
inT16 gap;
inT16 max_gap = -MAX_INT16;
inT16 max_gap_count = 0;
- STATS gap_stats (0, MAXSPACING);
+ STATS gap_stats(0, MAXSPACING);
BOOL8 result;
- const ROW *row = word->denorm.row ();
+ const ROW *row = word->denorm.row();
float max_non_space;
float normalised_max_nonspace;
inT16 i = 0;
inT16 offset = 0;
STRING punct_chars = "\"`',.:;";
- blob_it.set_to_list (word->outword->blob_list ());
-
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
- box = blob_it.data ()->bounding_box ();
+ for (TBLOB* blob = word->rebuild_word->blobs; blob != NULL;
+ blob = blob->next) {
+ box = blob->bounding_box();
if ((prev_right > -MAX_INT16) &&
- (!fixsp_ignore_punct ||
- (!punct_chars.contains (word->best_choice->unichar_string()
- [offset - word->best_choice->unichar_lengths()[i - 1]]) &&
- !punct_chars.contains (word->best_choice->unichar_string()[offset])))) {
- gap = box.left () - prev_right;
- if (gap < max_gap)
- gap_stats.add (gap, 1);
- else if (gap == max_gap)
+ (!punct_chars.contains(
+ word->best_choice->unichar_string()
+ [offset - word->best_choice->unichar_lengths()[i - 1]]) &&
+ !punct_chars.contains(
+ word->best_choice->unichar_string()[offset]))) {
+ gap = box.left() - prev_right;
+ if (gap < max_gap) {
+ gap_stats.add(gap, 1);
+ } else if (gap == max_gap) {
max_gap_count++;
- else {
+ } else {
if (max_gap_count > 0)
- gap_stats.add (max_gap, max_gap_count);
+ gap_stats.add(max_gap, max_gap_count);
max_gap = gap;
max_gap_count = 1;
}
}
- prev_right = box.right ();
+ prev_right = box.right();
offset += word->best_choice->unichar_lengths()[i++];
}
- max_non_space = (row->space () + 3 * row->kern ()) / 4;
- normalised_max_nonspace = max_non_space * bln_x_height / row->x_height ();
+ max_non_space = (row->space() + 3 * row->kern()) / 4;
+ normalised_max_nonspace = max_non_space * kBlnXHeight / row->x_height();
- result = ((gap_stats.get_total () == 0) ||
- (max_gap <= normalised_max_nonspace) ||
- ((gap_stats.get_total () > 2) &&
- (max_gap <= 2 * gap_stats.median ())) ||
- ((gap_stats.get_total () <= 2) &&
- (max_gap <= 2 * gap_stats.mean ())));
+ result = (
+ gap_stats.get_total() == 0 ||
+ max_gap <= normalised_max_nonspace ||
+ (gap_stats.get_total() > 2 && max_gap <= 2 * gap_stats.median()) ||
+ (gap_stats.get_total() <= 2 && max_gap <= 2 * gap_stats.mean()));
#ifndef SECURE_NAMES
if ((debug_fix_space_level > 1)) {
- if (result)
- tprintf
- ("ACCEPT SPACING FOR: \"%s\" norm_maxnon = %f max=%d maxcount=%d total=%d mean=%f median=%f\n",
- word->best_choice->unichar_string().string (), normalised_max_nonspace,
- max_gap, max_gap_count, gap_stats.get_total (), gap_stats.mean (),
- gap_stats.median ());
- else
- tprintf
- ("REJECT SPACING FOR: \"%s\" norm_maxnon = %f max=%d maxcount=%d total=%d mean=%f median=%f\n",
- word->best_choice->unichar_string().string (), normalised_max_nonspace,
- max_gap, max_gap_count, gap_stats.get_total (), gap_stats.mean (),
- gap_stats.median ());
+ if (result) {
+ tprintf(
+ "ACCEPT SPACING FOR: \"%s\" norm_maxnon = %f max=%d maxcount=%d "
+ "total=%d mean=%f median=%f\n",
+ word->best_choice->unichar_string().string(), normalised_max_nonspace,
+ max_gap, max_gap_count, gap_stats.get_total(), gap_stats.mean(),
+ gap_stats.median());
+ } else {
+ tprintf(
+ "REJECT SPACING FOR: \"%s\" norm_maxnon = %f max=%d maxcount=%d "
+ "total=%d mean=%f median=%f\n",
+ word->best_choice->unichar_string().string(), normalised_max_nonspace,
+ max_gap, max_gap_count, gap_stats.get_total(), gap_stats.mean(),
+ gap_stats.median());
+ }
}
#endif
return result;
}
-
-BOOL8 fixspace_thinks_word_done(WERD_RES *word) {
+BOOL8 Tesseract::fixspace_thinks_word_done(WERD_RES *word) {
if (word->done)
return TRUE;
@@ -615,23 +591,22 @@ BOOL8 fixspace_thinks_word_done(WERD_RES *word) {
reject.c BUT DONT REJECT IF THE WERD IS AMBIGUOUS - FOR SPACING WE DONT
CARE WHETHER WE HAVE of/at on/an etc.
*/
- if ((fixsp_done_mode > 0) &&
- (word->tess_accepted ||
- ((fixsp_done_mode == 2) &&
- (word->reject_map.reject_count () == 0)) ||
- (fixsp_done_mode == 3)) &&
- (strchr (word->best_choice->unichar_string().string (), ' ') == NULL) &&
- ((word->best_choice->permuter () == SYSTEM_DAWG_PERM) ||
- (word->best_choice->permuter () == FREQ_DAWG_PERM) ||
- (word->best_choice->permuter () == USER_DAWG_PERM) ||
- (word->best_choice->permuter () == NUMBER_PERM)))
+ if (fixsp_done_mode > 0 &&
+ (word->tess_accepted ||
+ (fixsp_done_mode == 2 && word->reject_map.reject_count() == 0) ||
+ fixsp_done_mode == 3) &&
+ (strchr(word->best_choice->unichar_string().string(), ' ') == NULL) &&
+ ((word->best_choice->permuter() == SYSTEM_DAWG_PERM) ||
+ (word->best_choice->permuter() == FREQ_DAWG_PERM) ||
+ (word->best_choice->permuter() == USER_DAWG_PERM) ||
+ (word->best_choice->permuter() == NUMBER_PERM))) {
return TRUE;
- else
+ } else {
return FALSE;
+ }
}
-namespace tesseract {
/**
* @name fix_sp_fp_word()
* Test the current word to see if it can be split by deleting noise blobs. If
@@ -648,30 +623,30 @@ void Tesseract::fix_sp_fp_word(WERD_RES_IT &word_res_it, ROW *row,
inT16 new_length;
float junk;
- word_res = word_res_it.data ();
- if (!fixsp_check_for_fp_noise_space ||
- word_res->word->flag (W_REP_CHAR) ||
- word_res->combination ||
- word_res->part_of_combo || !word_res->word->flag (W_DONT_CHOP))
+ word_res = word_res_it.data();
+ if (word_res->word->flag(W_REP_CHAR) ||
+ word_res->combination ||
+ word_res->part_of_combo ||
+ !word_res->word->flag(W_DONT_CHOP))
return;
- blob_index = worst_noise_blob (word_res, &junk);
+ blob_index = worst_noise_blob(word_res, &junk);
if (blob_index < 0)
return;
#ifndef SECURE_NAMES
if (debug_fix_space_level > 1) {
- tprintf ("FP fixspace working on \"%s\"\n",
- word_res->best_choice->unichar_string().string());
+ tprintf("FP fixspace working on \"%s\"\n",
+ word_res->best_choice->unichar_string().string());
}
#endif
- gblob_sort_list ((PBLOB_LIST *) word_res->word->rej_cblob_list (), FALSE);
- sub_word_list_it.add_after_stay_put (word_res_it.extract ());
+ gblob_sort_list((PBLOB_LIST *)word_res->word->rej_cblob_list(), FALSE);
+ sub_word_list_it.add_after_stay_put(word_res_it.extract());
fix_noisy_space_list(sub_word_list, row, block);
- new_length = sub_word_list.length ();
- word_res_it.add_list_before (&sub_word_list);
- for (; (!word_res_it.at_last () && (new_length > 1)); new_length--) {
- word_res_it.forward ();
+ new_length = sub_word_list.length();
+ word_res_it.add_list_before(&sub_word_list);
+ for (; !word_res_it.at_last() && new_length > 1; new_length--) {
+ word_res_it.forward();
}
}
@@ -686,40 +661,36 @@ void Tesseract::fix_noisy_space_list(WERD_RES_LIST &best_perm, ROW *row,
inT16 current_score;
BOOL8 improved = FALSE;
- //default score
- best_score = fp_eval_word_spacing (best_perm);
+ best_score = fp_eval_word_spacing(best_perm); // default score
- dump_words (best_perm, best_score, 1, improved);
+ dump_words(best_perm, best_score, 1, improved);
new_word_res = new WERD_RES;
- old_word_res = best_perm_it.data ();
- //Kludge to force deep copy
- old_word_res->combination = TRUE;
- *new_word_res = *old_word_res; //deep copy
- //Undo kludge
- old_word_res->combination = FALSE;
- //Undo kludge
- new_word_res->combination = FALSE;
- current_perm_it.add_to_end (new_word_res);
+ old_word_res = best_perm_it.data();
+ old_word_res->combination = TRUE; // Kludge to force deep copy
+ *new_word_res = *old_word_res; // deep copy
+ old_word_res->combination = FALSE; // Undo kludge
+ new_word_res->combination = FALSE; // Undo kludge
+ current_perm_it.add_to_end(new_word_res);
break_noisiest_blob_word(current_perm);
- while ((best_score != PERFECT_WERDS) && !current_perm.empty ()) {
+ while (best_score != PERFECT_WERDS && !current_perm.empty()) {
match_current_words(current_perm, row, block);
- current_score = fp_eval_word_spacing (current_perm);
- dump_words (current_perm, current_score, 2, improved);
+ current_score = fp_eval_word_spacing(current_perm);
+ dump_words(current_perm, current_score, 2, improved);
if (current_score > best_score) {
best_perm.clear();
best_perm.deep_copy(¤t_perm, &WERD_RES::deep_copy);
best_score = current_score;
improved = TRUE;
}
- if (current_score < PERFECT_WERDS)
+ if (current_score < PERFECT_WERDS) {
break_noisiest_blob_word(current_perm);
+ }
}
- dump_words (best_perm, best_score, 3, improved);
+ dump_words(best_perm, best_score, 3, improved);
}
-} // namespace tesseract
/**
@@ -727,13 +698,13 @@ void Tesseract::fix_noisy_space_list(WERD_RES_LIST &best_perm, ROW *row,
* Find the word with the blob which looks like the worst noise.
* Break the word into two, deleting the noise blob.
*/
-void break_noisiest_blob_word(WERD_RES_LIST &words) {
+void Tesseract::break_noisiest_blob_word(WERD_RES_LIST &words) {
WERD_RES_IT word_it(&words);
WERD_RES_IT worst_word_it;
float worst_noise_score = 9999;
- int worst_blob_index = -1; //noisiest blb of noisiest wd
- int blob_index; //of wds noisiest blb
- float noise_score; //of wds noisiest blb
+ int worst_blob_index = -1; // Noisiest blob of noisiest wd
+ int blob_index; // of wds noisiest blob
+ float noise_score; // of wds noisiest blob
WERD_RES *word_res;
C_BLOB_IT blob_it;
C_BLOB_IT rej_cblob_it;
@@ -744,119 +715,113 @@ void break_noisiest_blob_word(WERD_RES_LIST &words) {
inT16 start_of_noise_blob;
inT16 i;
- for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
- blob_index = worst_noise_blob (word_it.data (), &noise_score);
- if ((blob_index > -1) && (worst_noise_score > noise_score)) {
+ for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
+ blob_index = worst_noise_blob(word_it.data(), &noise_score);
+ if (blob_index > -1 && worst_noise_score > noise_score) {
worst_noise_score = noise_score;
worst_blob_index = blob_index;
worst_word_it = word_it;
}
}
if (worst_blob_index < 0) {
- words.clear (); //signal termination
+ words.clear(); // signal termination
return;
}
/* Now split the worst_word_it */
- word_res = worst_word_it.data ();
+ word_res = worst_word_it.data();
/* Move blobs before noise blob to a new bloblist */
- new_blob_it.set_to_list (&new_blob_list);
- blob_it.set_to_list (word_res->word->cblob_list ());
- for (i = 0; i < worst_blob_index; i++, blob_it.forward ()) {
- new_blob_it.add_after_then_move (blob_it.extract ());
+ new_blob_it.set_to_list(&new_blob_list);
+ blob_it.set_to_list(word_res->word->cblob_list());
+ for (i = 0; i < worst_blob_index; i++, blob_it.forward()) {
+ new_blob_it.add_after_then_move(blob_it.extract());
}
- start_of_noise_blob = blob_it.data ()->bounding_box ().left ();
- delete blob_it.extract (); //throw out noise blb
+ start_of_noise_blob = blob_it.data()->bounding_box().left();
+ delete blob_it.extract(); // throw out noise blob
- new_word = new WERD (&new_blob_list, word_res->word);
- new_word->set_flag (W_EOL, FALSE);
- word_res->word->set_flag (W_BOL, FALSE);
- word_res->word->set_blanks (1);//After break
+ new_word = new WERD(&new_blob_list, word_res->word);
+ new_word->set_flag(W_EOL, FALSE);
+ word_res->word->set_flag(W_BOL, FALSE);
+ word_res->word->set_blanks(1); // After break
- new_rej_cblob_it.set_to_list (new_word->rej_cblob_list ());
- rej_cblob_it.set_to_list (word_res->word->rej_cblob_list ());
+ new_rej_cblob_it.set_to_list(new_word->rej_cblob_list());
+ rej_cblob_it.set_to_list(word_res->word->rej_cblob_list());
for (;
- (!rej_cblob_it.empty () &&
- (rej_cblob_it.data ()->bounding_box ().left () <
- start_of_noise_blob)); rej_cblob_it.forward ()) {
- new_rej_cblob_it.add_after_then_move (rej_cblob_it.extract ());
+ (!rej_cblob_it.empty() &&
+ (rej_cblob_it.data()->bounding_box().left() < start_of_noise_blob));
+ rej_cblob_it.forward()) {
+ new_rej_cblob_it.add_after_then_move(rej_cblob_it.extract());
}
- worst_word_it.add_before_then_move (new WERD_RES (new_word));
+ worst_word_it.add_before_then_move(new WERD_RES(new_word));
- word_res->done = FALSE;
- if (word_res->outword != NULL) {
- delete word_res->outword;
- delete word_res->best_choice;
- delete word_res->raw_choice;
- word_res->outword = NULL;
- word_res->best_choice = NULL;
- word_res->raw_choice = NULL;
- }
+ word_res->ClearResults();
}
-
-inT16 worst_noise_blob(WERD_RES *word_res, float *worst_noise_score) {
- PBLOB_IT blob_it;
- inT16 blob_count;
+inT16 Tesseract::worst_noise_blob(WERD_RES *word_res,
+ float *worst_noise_score) {
float noise_score[512];
int i;
- int min_noise_blob; //1st contender
- int max_noise_blob; //last contender
+ int min_noise_blob; // 1st contender
+ int max_noise_blob; // last contender
int non_noise_count;
- int worst_noise_blob; //Worst blob
- float small_limit = bln_x_height * fixsp_small_outlines_size;
- float non_noise_limit = bln_x_height * 0.8;
-
- blob_it.set_to_list (word_res->outword->blob_list ());
- //normalised
- blob_count = blob_it.length ();
- ASSERT_HOST (blob_count <= 512);
+ int worst_noise_blob; // Worst blob
+ float small_limit = kBlnXHeight * fixsp_small_outlines_size;
+ float non_noise_limit = kBlnXHeight * 0.8;
+
+ TBLOB* blob = word_res->rebuild_word->blobs;
+ // Normalised.
+ int blob_count = word_res->box_word->length();
+ ASSERT_HOST(blob_count <= 512);
if (blob_count < 5)
- return -1; //too short to split
+ return -1; // too short to split
+
/* Get the noise scores for all blobs */
#ifndef SECURE_NAMES
if (debug_fix_space_level > 5)
- tprintf ("FP fixspace Noise metrics for \"%s\": ",
- word_res->best_choice->unichar_string().string());
+ tprintf("FP fixspace Noise metrics for \"%s\": ",
+ word_res->best_choice->unichar_string().string());
#endif
- for (i = 0; i < blob_count; i++, blob_it.forward ()) {
- if (word_res->reject_map[i].accepted ())
+ for (i = 0; i < blob_count && blob != NULL; i++, blob = blob->next) {
+ if (word_res->reject_map[i].accepted())
noise_score[i] = non_noise_limit;
else
- noise_score[i] = blob_noise_score (blob_it.data ());
+ noise_score[i] = blob_noise_score(blob);
if (debug_fix_space_level > 5)
- tprintf ("%1.1f ", noise_score[i]);
+ tprintf("%1.1f ", noise_score[i]);
}
if (debug_fix_space_level > 5)
- tprintf ("\n");
+ tprintf("\n");
/* Now find the worst one which is far enough away from the end of the word */
non_noise_count = 0;
- for (i = 0;
- (i < blob_count) && (non_noise_count < fixsp_non_noise_limit); i++) {
- if (noise_score[i] >= non_noise_limit)
+ for (i = 0; i < blob_count && non_noise_count < fixsp_non_noise_limit; i++) {
+ if (noise_score[i] >= non_noise_limit) {
non_noise_count++;
+ }
}
if (non_noise_count < fixsp_non_noise_limit)
return -1;
+
min_noise_blob = i;
non_noise_count = 0;
- for (i = blob_count - 1;
- (i >= 0) && (non_noise_count < fixsp_non_noise_limit); i--) {
- if (noise_score[i] >= non_noise_limit)
+ for (i = blob_count - 1; i >= 0 && non_noise_count < fixsp_non_noise_limit;
+ i--) {
+ if (noise_score[i] >= non_noise_limit) {
non_noise_count++;
+ }
}
if (non_noise_count < fixsp_non_noise_limit)
return -1;
+
max_noise_blob = i;
if (min_noise_blob > max_noise_blob)
@@ -873,69 +838,64 @@ inT16 worst_noise_blob(WERD_RES *word_res, float *worst_noise_score) {
return worst_noise_blob;
}
-
-float blob_noise_score(PBLOB *blob) {
- OUTLINE_IT outline_it;
- TBOX box; //BB of outline
+float Tesseract::blob_noise_score(TBLOB *blob) {
+ TBOX box; // BB of outline
inT16 outline_count = 0;
inT16 max_dimension;
inT16 largest_outline_dimension = 0;
- outline_it.set_to_list (blob->out_list ());
- for (outline_it.mark_cycle_pt ();
- !outline_it.cycled_list (); outline_it.forward ()) {
+ 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 ();
- else
- max_dimension = box.width ();
+ box = ol->bounding_box();
+ if (box.height() > box.width()) {
+ max_dimension = box.height();
+ } else {
+ max_dimension = box.width();
+ }
if (largest_outline_dimension < max_dimension)
largest_outline_dimension = max_dimension;
}
- if (fixsp_noise_score_fixing) {
- if (outline_count > 5)
- //penalise LOTS of blobs
- largest_outline_dimension *= 2;
-
- box = blob->bounding_box ();
+ if (outline_count > 5) {
+ // penalise LOTS of blobs
+ largest_outline_dimension *= 2;
+ }
- if ((box.bottom () > bln_baseline_offset * 4) ||
- (box.top () < bln_baseline_offset / 2))
- //Lax blob is if high or low
- largest_outline_dimension /= 2;
+ box = blob->bounding_box();
+ if (box.bottom() > kBlnBaselineOffset * 4 ||
+ box.top() < kBlnBaselineOffset / 2) {
+ // Lax blob is if high or low
+ largest_outline_dimension /= 2;
}
+
return largest_outline_dimension;
}
-
+} // namespace tesseract
void fixspace_dbg(WERD_RES *word) {
- TBOX box = word->word->bounding_box ();
+ TBOX box = word->word->bounding_box();
BOOL8 show_map_detail = FALSE;
inT16 i;
- box.print ();
- #ifndef SECURE_NAMES
- tprintf (" \"%s\" ", word->best_choice->unichar_string().string ());
- tprintf ("Blob count: %d (word); %d/%d (outword)\n",
- word->word->gblob_list ()->length (),
- word->outword->gblob_list ()->length (),
- word->outword->rej_blob_list ()->length ());
- word->reject_map.print (debug_fp);
- tprintf ("\n");
+ box.print();
+ tprintf(" \"%s\" ", word->best_choice->unichar_string().string());
+ tprintf("Blob count: %d (word); %d/%d (rebuild word)\n",
+ word->word->gblob_list()->length(),
+ word->rebuild_word->NumBlobs(),
+ word->box_word->length());
+ word->reject_map.print(debug_fp);
+ tprintf("\n");
if (show_map_detail) {
- tprintf ("\"%s\"\n", word->best_choice->unichar_string().string ());
+ tprintf("\"%s\"\n", word->best_choice->unichar_string().string());
for (i = 0; word->best_choice->unichar_string()[i] != '\0'; i++) {
- tprintf ("**** \"%c\" ****\n", word->best_choice->unichar_string()[i]);
- word->reject_map[i].full_print (debug_fp);
+ tprintf("**** \"%c\" ****\n", word->best_choice->unichar_string()[i]);
+ word->reject_map[i].full_print(debug_fp);
}
}
- tprintf ("Tess Accepted: %s\n", word->tess_accepted ? "TRUE" : "FALSE");
- tprintf ("Done flag: %s\n\n", word->done ? "TRUE" : "FALSE");
- #endif
+ tprintf("Tess Accepted: %s\n", word->tess_accepted ? "TRUE" : "FALSE");
+ tprintf("Done flag: %s\n\n", word->done ? "TRUE" : "FALSE");
}
@@ -955,25 +915,23 @@ inT16 Tesseract::fp_eval_word_spacing(WERD_RES_LIST &word_res_list) {
inT16 word_length;
inT16 score = 0;
inT16 i;
- float small_limit = bln_x_height * fixsp_small_outlines_size;
-
- if (!fixsp_fp_eval)
- return (eval_word_spacing (word_res_list));
+ float small_limit = kBlnXHeight * fixsp_small_outlines_size;
- for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
- word = word_it.data ();
+ for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
+ word = word_it.data();
word_length = word->reject_map.length();
- if ((word->done ||
- word->tess_accepted) ||
- (word->best_choice->permuter() == SYSTEM_DAWG_PERM) ||
- (word->best_choice->permuter() == FREQ_DAWG_PERM) ||
- (word->best_choice->permuter() == USER_DAWG_PERM) ||
- (safe_dict_word(*(word->best_choice)) > 0)) {
- blob_it.set_to_list(word->outword->blob_list());
+ if (word->done ||
+ word->tess_accepted ||
+ word->best_choice->permuter() == SYSTEM_DAWG_PERM ||
+ word->best_choice->permuter() == FREQ_DAWG_PERM ||
+ word->best_choice->permuter() == USER_DAWG_PERM ||
+ safe_dict_word(*word->best_choice) > 0) {
+ TBLOB* blob = word->rebuild_word->blobs;
UNICHAR_ID space = getDict().getUnicharset().unichar_to_id(" ");
- for (i = 0; i < word->best_choice->length(); ++i, blob_it.forward()) {
+ for (i = 0; i < word->best_choice->length() && blob != NULL;
+ ++i, blob = blob->next) {
if (word->best_choice->unichar_id(i) == space ||
- (blob_noise_score(blob_it.data()) < small_limit)) {
+ blob_noise_score(blob) < small_limit) {
score -= 1; // penalise possibly erroneous non-space
} else if (word->reject_map[i].accepted()) {
score++;
@@ -985,4 +943,5 @@ inT16 Tesseract::fp_eval_word_spacing(WERD_RES_LIST &word_res_list) {
score = 0;
return score;
}
+
} // namespace tesseract
diff --git a/ccmain/fixspace.h b/ccmain/fixspace.h
index 1d862b4a67..56c3018794 100644
--- a/ccmain/fixspace.h
+++ b/ccmain/fixspace.h
@@ -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
diff --git a/ccmain/fixxht.cpp b/ccmain/fixxht.cpp
index 07e35537ea..ded7dd7e57 100644
--- a/ccmain/fixxht.cpp
+++ b/ccmain/fixxht.cpp
@@ -17,816 +17,150 @@
*
**********************************************************************/
-#ifdef _MSC_VER
-#pragma warning(disable:4244) // Conversion warnings
-#endif
-
#include "mfcpch.h"
#include
#include
-#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);
+namespace tesseract {
- 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++;
- }
+// 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.
+
+// 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;
+
+// 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(x_ht_acceptance_tolerance));
+ }
+ }
+ }
+ return bad_blobs;
+}
- 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);
- }
+// 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");
}
}
}
- 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 (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 ("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);
+ 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());
}
- #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 ();
+ // 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
- *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
-
+ return 0.0f;
}
-
-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;
-
- new_string_lengths[0] = 0;
-
- 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++;
- }
- }
- 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;
- }
-}
} // 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;
-}
diff --git a/ccmain/fixxht.h b/ccmain/fixxht.h
deleted file mode 100644
index 18a62de554..0000000000
--- a/ccmain/fixxht.h
+++ /dev/null
@@ -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
diff --git a/ccmain/matmatch.cpp b/ccmain/matmatch.cpp
deleted file mode 100644
index 694510911d..0000000000
--- a/ccmain/matmatch.cpp
+++ /dev/null
@@ -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
-#include
-#include
-#include
-#ifdef __UNIX__
-#include
-#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
diff --git a/ccmain/matmatch.h b/ccmain/matmatch.h
deleted file mode 100644
index a77f13a05a..0000000000
--- a/ccmain/matmatch.h
+++ /dev/null
@@ -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
diff --git a/ccmain/osdetect.cpp b/ccmain/osdetect.cpp
index be7d5d6c7a..157dff5dc7 100644
--- a/ccmain/osdetect.cpp
+++ b/ccmain/osdetect.cpp
@@ -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(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(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(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);
}
diff --git a/ccmain/osdetect.h b/ccmain/osdetect.h
index 364ac00cab..f649b8a6a1 100644
--- a/ccmain/osdetect.h
+++ b/ccmain/osdetect.h
@@ -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__
diff --git a/ccmain/output.cpp b/ccmain/output.cpp
index 2cba0445ff..7a361aa5bf 100644
--- a/ccmain/output.cpp
+++ b/ccmain/output.cpp
@@ -22,28 +22,25 @@
#endif
#include "mfcpch.h"
-#include "ocrshell.h"
-#include
-#include
+#include
+#include
#ifdef __UNIX__
#include
#include
-#include
+#include
#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(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);
diff --git a/ccmain/output.h b/ccmain/output.h
index 4c923020df..9e9bfd514c 100644
--- a/ccmain/output.h
+++ b/ccmain/output.h
@@ -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
diff --git a/textord/pagesegmain.cpp b/ccmain/pagesegmain.cpp
similarity index 52%
rename from textord/pagesegmain.cpp
rename to ccmain/pagesegmain.cpp
index 4766fc3d7e..e63b30dfb5 100644
--- a/textord/pagesegmain.cpp
+++ b/ccmain/pagesegmain.cpp
@@ -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(
static_cast(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(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.
diff --git a/ccmain/pagewalk.cpp b/ccmain/pagewalk.cpp
index ff15947444..3b3bcb3da6 100644
--- a/ccmain/pagewalk.cpp
+++ b/ccmain/pagewalk.cpp
@@ -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;
- }
- }
- }
- }
- }
- }
-}
-} // 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)
+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;
}
}
}
-
-
-/**
- * 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;
- }
- }
- }
- }
-}
+} // namespace tesseract
diff --git a/ccmain/pagewalk.h b/ccmain/pagewalk.h
deleted file mode 100644
index 65e0da8457..0000000000
--- a/ccmain/pagewalk.h
+++ /dev/null
@@ -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
diff --git a/ccmain/paircmp.cpp b/ccmain/paircmp.cpp
deleted file mode 100644
index fb25070383..0000000000
--- a/ccmain/paircmp.cpp
+++ /dev/null
@@ -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
diff --git a/ccmain/varabled.cpp b/ccmain/paramsd.cpp
similarity index 56%
rename from ccmain/varabled.cpp
rename to ccmain/paramsd.cpp
index e059058d25..087a95baca 100644
--- a/ccmain/varabled.cpp
+++ b/ccmain/paramsd.cpp
@@ -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 vcMap;
-
-static int nrVariables = 0;
+static std::map 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(v1);
- const VariableContent* two =
- *reinterpret_cast(v2);
+int ParamContent::Compare(const void* v1, const void* v2) {
+ const ParamContent* one =
+ *reinterpret_cast(v1);
+ const ParamContent* two =
+ *reinterpret_cast(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 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::iterator iter = vcMap.begin();
+ for (std::map::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());
diff --git a/ccmain/paramsd.h b/ccmain/paramsd.h
new file mode 100644
index 0000000000..12ddc8ee20
--- /dev/null
+++ b/ccmain/paramsd.h
@@ -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
diff --git a/ccmain/pgedit.cpp b/ccmain/pgedit.cpp
index 48d1cfd025..814afb33e9 100755
--- a/ccmain/pgedit.cpp
+++ b/ccmain/pgedit.cpp
@@ -21,20 +21,22 @@
#pragma warning(disable:4244) // Conversion warnings
#endif
+// Include automatically generated configuration file if running autoconf.
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif
+
#include "pgedit.h"
#include
#include
#include "genblob.h"
-#include "tessio.h"
-#include "tessout.h"
#include "tordmain.h"
#include "statistc.h"
#include "debugwin.h"
#include "svshowim.h"
-#include "mainblk.h"
-#include "varabled.h"
+#include "paramsd.h"
#include "string.h"
#include "scrollview.h"
@@ -45,47 +47,31 @@
#include "blread.h"
-// Include automatically generated configuration file if running autoconf.
-#ifdef HAVE_CONFIG_H
-#include "config_auto.h"
-#endif
-
#ifndef GRAPHICS_DISABLED
-#define ASC_HEIGHT (2 * bln_baseline_offset + bln_x_height)
-#define X_HEIGHT (bln_baseline_offset + bln_x_height)
-#define BL_HEIGHT bln_baseline_offset
+#define ASC_HEIGHT (2 * kBlnBaselineOffset + kBlnXHeight)
+#define X_HEIGHT (kBlnBaselineOffset + kBlnXHeight)
+#define BL_HEIGHT kBlnBaselineOffset
#define DESC_HEIGHT 0
#define MAXSPACING 128 /*max expected spacing in pix */
const ERRCODE EMPTYBLOCKLIST = "No blocks to edit";
-extern IMAGE page_image;
enum CMD_EVENTS
{
NULL_CMD_EVENT,
- DELETE_CMD_EVENT,
- COPY_CMD_EVENT,
CHANGE_DISP_CMD_EVENT,
- CHANGE_TEXT_CMD_EVENT,
- TOGGLE_SEG_CMD_EVENT,
DUMP_WERD_CMD_EVENT,
SHOW_POINT_CMD_EVENT,
- ROW_SPACE_STAT_CMD_EVENT,
- BLOCK_SPACE_STAT_CMD_EVENT,
SHOW_BLN_WERD_CMD_EVENT,
- SEGMENT_WERD_CMD_EVENT,
+ DEBUG_WERD_CMD_EVENT,
BOUNDING_BOX_CMD_EVENT,
CORRECT_TEXT_CMD_EVENT,
POLYGONAL_CMD_EVENT,
BL_NORM_CMD_EVENT,
BITMAP_CMD_EVENT,
- TIDY_CMD_EVENT,
- VIEW_CMD_EVENT,
IMAGE_CMD_EVENT,
BLOCKS_CMD_EVENT,
BASELINES_CMD_EVENT,
- WRITE_CMD_EVENT,
- NEW_SOURCE_CMD_EVENT,
UNIFORM_DISP_CMD_EVENT,
REFRESH_CMD_EVENT,
QUIT_CMD_EVENT,
@@ -100,196 +86,51 @@ enum CMD_EVENTS
*/
ScrollView* image_win;
-VariablesEditor* ve;
+ParamsEditor* pe;
bool stillRunning = false;
#ifdef __UNIX__
-FILE *debug_window = NULL; // opened on demand
+FILE *debug_window = NULL; // opened on demand
#endif
- // baseline norm words
-ScrollView* bln_word_window = NULL;
+ScrollView* bln_word_window = NULL; // baseline norm words
-CMD_EVENTS mode = CHANGE_DISP_CMD_EVENT;
- // Selected words op
+CMD_EVENTS mode = CHANGE_DISP_CMD_EVENT; // selected words op
+// These variables should remain global, since they are only used for the
+// debug mode (in which only a single Tesseract thread/instance will be exist).
BITS16 word_display_mode;
BOOL8 display_image = FALSE;
BOOL8 display_blocks = FALSE;
BOOL8 display_baselines = FALSE;
-BOOL8 viewing_source = TRUE;
-
-BLOCK_LIST *source_block_list = NULL; // image blocks
-BLOCK_LIST target_block_list; // target blocks
-BLOCK_LIST *other_block_list = &target_block_list;
-
-BOOL8 source_changed = FALSE; // Changes not saved
-BOOL8 target_changed = FALSE; // Changes not saved
-BOOL8 *other_image_changed = &target_changed;
-
-
-/* Public globals */
-
-#define EXTERN
-
-EXTERN BLOCK_LIST *current_block_list = NULL;
-EXTERN BOOL8 *current_image_changed = &source_changed;
-
-/* Variables */
-
-EXTERN STRING_VAR(editor_image_win_name, "EditorImage",
-"Editor image window name");
-EXTERN INT_VAR(editor_image_xpos, 590, "Editor image X Pos");
-EXTERN INT_VAR(editor_image_ypos, 10, "Editor image Y Pos");
-EXTERN INT_VAR(editor_image_menuheight, 50, "Add to image height for menu bar");
-EXTERN INT_VAR(editor_image_word_bb_color, ScrollView::BLUE,
-"Word bounding box colour");
-EXTERN INT_VAR(editor_image_blob_bb_color, ScrollView::YELLOW,
-"Blob bounding box colour");
-EXTERN INT_VAR(editor_image_text_color, ScrollView::WHITE,
-"Correct text colour");
-
-EXTERN STRING_VAR(editor_dbwin_name, "EditorDBWin",
-"Editor debug window name");
-EXTERN INT_VAR(editor_dbwin_xpos, 50, "Editor debug window X Pos");
-EXTERN INT_VAR(editor_dbwin_ypos, 500, "Editor debug window Y Pos");
-EXTERN INT_VAR(editor_dbwin_height, 24, "Editor debug window height");
-EXTERN INT_VAR(editor_dbwin_width, 80, "Editor debug window width");
-
-EXTERN STRING_VAR(editor_word_name, "BlnWords", "BL normalised word window");
-EXTERN INT_VAR(editor_word_xpos, 60, "Word window X Pos");
-EXTERN INT_VAR(editor_word_ypos, 510, "Word window Y Pos");
-EXTERN INT_VAR(editor_word_height, 240, "Word window height");
-EXTERN INT_VAR(editor_word_width, 655, "Word window width");
-
-EXTERN double_VAR(editor_smd_scale_factor, 1.0, "Scaling for smd image");
-
-/**
- * add_word()
- *
- * Inserts the a word into a specified block list. The list is searched for a
- * block and row of the same file as those of the word to be added, which
- * contain the bounding box of the word. If such a row is found, the new
- * word is inserted into the row in the correct X order. If the
- * block is found, but not the row, a copy of the word's old row is added to
- * the block in the correct Y order, and the word is put in that row.
- * If neither the row nor the block are found, then the word's old block is
- * copied with only the word's row. It is added to the block list in the
- * correct Y order.
- */
-
-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
- ) {
- BLOCK_IT block_it(dest_block_list);
- BLOCK *block; // current block
- BLOCK *dest_block = NULL; // destination block
- ROW_IT row_it;
- ROW *row; // destination row
- ROW *dest_row = NULL; // destination row
- WERD_IT word_it;
- TBOX word_box = word->bounding_box();
- TBOX insert_point_word_box;
- BOOL8 seen_blocks_for_current_file = FALSE;
-
- block_it.mark_cycle_pt();
- while(!block_it.cycled_list() &&(dest_block == NULL)) {
- block = block_it.data();
- if ((block->bounding_box().contains(word_box)) &&
- (strcmp(block->name(), src_block->name()) == 0)) {
- dest_block = block; // found dest block
- row_it.set_to_list(block->row_list());
- row_it.mark_cycle_pt();
- while((!row_it.cycled_list()) &&(dest_row == NULL)) {
- row = row_it.data();
- if (row->bounding_box().contains(word_box))
- dest_row = row; // found dest row
- else
- row_it.forward();
- }
- }
- else
- block_it.forward();
- }
-
- if (dest_block == NULL) { // make a new one
- dest_block = new BLOCK;
- *dest_block = *src_block;
-
- block_it.set_to_list(dest_block_list);
- for (block_it.mark_cycle_pt();
- !block_it.cycled_list(); block_it.forward()) {
- block = block_it.data();
-
- if (!seen_blocks_for_current_file &&
- (strcmp(block->name(), dest_block->name()) == 0))
- seen_blocks_for_current_file = TRUE;
-
- if (seen_blocks_for_current_file &&
- ((strcmp(block->name(), dest_block->name()) != 0) ||
- (block->bounding_box().top() <
- dest_block->bounding_box().top())))
- break;
- }
-
- if (block_it.cycled_list())
- // didn't find insrt pt
- block_it.add_to_end(dest_block);
- else
- // did find insert pt
- block_it.add_before_stay_put(dest_block);
- }
-
- if (dest_row == NULL) { // make a new one
- dest_row = new ROW;
- *dest_row = *src_row;
-
- row_it.set_to_list(dest_block->row_list());
- for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
- if (row_it.data()->bounding_box().top() <
- dest_row->bounding_box().top())
- break;
- }
-
- if (row_it.cycled_list())
- // didn't find insrt pt
- row_it.add_to_end(dest_row);
- else
- // did find insert pt
- row_it.add_before_stay_put(dest_row);
- }
-
- /* dest_block and dest_row are now found or built and inserted as necessesary
- so add the word to dest row */
-
- word_it.set_to_list(dest_row->word_list());
- for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
- if (word_it.data()->bounding_box().right() >= word_box.left())
- break;
- }
-
- if (word_it.cycled_list())
- word_it.add_to_end(word); // didn't find insrt pt
- else { // did find insert pt
- insert_point_word_box = word_it.data()->bounding_box();
- if (insert_point_word_box.contains(word_box) ||
- word_box.contains(insert_point_word_box))
- image_win->AddMessage("Refusing to add words which obliterate,"
- " or are obliterated by, others");
- else {
- if (word_it.data()->bounding_box().left() >
- word->bounding_box().left())
- // infront of insert pt
- word_it.add_before_stay_put(word);
- else
- // behind insert pt
- word_it.add_after_stay_put(word);
- }
- }
-}
+PAGE_RES *current_page_res = NULL;
+
+STRING_VAR(editor_image_win_name, "EditorImage",
+ "Editor image window name");
+INT_VAR(editor_image_xpos, 590, "Editor image X Pos");
+INT_VAR(editor_image_ypos, 10, "Editor image Y Pos");
+INT_VAR(editor_image_menuheight, 50, "Add to image height for menu bar");
+INT_VAR(editor_image_word_bb_color, ScrollView::BLUE,
+ "Word bounding box colour");
+INT_VAR(editor_image_blob_bb_color, ScrollView::YELLOW,
+ "Blob bounding box colour");
+INT_VAR(editor_image_text_color, ScrollView::WHITE,
+ "Correct text colour");
+
+STRING_VAR(editor_dbwin_name, "EditorDBWin",
+ "Editor debug window name");
+INT_VAR(editor_dbwin_xpos, 50, "Editor debug window X Pos");
+INT_VAR(editor_dbwin_ypos, 500, "Editor debug window Y Pos");
+INT_VAR(editor_dbwin_height, 24, "Editor debug window height");
+INT_VAR(editor_dbwin_width, 80, "Editor debug window width");
+
+STRING_VAR(editor_word_name, "BlnWords", "BL normalized word window");
+INT_VAR(editor_word_xpos, 60, "Word window X Pos");
+INT_VAR(editor_word_ypos, 510, "Word window Y Pos");
+INT_VAR(editor_word_height, 240, "Word window height");
+INT_VAR(editor_word_width, 655, "Word window width");
+
+STRING_VAR(editor_debug_config_file, "", "Config file to apply to single words");
class BlnEventHandler : public SVEventHandler {
public:
@@ -297,7 +138,7 @@ class BlnEventHandler : public SVEventHandler {
if (sv_event->type == SVET_DESTROY)
bln_word_window = NULL;
else if (sv_event->type == SVET_CLICK)
- show_point(current_block_list, sv_event->x, sv_event->y);
+ show_point(current_page_res, sv_event->x, sv_event->y);
}
};
@@ -327,106 +168,21 @@ ScrollView* bln_word_window_handle() { // return handle
* new window needs to be. Create it and re-display.
*/
-void build_image_window(TBOX page_bounding_box) {
+void build_image_window(int width, int height) {
if (image_win != NULL) { delete image_win; }
image_win = new ScrollView(editor_image_win_name.string(),
editor_image_xpos, editor_image_ypos,
- page_bounding_box.right() + 1,
- page_bounding_box.top() +
- editor_image_menuheight + 1,
- page_bounding_box.right() + 1,
- page_bounding_box.top() + 1,
+ width + 1,
+ height + editor_image_menuheight + 1,
+ width + 1,
+ height + 1,
true);
}
-
-/**
- * build_menu()
- *
- * Construct the menu tree used by the command window
- */
-namespace tesseract {
-SVMenuNode *Tesseract::build_menu_new() {
-
- SVMenuNode* parent_menu;
- SVMenuNode* root_menu_item = new SVMenuNode();
-
- SVMenuNode* modes_menu_item = root_menu_item->AddChild("MODES");
-
- modes_menu_item->AddChild("Change Display",
- CHANGE_DISP_CMD_EVENT);
- modes_menu_item->AddChild("Delete",
- DELETE_CMD_EVENT);
- modes_menu_item->AddChild("Copy to TARGET",
- COPY_CMD_EVENT);
- modes_menu_item->AddChild("Change Text",
- CHANGE_TEXT_CMD_EVENT);
- modes_menu_item->AddChild("Toggle Correct Seg Flg",
- TOGGLE_SEG_CMD_EVENT);
- modes_menu_item->AddChild("Dump Word",
- DUMP_WERD_CMD_EVENT);
- modes_menu_item->AddChild("Show Point",
- SHOW_POINT_CMD_EVENT);
- modes_menu_item->AddChild("Row gaps hist",
- ROW_SPACE_STAT_CMD_EVENT);
- modes_menu_item->AddChild("Block gaps hist",
- BLOCK_SPACE_STAT_CMD_EVENT);
- modes_menu_item->AddChild("Show BL Norm Word",
- SHOW_BLN_WERD_CMD_EVENT);
- modes_menu_item->AddChild("Re-Segment Word",
- SEGMENT_WERD_CMD_EVENT);
- modes_menu_item->AddChild("Recog Words",
- RECOG_WERDS);
- modes_menu_item->AddChild("Recog Blobs",
- RECOG_PSEUDO);
-
- parent_menu = root_menu_item->AddChild("DISPLAY");
-
- parent_menu->AddChild("Bounding Boxes",
- BOUNDING_BOX_CMD_EVENT, FALSE);
- parent_menu->AddChild("Correct Text",
- CORRECT_TEXT_CMD_EVENT, FALSE);
- parent_menu->AddChild("Polygonal Approx",
- POLYGONAL_CMD_EVENT, FALSE);
- parent_menu->AddChild("Baseline Normalised",
- BL_NORM_CMD_EVENT, FALSE);
- parent_menu->AddChild("Edge Steps",
- BITMAP_CMD_EVENT, TRUE);
-
- parent_menu = root_menu_item->AddChild("OTHER");
-
- parent_menu->AddChild("Quit",
- QUIT_CMD_EVENT);
- parent_menu->AddChild("Tidy Target",
- TIDY_CMD_EVENT);
-
- parent_menu->AddChild("View TARGET",
- VIEW_CMD_EVENT, FALSE);
- parent_menu->AddChild("Show Image",
- IMAGE_CMD_EVENT, FALSE);
- parent_menu->AddChild("ShowBlock Outlines",
- BLOCKS_CMD_EVENT, FALSE);
- parent_menu->AddChild("Show Baselines",
- BASELINES_CMD_EVENT, FALSE);
- parent_menu->AddChild("Write File",
- WRITE_CMD_EVENT, imagebasename.string());
- parent_menu->AddChild("New Source File",
- NEW_SOURCE_CMD_EVENT, imagebasename.string());
- parent_menu->AddChild("Uniform Display",
- UNIFORM_DISP_CMD_EVENT);
- parent_menu->AddChild("Refresh Display",
- REFRESH_CMD_EVENT);
-
- return root_menu_item;
-}
-
-} // namespace tesseract
-
-
/**
* display_bln_lines()
*
- * Display normalised baseline, x-height, ascender limit and descender limit
+ * Display normalized baseline, x-height, ascender limit and descender limit
*/
void display_bln_lines(ScrollView* window,
@@ -446,251 +202,97 @@ void display_bln_lines(ScrollView* window,
maxx, y_offset + scale_factor * ASC_HEIGHT);
}
-
/**
- * do_new_source()
+ * notify()
*
- * Change to another source file. Automatically tidy page first
+ * Event handler that processes incoming events, either forwarding
+ * them to process_cmd_win_event or process_image_event.
*
*/
-namespace tesseract {
-void Tesseract::do_new_source( // serialise
- ) {
- FILE *infp; // input file
-
- char* name = image_win->ShowInputDialog("New Source File name");
-
- STRING name_str(name);
- delete[] name;
-
- if (source_changed) {
-
- int a = image_win->ShowYesNoDialog(
- "Source changes will be LOST. Continue?(Y/N)");
- if (a != 'y') { image_win->AddMessage("Write cancelled"); return; }
+void PGEventHandler::Notify(const SVEvent* event) {
+ char myval = '0';
+ if (event->type == SVET_POPUP) {
+ pe->Notify(event);
+ } // These are handled by ParamsEditor
+ else if (event->type == SVET_EXIT) { stillRunning = false; }
+ else if (event->type == SVET_MENU) {
+ if (strcmp(event->parameter, "true") == 0) { myval = 'T'; }
+ else if (strcmp(event->parameter, "false") == 0) { myval = 'F'; }
+ tess_->process_cmd_win_event(event->command_id, &myval);
}
-
- // if not file exists
- if (!(infp = fopen(name_str.string(), "r"))) {
-
- image_win->AddMessage("Cant open file " "%s" "", name_str.string());
- return;
+ else {
+ tess_->process_image_event(*event);
}
-
- fclose(infp);
-
- image_win->AddMessage("Reading file " "%s" "...", name_str.string());
- source_block_list->clear();
- // appends to SOURCE
- pgeditor_read_file(name_str, source_block_list);
- source_changed = FALSE;
-
- image_win->AddMessage("Doing automatic Tidy Target...");
- viewing_source = FALSE; // Force viewing source
- do_tidy_cmd();
-
- image_win->AddMessage("Doing automatic Tidy Target...Done");
-
}
-} // namespace tesseract
-
/**
- * do_re_display()
+ * build_menu()
*
- * Redisplay page
+ * Construct the menu tree used by the command window
*/
+namespace tesseract {
+SVMenuNode *Tesseract::build_menu_new() {
+ SVMenuNode* parent_menu;
+ SVMenuNode* root_menu_item = new SVMenuNode();
-void
- // function to call
-do_re_display(BOOL8 word_painter(
-BLOCK *, ROW *, WERD *)) {
- BLOCK_IT block_it(current_block_list);
- BLOCK *block;
- int block_count = 1;
-
- ROW_IT row_it;
- ROW *row;
-
- WERD_IT word_it;
- WERD *word;
-
- image_win->Clear();
- if (display_image != 0) {
- sv_show_sub_image(&page_image, 0, 0,
- page_image.get_xsize(), page_image.get_ysize(),
- image_win, 0, 0);
- }
-
- 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();
- word_painter(block, row, word);
- }
- if (display_baselines)
- row->plot_baseline(image_win, ScrollView::GREEN);
- }
- if (display_blocks)
- block->plot(image_win, block_count++, ScrollView::RED);
- }
- image_win->Update();
-}
-
+ SVMenuNode* modes_menu_item = root_menu_item->AddChild("MODES");
-/**
- * do_tidy_cmd()
- *
- * Tidy TARGET page
- */
+ modes_menu_item->AddChild("Change Display", CHANGE_DISP_CMD_EVENT);
+ modes_menu_item->AddChild("Dump Word", DUMP_WERD_CMD_EVENT);
+ modes_menu_item->AddChild("Show Point", SHOW_POINT_CMD_EVENT);
+ modes_menu_item->AddChild("Show BL Norm Word", SHOW_BLN_WERD_CMD_EVENT);
+ modes_menu_item->AddChild("Config Words", DEBUG_WERD_CMD_EVENT);
+ modes_menu_item->AddChild("Recog Words", RECOG_WERDS);
+ modes_menu_item->AddChild("Recog Blobs", RECOG_PSEUDO);
-const TBOX do_tidy_cmd() { // tidy
- ICOORD shift_vector;
- TBOX tidy_box; // Just the tidy area
- TBOX source_box; // source file area
+ parent_menu = root_menu_item->AddChild("DISPLAY");
- source_box = block_list_bounding_box(source_block_list);
- // find src area
+ parent_menu->AddChild("Bounding Boxes", BOUNDING_BOX_CMD_EVENT, FALSE);
+ parent_menu->AddChild("Correct Text", CORRECT_TEXT_CMD_EVENT, FALSE);
+ parent_menu->AddChild("Polygonal Approx", POLYGONAL_CMD_EVENT, FALSE);
+ parent_menu->AddChild("Baseline Normalized", BL_NORM_CMD_EVENT, FALSE);
+ parent_menu->AddChild("Edge Steps", BITMAP_CMD_EVENT, TRUE);
- if (!target_block_list.empty()) {
- tidy_box = block_list_compress(&target_block_list);
+ parent_menu = root_menu_item->AddChild("OTHER");
- /* Shift tidied target above the source image area. */
+ parent_menu->AddChild("Quit", QUIT_CMD_EVENT);
+ parent_menu->AddChild("Show Image", IMAGE_CMD_EVENT, FALSE);
+ parent_menu->AddChild("ShowBlock Outlines", BLOCKS_CMD_EVENT, FALSE);
+ parent_menu->AddChild("Show Baselines", BASELINES_CMD_EVENT, FALSE);
+ parent_menu->AddChild("Uniform Display", UNIFORM_DISP_CMD_EVENT);
+ parent_menu->AddChild("Refresh Display", REFRESH_CMD_EVENT);
- shift_vector = ICOORD(0, source_box.top() + BLOCK_SPACING)
- - tidy_box.botleft();
- block_list_move(&target_block_list, shift_vector);
- tidy_box.move(shift_vector);
- }
- source_box += tidy_box;
- // big enough for both
- build_image_window(source_box);
- do_view_cmd();
- return tidy_box;
+ return root_menu_item;
}
-
/**
- * do_view_cmd()
+ * do_re_display()
*
- * View TARGET/View SOURCE command
+ * Redisplay page
*/
+void Tesseract::do_re_display(
+ BOOL8 (tesseract::Tesseract::*word_painter)(BLOCK* block,
+ ROW* row,
+ WERD_RES* word_res)) {
+ PAGE_RES_IT pr_it(current_page_res);
+ int block_count = 1;
-void do_view_cmd() {
- viewing_source = !viewing_source;
image_win->Clear();
- if (viewing_source) {
- current_block_list = source_block_list;
- current_image_changed = &source_changed;
- other_block_list = &target_block_list;
- other_image_changed = &target_changed;
- do_re_display(&word_display);
- }
- else {
- current_block_list = &target_block_list;
- current_image_changed = &target_changed;
- other_block_list = source_block_list;
- other_image_changed = &source_changed;
- do_re_display(&word_display);
- }
-}
-
-
-/**
- * do_write_file()
- *
- * Serialise a block list to file
- *
- * If writing image, tidy page and move to(0,0) first
- */
-
-void do_write_file( // serialise
- ) {
-
- char* name = image_win->ShowInputDialog("File Name");
-
- FILE *infp; // input file
- char msg_str[80];
-
- TBOX enclosing_box;
-
- // if file exists
- if ((infp = fopen(name, "r")) != NULL) {
- fclose(infp);
- sprintf(msg_str, "Overwrite file " "%s" "?(Y/N)", name);
-
- int a = image_win->ShowYesNoDialog(msg_str);
- if (a != 'y') { image_win->AddMessage("Write cancelled"); delete[] name; return; }
+ if (display_image != 0) {
+ image_win->Image(pix_binary_, 0, 0);
}
- infp = fopen(name, "w"); // can we write to it?
- if (infp == NULL) {
-
- image_win->AddMessage("Cant write to file " "%s" "", name);
- delete[] name;
- return;
- }
- fclose(infp);
-
- delete [] name;
-
- if (!viewing_source && !target_block_list.empty()) {
- // Tidy & move to(0,0)
- image_win->AddMessage("Automatic tidy...");
- viewing_source = TRUE; // Stay viewing target!
- enclosing_box = do_tidy_cmd();
- block_list_move(&target_block_list, -enclosing_box.botleft());
- image_win->AddMessage("Writing file...");
- pgeditor_write_file(name, &target_block_list);
- // move back
- block_list_move(&target_block_list,
- enclosing_box.botleft());
- }
- else {
- image_win->AddMessage("Writing file...");
- pgeditor_write_file(name, current_block_list);
+ for (WERD_RES* word = pr_it.word(); word != NULL; word = pr_it.forward()) {
+ (this->*word_painter)(pr_it.block()->block, pr_it.row()->row, word);
+ if (display_baselines && pr_it.row() != pr_it.prev_row())
+ pr_it.row()->row->plot_baseline(image_win, ScrollView::GREEN);
+ if (display_blocks && pr_it.block() != pr_it.prev_block())
+ pr_it.block()->block->plot(image_win, block_count++, ScrollView::RED);
}
- image_win->AddMessage("Writing file...Done");
- *current_image_changed = FALSE;
-
-}
-
-/**
- * notify()
- *
- * Event handler that processes incoming events, either forwarding
- * them to process_cmd_win_event or process_image_event.
- *
- */
-
-void PGEventHandler::Notify(const SVEvent* event) {
- char myval = '0';
- if (event->type == SVET_POPUP) {
-ve->Notify(event);
- } // These are handled by Var. Editor
- else if (event->type == SVET_EXIT) { stillRunning = false; }
- else if (event->type == SVET_MENU) {
- if (strcmp(event->parameter, "true") == 0) { myval = 'T'; }
- else if (strcmp(event->parameter, "false") == 0) { myval = 'F'; }
- tess_->process_cmd_win_event(event->command_id, &myval);
- }
- else {
- tess_->process_image_event(*event);
- // else pgeditor_show_point(*event);
- }
- current_word_quit.set_value(FALSE);
- selection_quit.set_value(FALSE);
- // replot all var wins
+ image_win->Update();
}
-
/**
* pgeditor_main()
*
@@ -699,21 +301,19 @@ ve->Notify(event);
*
*/
-namespace tesseract {
-void Tesseract::pgeditor_main(BLOCK_LIST *blocks) {
+void Tesseract::pgeditor_main(int width, int height, PAGE_RES *page_res) {
- source_block_list = blocks;
- current_block_list = blocks;
- if (current_block_list->empty())
+ current_page_res = page_res;
+ if (current_page_res->block_res_list.empty())
return;
stillRunning = true;
- build_image_window(block_list_bounding_box(source_block_list));
+ build_image_window(width, height);
word_display_mode.turn_on_bit(DF_EDGE_STEP);
- do_re_display(&word_set_display);
+ do_re_display(&tesseract::Tesseract::word_set_display);
#ifndef GRAPHICS_DISABLED
- ve = new VariablesEditor(this, image_win);
+ pe = new ParamsEditor(this, image_win);
#endif
PGEventHandler pgEventHandler(this);
@@ -742,34 +342,6 @@ void pgeditor_msg( // message display
image_win->AddMessage(msg);
}
-
-/**
- * pgeditor_read_file()
- *
- * Deserialise source file
- */
-
-namespace tesseract {
-void Tesseract::pgeditor_read_file( // of serialised file
- STRING &filename,
- BLOCK_LIST *blocks // block list to add to
- ) {
- STRING name = filename; //truncated name
- const char *lastdot; //of name
- TO_BLOCK_LIST land_blocks, port_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);
- textord_page(page_box.topright(), blocks, &land_blocks, &port_blocks, this);
-}
-} // namespace tesseract
-
/**
* pgeditor_show_point()
*
@@ -781,43 +353,6 @@ void pgeditor_show_point( // display coords
image_win->AddMessage("Pointing at(%d, %d)", event->x, event->y);
}
-
-/**
- * pgeditor_write_file()
- *
- * Serialise a block list to file
- *
- */
-
-void pgeditor_write_file( // serialise
- char *name, // file name
- BLOCK_LIST *blocks // block list to write
- ) {
- FILE *infp; // input file
- BLOCK_IT block_it(blocks); // block iterator
- BLOCK *block; // current block
- ROW_IT row_it; // row iterator
-
- infp = fopen(name, "w"); // create output file
- if (infp == NULL)
- CANTCREATEFILE.error("pgeditor_write_file", EXIT, name);
-
- for (block_it.mark_cycle_pt();
- !block_it.cycled_list(); block_it.forward()) {
- block = block_it.extract();
-
- row_it.set_to_list(block->row_list());
- for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward())
- // ensure correct
- row_it.data()->recalc_bounding_box();
-
- block->serialise(infp); // serialize non-empty
- block_it.add_after_then_move(block);
- }
- fclose(infp);
-}
-
-
/**
* process_cmd_win_event()
*
@@ -837,25 +372,17 @@ BOOL8 Tesseract::process_cmd_win_event( // UI command semantics
case NULL_CMD_EVENT:
break;
- case VIEW_CMD_EVENT:
- do_view_cmd();
- break;
case CHANGE_DISP_CMD_EVENT:
- case DELETE_CMD_EVENT:
- case CHANGE_TEXT_CMD_EVENT:
- case TOGGLE_SEG_CMD_EVENT:
case DUMP_WERD_CMD_EVENT:
case SHOW_POINT_CMD_EVENT:
- case ROW_SPACE_STAT_CMD_EVENT:
- case BLOCK_SPACE_STAT_CMD_EVENT:
case SHOW_BLN_WERD_CMD_EVENT:
- case SEGMENT_WERD_CMD_EVENT:
+ case RECOG_WERDS:
+ case RECOG_PSEUDO:
mode =(CMD_EVENTS) cmd_event;
break;
- case COPY_CMD_EVENT:
- mode =(CMD_EVENTS) cmd_event;
- if (!viewing_source)
- image_win->AddMessage("Can't COPY while viewing target!");
+ case DEBUG_WERD_CMD_EVENT:
+ mode = DEBUG_WERD_CMD_EVENT;
+ word_config_ = image_win->ShowInputDialog("Config File Name");
break;
case BOUNDING_BOX_CMD_EVENT:
if (new_value[0] == 'T')
@@ -893,53 +420,27 @@ BOOL8 Tesseract::process_cmd_win_event( // UI command semantics
mode = CHANGE_DISP_CMD_EVENT;
break;
case UNIFORM_DISP_CMD_EVENT:
- do_re_display(&word_set_display);
- *current_image_changed = TRUE;
- break;
- case WRITE_CMD_EVENT:
- do_write_file();
- break;
- case TIDY_CMD_EVENT:
- if (!target_block_list.empty()) {
- viewing_source = TRUE; // Force viewing target
- do_tidy_cmd();
- }
- break;
- case NEW_SOURCE_CMD_EVENT:
- do_new_source();
+ do_re_display(&tesseract::Tesseract::word_set_display);
break;
case IMAGE_CMD_EVENT:
display_image =(new_value[0] == 'T');
- do_re_display(&word_display);
+ do_re_display(&tesseract::Tesseract::word_display);
break;
case BLOCKS_CMD_EVENT:
display_blocks =(new_value[0] == 'T');
- do_re_display(&word_display);
+ do_re_display(&tesseract::Tesseract::word_display);
break;
case BASELINES_CMD_EVENT:
display_baselines =(new_value[0] == 'T');
- do_re_display(&word_display);
+ do_re_display(&tesseract::Tesseract::word_display);
break;
case REFRESH_CMD_EVENT:
- do_re_display(&word_display);
+ do_re_display(&tesseract::Tesseract::word_display);
break;
case QUIT_CMD_EVENT:
- if (source_changed || target_changed) {
- int a = image_win->ShowYesNoDialog(
- "Changes not saved. Exit anyway?(Y/N)");
- if (a == 'y') { exit = TRUE; ScrollView::Exit(); }
- }
- else {
- exit = TRUE;
- ScrollView::Exit();
- }
+ exit = TRUE;
+ ScrollView::Exit();
break;
- case RECOG_WERDS:
- mode = RECOG_WERDS;
- break;
- case RECOG_PSEUDO:
- mode = RECOG_PSEUDO;
- break;
default:
sprintf(msg, "Unrecognised event " INT32FORMAT "(%s)",
@@ -962,6 +463,8 @@ BOOL8 Tesseract::process_cmd_win_event( // UI command semantics
*/
void Tesseract::process_image_event( // action in image win
const SVEvent &event) {
+ // The following variable should remain static, since it is used by
+ // debug editor, which uses a single Tesseract instance.
static ICOORD down;
ICOORD up;
TBOX selection_box;
@@ -971,10 +474,10 @@ void Tesseract::process_image_event( // action in image win
case SVET_SELECTION:
if (event.type == SVET_SELECTION) {
- down.set_x(event.x - event.x_size);
+ down.set_x(event.x + event.x_size);
down.set_y(event.y + event.y_size);
if (mode == SHOW_POINT_CMD_EVENT)
- show_point(current_block_list, event.x, event.y);
+ show_point(current_page_res, event.x, event.y);
}
up.set_x(event.x);
@@ -984,64 +487,36 @@ void Tesseract::process_image_event( // action in image win
switch(mode) {
case CHANGE_DISP_CMD_EVENT:
- ::process_selected_words(current_block_list,
- selection_box,
- &word_blank_and_set_display);
+ process_selected_words(
+ current_page_res,
+ selection_box,
+ &tesseract::Tesseract::word_blank_and_set_display);
break;
- case COPY_CMD_EVENT:
- if (!viewing_source)
- image_win->AddMessage("Can't COPY while viewing target!");
- else
- ::process_selected_words(current_block_list,
- selection_box,
- &word_copy);
- break;
- case DELETE_CMD_EVENT:
- ::process_selected_words_it(current_block_list,
- selection_box,
- &word_delete);
- break;
- case CHANGE_TEXT_CMD_EVENT:
- ::process_selected_words(current_block_list,
+ case DUMP_WERD_CMD_EVENT:
+ process_selected_words(current_page_res,
selection_box,
- &word_change_text);
- break;
- case TOGGLE_SEG_CMD_EVENT:
- ::process_selected_words(current_block_list,
- selection_box,
- &word_toggle_seg);
- break;
- case DUMP_WERD_CMD_EVENT:
- ::process_selected_words(current_block_list,
- selection_box,
- &word_dumper);
+ &tesseract::Tesseract::word_dumper);
break;
case SHOW_BLN_WERD_CMD_EVENT:
- ::process_selected_words(current_block_list,
- selection_box,
- &word_bln_display);
- break;
- case SEGMENT_WERD_CMD_EVENT:
- re_segment_word(current_block_list, selection_box);
- break;
- case ROW_SPACE_STAT_CMD_EVENT:
- row_space_stat(current_block_list, selection_box);
+ process_selected_words(current_page_res,
+ selection_box,
+ &tesseract::Tesseract::word_bln_display);
break;
- case BLOCK_SPACE_STAT_CMD_EVENT:
- block_space_stat(current_block_list, selection_box);
+ case DEBUG_WERD_CMD_EVENT:
+ debug_word(current_page_res, selection_box);
break;
case SHOW_POINT_CMD_EVENT:
break; // ignore up event
case RECOG_WERDS:
image_win->AddMessage("Recogging selected words");
- this->process_selected_words(current_block_list,
+ this->process_selected_words(current_page_res,
selection_box,
&Tesseract::recog_interactive);
break;
case RECOG_PSEUDO:
image_win->AddMessage("Recogging selected blobs");
- recog_pseudo_word(current_block_list, selection_box);
+ recog_pseudo_word(current_page_res, selection_box);
break;
default:
@@ -1053,328 +528,17 @@ void Tesseract::process_image_event( // action in image win
break;
}
}
-} // namespace tesseract
-
-
-/**
- * re_scale_and_move_bln_word()
- *
- * Scale and move a bln word so that it fits in a specified bounding box.
- * Scale by width or height to generate the largest image
- */
-
-float re_scale_and_move_bln_word( // put bln word in box
- WERD *norm_word, //< BL normalised word
- const TBOX &box //< destination box
- ) {
- TBOX norm_box = norm_word->bounding_box();
- float width_scale_factor;
- float height_scale_factor;
- float selected_scale_factor;
-
- width_scale_factor = box.width() /(float) norm_box.width();
- height_scale_factor = box.height() /(float) ASC_HEIGHT;
-
- if ((ASC_HEIGHT * width_scale_factor) <= box.height())
- selected_scale_factor = width_scale_factor;
- else
- selected_scale_factor = height_scale_factor;
-
- norm_word->scale(selected_scale_factor);
- norm_word->move(ICOORD((box.left() + box.width() / 2), box.bottom()));
- return selected_scale_factor;
-}
-
/**
- * re_segment_word()
- *
- * If all selected blobs are in the same row, remove them from their current
- * word(s) and put them in a new word. Insert the new word in the row at the
- * appropriate point. Delete any empty words.
+ * debug_word
*
+ * Process the whole image, but load word_config_ for the selected word(s).
*/
-
-void re_segment_word( // break/join words
- BLOCK_LIST *block_list, // blocks to check
- TBOX &selection_box) {
- BLOCK_IT block_it(block_list);
- BLOCK *block;
- BLOCK *block_to_process = NULL;
- ROW_IT row_it;
- ROW *row;
- ROW *row_to_process = NULL;
- WERD_IT word_it;
- WERD *word;
- WERD *new_word = NULL;
- BOOL8 polyg = false;
- PBLOB_IT blob_it;
- PBLOB_LIST dummy; // Just to initialize new_blob_it.
- PBLOB_IT new_blob_it = &dummy;
- PBLOB *blob;
-
- /* Find row to process - error if selections from more than one 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_to_process == NULL) {
- block_to_process = block;
- row_to_process = row;
- }
- else {
- image_win->AddMessage("Cant resegment words "
- "in more than one row");
- return;
- }
- }
- }
- }
- }
- /* Continue with row_to_process */
-
- word_it.set_to_list(row_to_process->word_list());
- for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
- word = word_it.data();
- polyg = word->flag(W_POLYGON);
- if (word->bounding_box().overlap(selection_box)) {
- blob_it.set_to_list(word->gblob_list());
- for (blob_it.mark_cycle_pt();
- !blob_it.cycled_list(); blob_it.forward()) {
- blob = blob_it.data();
- if (gblob_bounding_box(blob, polyg).overlap(selection_box)) {
- if (new_word == NULL) {
- new_word = word->shallow_copy();
- new_blob_it.set_to_list(new_word->gblob_list());
- }
- new_blob_it.add_to_end(blob_it.extract());
- // move blob
- }
- }
- if (blob_it.empty()) { // no blobs in word
- // so delete word
- delete word_it.extract();
- }
- }
- }
- if (new_word != NULL) {
- gblob_sort_list(new_word->gblob_list(), polyg);
- word_it.add_to_end(new_word);
- word_it.sort(word_comparator);
- row_to_process->bounding_box().plot(image_win,
- ScrollView::BLACK, ScrollView::BLACK);
- word_it.set_to_list(row_to_process->word_list());
- for (word_it.mark_cycle_pt();
- !word_it.cycled_list(); word_it.forward())
- word_display(block_to_process, row_to_process, word_it.data());
- *current_image_changed = TRUE;
- }
-}
-
-/// show space stats
-void block_space_stat(BLOCK_LIST *block_list, // blocks to check
- TBOX &selection_box) {
- BLOCK_IT block_it(block_list);
- BLOCK *block;
- ROW_IT row_it;
- ROW *row;
- int block_idx = 0;
- STATS all_gap_stats(0, MAXSPACING);
- WERD_IT word_it;
- WERD *word;
- PBLOB_IT blob_it;
- PBLOB *blob;
- C_BLOB_IT cblob_it;
- C_BLOB *cblob;
- TBOX box;
- inT16 prev_box_right;
- inT16 gap_width;
- inT16 min_inter_word_gap;
- inT16 max_inter_char_gap;
-
- /* Find blocks to process */
-
- for (block_it.mark_cycle_pt();
- !block_it.cycled_list(); block_it.forward()) {
- block_idx++;
- block = block_it.data();
- if (block->bounding_box().overlap(selection_box)) {
- /* Process a block */
- tprintf("\nBlock %d\n", block_idx);
- min_inter_word_gap = 3000;
- max_inter_char_gap = 0;
- all_gap_stats.clear();
- 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();
- prev_box_right = -1;
- 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)) {
- 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();
- box = blob->bounding_box();
- if (prev_box_right > -1) {
- gap_width = box.left() - prev_box_right;
- all_gap_stats.add(gap_width, 1);
- if (blob_it.at_first()) {
- if (gap_width < min_inter_word_gap)
- min_inter_word_gap = gap_width;
- }
- else {
- if (gap_width > max_inter_char_gap)
- max_inter_char_gap = gap_width;
- }
- }
- prev_box_right = box.right();
- }
- }
- else {
- cblob_it.set_to_list(word->cblob_list());
- for (cblob_it.mark_cycle_pt();
- !cblob_it.cycled_list(); cblob_it.forward()) {
- cblob = cblob_it.data();
- box = cblob->bounding_box();
- if (prev_box_right > -1) {
- gap_width = box.left() - prev_box_right;
- all_gap_stats.add(gap_width, 1);
- if (cblob_it.at_first()) {
- if (gap_width < min_inter_word_gap)
- min_inter_word_gap = gap_width;
- }
- else {
- if (gap_width > max_inter_char_gap)
- max_inter_char_gap = gap_width;
- }
- }
- prev_box_right = box.right();
- }
- }
- }
- }
- tprintf("Max inter char gap = %d.\nMin inter word gap = %d.\n",
- max_inter_char_gap, min_inter_word_gap);
- all_gap_stats.short_print(NULL, TRUE);
- all_gap_stats.smooth(2);
- tprintf("SMOOTHED DATA...\n");
- all_gap_stats.short_print(NULL, TRUE);
- }
- }
-}
-
-/// show space stats
-void row_space_stat(BLOCK_LIST *block_list, // blocks to check
- TBOX &selection_box) {
- BLOCK_IT block_it(block_list);
- BLOCK *block;
- ROW_IT row_it;
- ROW *row;
- int block_idx = 0;
- int row_idx;
- STATS all_gap_stats(0, MAXSPACING);
- WERD_IT word_it;
- WERD *word;
- PBLOB_IT blob_it;
- PBLOB *blob;
- C_BLOB_IT cblob_it;
- C_BLOB *cblob;
- TBOX box;
- inT16 prev_box_right;
- inT16 gap_width;
- inT16 min_inter_word_gap;
- inT16 max_inter_char_gap;
-
- /* Find rows to process */
-
- for (block_it.mark_cycle_pt();
- !block_it.cycled_list(); block_it.forward()) {
- block_idx++;
- block = block_it.data();
- if (block->bounding_box().overlap(selection_box)) {
- row_it.set_to_list(block->row_list());
- row_idx = 0;
- for (row_it.mark_cycle_pt();
- !row_it.cycled_list(); row_it.forward()) {
- row_idx++;
- row = row_it.data();
- if (row->bounding_box().overlap(selection_box)) {
- /* Process a row */
-
- tprintf("\nBlock %d Row %d\n", block_idx, row_idx);
- min_inter_word_gap = 3000;
- max_inter_char_gap = 0;
- prev_box_right = -1;
- all_gap_stats.clear();
- 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)) {
- 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();
- box = blob->bounding_box();
- if (prev_box_right > -1) {
- gap_width = box.left() - prev_box_right;
- all_gap_stats.add(gap_width, 1);
- if (blob_it.at_first()) {
- if (gap_width < min_inter_word_gap)
- min_inter_word_gap = gap_width;
- }
- else {
- if (gap_width > max_inter_char_gap)
- max_inter_char_gap = gap_width;
- }
- }
- prev_box_right = box.right();
- }
- }
- else {
- cblob_it.set_to_list(word->cblob_list());
- for (cblob_it.mark_cycle_pt();
- !cblob_it.cycled_list(); cblob_it.forward()) {
- cblob = cblob_it.data();
- box = cblob->bounding_box();
- if (prev_box_right > -1) {
- gap_width = box.left() - prev_box_right;
- all_gap_stats.add(gap_width, 1);
- if (cblob_it.at_first()) {
- if (gap_width < min_inter_word_gap)
- min_inter_word_gap = gap_width;
- }
- else {
- if (gap_width > max_inter_char_gap)
- max_inter_char_gap = gap_width;
- }
- }
- prev_box_right = box.right();
- }
- }
- }
- tprintf
- ("Max inter char gap = %d.\nMin inter word gap = %d.\n",
- max_inter_char_gap, min_inter_word_gap);
- all_gap_stats.short_print(NULL, TRUE);
- all_gap_stats.smooth(2);
- tprintf("SMOOTHED DATA...\n");
- all_gap_stats.short_print(NULL, TRUE);
- }
- }
- }
- }
+void Tesseract::debug_word(PAGE_RES* page_res, const TBOX &selection_box) {
+ ResetAdaptiveClassifier();
+ recog_all_words(page_res, NULL, &selection_box, word_config_.string(), 0);
}
+} // namespace tesseract
/**
@@ -1384,87 +548,37 @@ void row_space_stat(BLOCK_LIST *block_list, // blocks to check
* row baseline
*/
-void show_point( // display posn of bloba word
- BLOCK_LIST *block_list, // blocks to check
- float x,
- float y) {
+void show_point(PAGE_RES* page_res, float x, float y) {
FCOORD pt(x, y);
- TBOX box;
- 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 cblob_it;
- C_BLOB *cblob;
+ PAGE_RES_IT pr_it(page_res);
char msg[160];
char *msg_ptr = msg;
msg_ptr += sprintf(msg_ptr, "Pt:(%0.3f, %0.3f) ", x, y);
- for (block_it.mark_cycle_pt();
- !block_it.cycled_list(); block_it.forward()) {
- block = block_it.data();
- if (block->bounding_box().contains(pt)) {
- 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().contains(pt)) {
- msg_ptr += sprintf(msg_ptr, "BL(x)=%0.3f ",
- row->base_line(x));
-
- 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();
- box = word->bounding_box();
- if (box.contains(pt)) {
- msg_ptr += sprintf(msg_ptr,
- "Wd(%d, %d)/(%d, %d) ",
- box.left(), box.bottom(),
- box.right(), box.top());
-
- if (word->flag(W_POLYGON)) {
- 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();
- box = blob->bounding_box();
- if (box.contains(pt)) {
- msg_ptr += sprintf(msg_ptr,
- "Blb(%d, %d)/(%d, %d) ",
- box.left(),
- box.bottom(),
- box.right(),
- box.top());
- }
- }
- }
- else {
- cblob_it.set_to_list(word->cblob_list());
- for (cblob_it.mark_cycle_pt();
- !cblob_it.cycled_list();
- cblob_it.forward()) {
- cblob = cblob_it.data();
- box = cblob->bounding_box();
- if (box.contains(pt)) {
- msg_ptr += sprintf(msg_ptr,
- "CBlb(%d, %d)/(%d, %d) ",
- box.left(),
- box.bottom(),
- box.right(),
- box.top());
- }
- }
- }
- }
- }
+ for (WERD_RES* word = pr_it.word(); word != NULL; word = pr_it.forward()) {
+ if (pr_it.row() != pr_it.prev_row() &&
+ pr_it.row()->row->bounding_box().contains(pt)) {
+ msg_ptr += sprintf(msg_ptr, "BL(x)=%0.3f ",
+ pr_it.row()->row->base_line(x));
+ }
+ if (word->word->bounding_box().contains(pt)) {
+ TBOX box = word->word->bounding_box();
+ msg_ptr += sprintf(msg_ptr, "Wd(%d, %d)/(%d, %d) ",
+ box.left(), box.bottom(),
+ box.right(), box.top());
+ C_BLOB_IT cblob_it(word->word->cblob_list());
+ for (cblob_it.mark_cycle_pt();
+ !cblob_it.cycled_list();
+ cblob_it.forward()) {
+ C_BLOB* cblob = cblob_it.data();
+ box = cblob->bounding_box();
+ if (box.contains(pt)) {
+ msg_ptr += sprintf(msg_ptr,
+ "CBlb(%d, %d)/(%d, %d) ",
+ box.left(), box.bottom(),
+ box.right(), box.top());
}
}
}
@@ -1492,144 +606,48 @@ void show_point( // display posn of bloba word
* Blank display of word then redisplay word according to current display mode
* settings
*/
-
-BOOL8 word_blank_and_set_display( // display a word
- BLOCK *block, // block holding word
- ROW *row, // row holding word
- WERD *word // word to be processed
- ) {
- word->bounding_box().plot(image_win, ScrollView::BLACK, ScrollView::BLACK);
- return word_set_display(block, row, word);
+namespace tesseract {
+BOOL8 Tesseract:: word_blank_and_set_display(BLOCK* block, ROW* row,
+ WERD_RES* word_res) {
+ word_res->word->bounding_box().plot(image_win, ScrollView::BLACK,
+ ScrollView::BLACK);
+ return word_set_display(block, row, word_res);
}
/**
* word_bln_display()
*
- * Normalise word and display in word window
+ * Normalize word and display in word window
*/
-
-BOOL8 word_bln_display( // bln & display
- BLOCK *, // block holding word
- ROW *row, // row holding word
- WERD *word // word to be processed
- ) {
- WERD *bln_word;
-
- bln_word = word->poly_copy(row->x_height());
- bln_word->baseline_normalise(row);
+BOOL8 Tesseract::word_bln_display(BLOCK* block, ROW* row, WERD_RES* word_res) {
+ TWERD *bln_word = word_res->chopped_word;
+ if (bln_word == NULL) {
+ word_res->SetupForRecognition(unicharset, false, row, block);
+ bln_word = word_res->chopped_word;
+ }
bln_word_window_handle()->Clear();
display_bln_lines(bln_word_window_handle(), ScrollView::CYAN,
1.0, 0.0f, -1000.0f, 1000.0f);
- bln_word->plot(bln_word_window_handle(), ScrollView::RED);
- delete bln_word;
- return TRUE;
-}
-
-
-/**
- * word_change_text()
- *
- * Change the correct text of a word
- */
-
-BOOL8 word_change_text( // change correct text
- BLOCK *block, // block holding word
- ROW *row, // row holding word
- WERD *word // word to be processed
- ) {
- char* cp = image_win->ShowInputDialog(
- "Enter/edit the correct text and press <>");
- word->set_text(cp);
- delete[] cp;
-
- if (word_display_mode.bit(DF_TEXT) || word->display_flag(DF_TEXT)) {
- word_blank_and_set_display(block, row, word);
- ScrollView::Update();
- }
-
- *current_image_changed = TRUE;
+ bln_word->plot(bln_word_window_handle());
+ bln_word_window_handle()->Update();
return TRUE;
}
-/**
- * word_copy()
- *
- * Copy a word to other display list
- */
-
-BOOL8 word_copy( // copy a word
- BLOCK *block, // block holding word
- ROW *row, // row holding word
- WERD *word // word to be processed
- ) {
- WERD *copy_word = new WERD;
-
- *copy_word = *word;
- add_word(copy_word, row, block, other_block_list);
- *other_image_changed = TRUE;
- return TRUE;
-}
-
-
-/**
- * word_delete()
- *
- * Delete a word
- */
-
-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
- ) {
- word_it.extract();
- word->bounding_box().plot(image_win, ScrollView::BLACK, ScrollView::BLACK);
- delete(word);
-
- if (word_it.empty()) { // no words left in row
- // so delete row
- row_it.extract();
- row->bounding_box().plot(image_win, ScrollView::BLACK, ScrollView::BLACK);
- delete(row);
-
- if (row_it.empty()) { // no rows left in blk
- // so delete block
- block_it.extract();
- block->bounding_box().plot(image_win, ScrollView::BLACK, ScrollView::BLACK);
- delete(block);
- }
- }
- *current_image_changed = TRUE;
- return TRUE;
-}
-
/**
* word_display() Word Processor
*
* Display a word according to its display modes
*/
-
-BOOL8 word_display( // display a word
- BLOCK *, // block holding word
- ROW *row, // row holding word
- WERD *word // word to be processed
- ) {
+BOOL8 Tesseract::word_display(BLOCK* block, ROW* row, WERD_RES* word_res) {
+ WERD* word = word_res->word;
TBOX word_bb; // word bounding box
int word_height; // ht of word BB
BOOL8 displayed_something = FALSE;
- BOOL8 displayed_rainbow = FALSE;
float shift; // from bot left
- PBLOB_IT it; // blob iterator
C_BLOB_IT c_it; // cblob iterator
- WERD *word_ptr; // poly copy
- WERD temp_word;
- float scale_factor; // for BN_POLYGON
/*
Note the double coercions of(COLOUR)((inT32)editor_image_word_bb_color)
@@ -1647,88 +665,29 @@ BOOL8 word_display( // display a word
ScrollView::Color c = (ScrollView::Color)
((inT32) editor_image_blob_bb_color);
image_win->Pen(c);
- if (word->flag(W_POLYGON)) {
- it.set_to_list(word->blob_list());
- for (it.mark_cycle_pt(); !it.cycled_list(); it.forward())
- it.data()->bounding_box().plot(image_win);
- }
- else {
- c_it.set_to_list(word->cblob_list());
- for (c_it.mark_cycle_pt(); !c_it.cycled_list(); c_it.forward())
- c_it.data()->bounding_box().plot(image_win);
- }
+ c_it.set_to_list(word->cblob_list());
+ for (c_it.mark_cycle_pt(); !c_it.cycled_list(); c_it.forward())
+ c_it.data()->bounding_box().plot(image_win);
displayed_something = TRUE;
}
// display edge steps
- if (word->display_flag(DF_EDGE_STEP) &&
- !word->flag(W_POLYGON)) { // edgesteps available
+ if (word->display_flag(DF_EDGE_STEP)) { // edgesteps available
word->plot(image_win); // rainbow colors
displayed_something = TRUE;
- displayed_rainbow = TRUE;
}
// display poly approx
if (word->display_flag(DF_POLYGONAL)) {
// need to convert
- if (!word->flag(W_POLYGON)) {
- word_ptr = word->poly_copy(row->x_height());
-
- /* CALL POLYGONAL APPROXIMATOR WHEN AVAILABLE - on a temp_word */
-
- if (displayed_rainbow)
- // ensure its visible
- word_ptr->plot(image_win, ScrollView::WHITE);
- else
- // rainbow colors
- word_ptr->plot(image_win);
- delete word_ptr;
- }
- else {
- if (displayed_rainbow)
- // ensure its visible
- word->plot(image_win, ScrollView::WHITE);
- else
- word->plot(image_win); // rainbow colors
- }
-
- displayed_rainbow = TRUE;
- displayed_something = TRUE;
- }
-
- // disp BN poly approx
- if (word->display_flag(DF_BN_POLYGONAL)) {
- // need to convert
- if (!word->flag(W_POLYGON)) {
- word_ptr = word->poly_copy(row->x_height());
- temp_word = *word_ptr;
- delete word_ptr;
-
- /* CALL POLYGONAL APPROXIMATOR WHEN AVAILABLE - on a temp_word */
-
- }
- else
- temp_word = *word; // copy word
- word_bb = word->bounding_box();
- if (!temp_word.flag(W_NORMALIZED))
- temp_word.baseline_normalise(row);
-
- scale_factor = re_scale_and_move_bln_word(&temp_word, word_bb);
- display_bln_lines(image_win, ScrollView::CYAN, scale_factor,
- word_bb.bottom(), word_bb.left(), word_bb.right());
-
- if (displayed_rainbow)
- // ensure its visible
- temp_word.plot(image_win, ScrollView::WHITE);
- else
- temp_word.plot(image_win); // rainbow colors
-
- displayed_rainbow = TRUE;
+ TWERD* tword = TWERD::PolygonalCopy(word);
+ tword->plot(image_win);
+ delete tword;
displayed_something = TRUE;
}
// display correct text
- if (word->display_flag(DF_TEXT)) {
+ if (word->display_flag(DF_TEXT) && word->text() != NULL) {
word_bb = word->bounding_box();
ScrollView::Color c =(ScrollView::Color)
((inT32) editor_image_blob_bb_color);
@@ -1756,19 +715,12 @@ BOOL8 word_display( // display a word
return TRUE;
}
-
/**
* word_dumper()
*
* Dump members to the debug window
*/
-
-BOOL8 word_dumper( // dump word
- BLOCK *block, //< block holding word
- ROW *row, //< row holding word
- WERD *word //< word to be processed
- ) {
-
+BOOL8 Tesseract::word_dumper(BLOCK* block, ROW* row, WERD_RES* word_res) {
if (block != NULL) {
tprintf("\nBlock data...\n");
block->print(NULL, FALSE);
@@ -1776,7 +728,7 @@ BOOL8 word_dumper( // dump word
tprintf("\nRow data...\n");
row->print(NULL);
tprintf("\nWord data...\n");
- word->print(NULL);
+ word_res->word->print();
return TRUE;
}
@@ -1786,46 +738,17 @@ BOOL8 word_dumper( // dump word
*
* Display word according to current display mode settings
*/
-
-BOOL8 word_set_display( // display a word
- BLOCK *block, //< block holding word
- ROW *row, //< row holding word
- WERD *word //< word to be processed
- ) {
- TBOX word_bb; // word bounding box
-
+BOOL8 Tesseract::word_set_display(BLOCK* block, ROW* row, WERD_RES* word_res) {
+ WERD* word = word_res->word;
word->set_display_flag(DF_BOX, word_display_mode.bit(DF_BOX));
word->set_display_flag(DF_TEXT, word_display_mode.bit(DF_TEXT));
word->set_display_flag(DF_POLYGONAL, word_display_mode.bit(DF_POLYGONAL));
word->set_display_flag(DF_EDGE_STEP, word_display_mode.bit(DF_EDGE_STEP));
word->set_display_flag(DF_BN_POLYGONAL,
word_display_mode.bit(DF_BN_POLYGONAL));
- *current_image_changed = TRUE;
- return word_display(block, row, word);
+ return word_display(block, row, word_res);
}
+} // namespace tesseract
-/**
- * word_toggle_seg()
- *
- * Toggle the correct segmentation flag
- */
-
-BOOL8 word_toggle_seg( // toggle seg flag
- BLOCK *, //< block holding word
- ROW *, //< row holding word
- WERD *word //< word to be processed
- ) {
- word->set_flag(W_SEGMENTED, !word->flag(W_SEGMENTED));
- *current_image_changed = TRUE;
- return TRUE;
-}
-
#endif // GRAPHICS_DISABLED
-
-/* DEBUG ONLY */
-
-void do_check_mem( // do it
- inT32 level) {
- check_mem("Doing it", level);
-}
diff --git a/ccmain/pgedit.h b/ccmain/pgedit.h
index 2cfcd1000d..667be1bb46 100755
--- a/ccmain/pgedit.h
+++ b/ccmain/pgedit.h
@@ -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
diff --git a/ccmain/recogtraining.cpp b/ccmain/recogtraining.cpp
new file mode 100644
index 0000000000..8c4b7e1ce3
--- /dev/null
+++ b/ccmain/recogtraining.cpp
@@ -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(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(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(tbox.right(), bbox.right(), kMaxBoxEdgeDiff) &&
+ NearlyEqual(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
diff --git a/ccmain/reject.cpp b/ccmain/reject.cpp
index b893efd195..69f091e039 100644
--- a/ccmain/reject.cpp
+++ b/ccmain/reject.cpp
@@ -32,24 +32,19 @@
#include "scanutils.h"
#include
#include
-//#include "tessbox.h"
#include "memry.h"
#include "reject.h"
#include "tfacep.h"
-#include "mainblk.h"
#include "charcut.h"
#include "imgs.h"
-#include "scaleimg.h"
#include "control.h"
#include "docqual.h"
#include "secname.h"
#include "globals.h"
+#include "helpers.h"
/* #define SECURE_NAMES done in secnames.h when necessary */
-//extern "C" {
-#include "callnet.h"
-//}
#include "tesseractclass.h"
#include "notdll.h"
@@ -59,151 +54,6 @@
#endif
CLISTIZEH (STRING) CLISTIZE (STRING)
-#define EXTERN
-EXTERN
-INT_VAR (tessedit_reject_mode, 0, "Rejection algorithm");
-EXTERN
-INT_VAR (tessedit_ok_mode, 5, "Acceptance decision algorithm");
-EXTERN
-BOOL_VAR (tessedit_use_nn, FALSE, "");
-EXTERN
-BOOL_VAR (tessedit_rejection_debug, FALSE, "Adaption debug");
-EXTERN
-BOOL_VAR (tessedit_rejection_stats, FALSE, "Show NN stats");
-EXTERN
-BOOL_VAR (tessedit_flip_0O, TRUE, "Contextual 0O O0 flips");
-EXTERN
-double_VAR (tessedit_lower_flip_hyphen, 1.5,
-"Aspect ratio dot/hyphen test");
-EXTERN
-double_VAR (tessedit_upper_flip_hyphen, 1.8,
-"Aspect ratio dot/hyphen test");
-
-EXTERN
-BOOL_VAR (rej_trust_doc_dawg, FALSE,
-"Use DOC dawg in 11l conf. detector");
-EXTERN
-BOOL_VAR (rej_1Il_use_dict_word, FALSE, "Use dictword test");
-EXTERN
-BOOL_VAR (rej_1Il_trust_permuter_type, TRUE, "Dont double check");
-
-EXTERN
-BOOL_VAR (one_ell_conflict_default, TRUE, "one_ell_conflict default");
-EXTERN
-BOOL_VAR (show_char_clipping, FALSE, "Show clip image window?");
-EXTERN
-BOOL_VAR (nn_debug, FALSE, "NN DEBUGGING?");
-EXTERN
-BOOL_VAR (nn_reject_debug, FALSE, "NN DEBUG each char?");
-EXTERN
-BOOL_VAR (nn_lax, FALSE, "Use 2nd rate matches");
-EXTERN
-BOOL_VAR (nn_double_check_dict, FALSE, "Double check");
-EXTERN
-BOOL_VAR (nn_conf_double_check_dict, TRUE,
-"Double check for confusions");
-EXTERN
-BOOL_VAR (nn_conf_1Il, TRUE, "NN use 1Il conflicts");
-EXTERN
-BOOL_VAR (nn_conf_Ss, TRUE, "NN use Ss conflicts");
-EXTERN
-BOOL_VAR (nn_conf_hyphen, TRUE, "NN hyphen conflicts");
-EXTERN
-BOOL_VAR (nn_conf_test_good_qual, FALSE, "NN dodgy 1Il cross check");
-EXTERN
-BOOL_VAR (nn_conf_test_dict, TRUE, "NN dodgy 1Il cross check");
-EXTERN
-BOOL_VAR (nn_conf_test_sensible, TRUE, "NN dodgy 1Il cross check");
-EXTERN
-BOOL_VAR (nn_conf_strict_on_dodgy_chs, TRUE,
-"Require stronger NN match");
-EXTERN
-double_VAR (nn_dodgy_char_threshold, 0.99, "min accept score");
-EXTERN
-INT_VAR (nn_conf_accept_level, 4, "NN accept dodgy 1Il matches? ");
-EXTERN
-INT_VAR (nn_conf_initial_i_level, 3,
-"NN accept initial Ii match level ");
-
-EXTERN
-BOOL_VAR (no_unrej_dubious_chars, TRUE, "Dubious chars next to reject?");
-EXTERN
-BOOL_VAR (no_unrej_no_alphanum_wds, TRUE, "Stop unrej of non A/N wds?");
-EXTERN
-BOOL_VAR (no_unrej_1Il, FALSE, "Stop unrej of 1Ilchars?");
-EXTERN
-BOOL_VAR (rej_use_tess_accepted, TRUE, "Individual rejection control");
-EXTERN
-BOOL_VAR (rej_use_tess_blanks, TRUE, "Individual rejection control");
-EXTERN
-BOOL_VAR (rej_use_good_perm, TRUE, "Individual rejection control");
-EXTERN
-BOOL_VAR (rej_use_sensible_wd, FALSE, "Extend permuter check");
-EXTERN
-BOOL_VAR (rej_alphas_in_number_perm, FALSE, "Extend permuter check");
-
-EXTERN
-double_VAR (rej_whole_of_mostly_reject_word_fract, 0.85,
-"if >this fract");
-EXTERN
-INT_VAR (rej_mostly_reject_mode, 1,
-"0-never, 1-afterNN, 2-after new xht");
-EXTERN
-double_VAR (tessed_fullstop_aspect_ratio, 1.2,
-"if >this fract then reject");
-
-EXTERN
-INT_VAR (net_image_width, 40, "NN input image width");
-EXTERN
-INT_VAR (net_image_height, 36, "NN input image height");
-EXTERN
-INT_VAR (net_image_x_height, 22, "NN input image x_height");
-EXTERN
-INT_VAR (tessedit_image_border, 2, "Rej blbs near image edge limit");
-
-/*
- Net input is assumed to have (net_image_width * net_image_height) input
- units of image pixels, followed by 0, 1, or N units representing the
- baseline position. 0 implies no baseline information. 1 implies a floating
- point value. N implies a "guage" of N units. For any char an initial set
- of these are ON, the remainder OFF to indicate the "level" of the
- baseline.
-
- HOWEVER!!! NOTE THAT EACH NEW INPUT LAYER FORMAT EXPECTS TO BE RUN WITH A
- DIFFERENT tessed/netmatch/nmatch.c MODULE. - These are classic C modules
- generated by aspirin with HARD CODED CONSTANTS
-*/
-
-EXTERN
-INT_VAR (net_bl_nodes, 20, "Number of baseline nodes");
-
-EXTERN
-double_VAR (nn_reject_threshold, 0.5, "NN min accept score");
-EXTERN
-double_VAR (nn_reject_head_and_shoulders, 0.6, "top scores sep factor");
-
-/* NOTE - ctoh doesn't handle "=" properly, hence \075 */
-EXTERN
-STRING_VAR (ok_single_ch_non_alphanum_wds, "-?\075",
-"Allow NN to unrej");
-EXTERN
-STRING_VAR (ok_repeated_ch_non_alphanum_wds, "-?*\075",
-"Allow NN to unrej");
-EXTERN
-STRING_VAR (conflict_set_I_l_1, "Il1[]", "Il1 conflict set");
-EXTERN
-STRING_VAR (conflict_set_S_s, "Ss$", "Ss conflict set");
-EXTERN
-STRING_VAR (conflict_set_hyphen, "-_~", "hyphen conflict set");
-EXTERN
-STRING_VAR (dubious_chars_left_of_reject, "!'+`()-./\\<>;:^_,~\"",
-"Unreliable chars");
-EXTERN
-STRING_VAR (dubious_chars_right_of_reject, "!'+`()-./\\<>;:^_,~\"",
-"Unreliable chars");
-
-EXTERN
-INT_VAR (min_sane_x_ht_pixels, 8, "Reject any x-ht lt or eq than this");
/*************************************************************************
* set_done()
@@ -370,7 +220,7 @@ void Tesseract::make_reject_map( //make rej map for wd //detailed results
and the whole of any words which are very small
*/
else if (tessedit_reject_mode == 5) {
- if (bln_x_height / word->denorm.scale () <= min_sane_x_ht_pixels)
+ if (kBlnXHeight / word->denorm.scale () <= min_sane_x_ht_pixels)
word->reject_map.rej_word_small_xht ();
else {
one_ell_conflict(word, TRUE);
@@ -437,11 +287,6 @@ void Tesseract::make_reject_map( //make rej map for wd //detailed results
tprintf("Dict word: %d\n", dict_word(*(word->best_choice)));
}
- /* Un-reject any rejected characters if NN permits */
-
- if (tessedit_use_nn && (pass == 2) &&
- word->reject_map.recoverable_rejects ())
- nn_recover_rejects(word, row);
flip_hyphens(word);
check_debug_pt (word, 20);
}
@@ -460,8 +305,8 @@ void reject_blanks(WERD_RES *word) {
}
}
-
-void reject_I_1_L(WERD_RES *word) {
+namespace tesseract {
+void Tesseract::reject_I_1_L(WERD_RES *word) {
inT16 i;
inT16 offset;
@@ -474,6 +319,7 @@ void reject_I_1_L(WERD_RES *word) {
}
}
}
+} // namespace tesseract
void reject_poor_matches( //detailed results
@@ -493,12 +339,12 @@ void reject_poor_matches( //detailed results
("ASSERT FAIL string:\"%s\"; strlen=%d; choices len=%d; blob len=%d\n",
word->best_choice->unichar_string().string(),
strlen (word->best_choice->unichar_lengths().string()), list_it.length(),
- word->outword->blob_list()->length());
+ word->box_word->length());
}
#endif
ASSERT_HOST (strlen (word->best_choice->unichar_lengths().string ()) ==
list_it.length ());
- ASSERT_HOST (word->outword->blob_list ()->length () == list_it.length ());
+ ASSERT_HOST(word->box_word->length() == list_it.length());
threshold = compute_reject_threshold (blob_choices);
for (list_it.mark_cycle_pt ();
@@ -583,47 +429,36 @@ float compute_reject_threshold( //compute threshold //detailed results
* If the word is perilously close to the edge of the image, reject those blobs
* in the word which are too close to the edge as they could be clipped.
*************************************************************************/
-
-void reject_edge_blobs(WERD_RES *word) {
- TBOX word_box = word->word->bounding_box ();
- TBOX blob_box;
- PBLOB_IT blob_it = word->outword->blob_list ();
- //blobs
- int blobindex = 0;
- float centre;
-
- if ((word_box.left () < tessedit_image_border) ||
- (word_box.bottom () < tessedit_image_border) ||
- (word_box.right () + tessedit_image_border >
- page_image.get_xsize () - 1) ||
- (word_box.top () + tessedit_image_border > page_image.get_ysize () - 1)) {
- ASSERT_HOST (word->reject_map.length () == blob_it.length ());
- for (blobindex = 0, blob_it.mark_cycle_pt ();
- !blob_it.cycled_list (); blobindex++, blob_it.forward ()) {
- blob_box = blob_it.data ()->bounding_box ();
- centre = (blob_box.left () + blob_box.right ()) / 2.0;
- if ((word->denorm.x (blob_box.left ()) < tessedit_image_border) ||
- (word->denorm.y (blob_box.bottom (), centre) <
- tessedit_image_border) ||
- (word->denorm.x (blob_box.right ()) + tessedit_image_border >
- page_image.get_xsize () - 1) ||
- (word->denorm.y (blob_box.top (), centre)
- + tessedit_image_border > page_image.get_ysize () - 1)) {
- word->reject_map[blobindex].setrej_edge_char ();
- //close to edge
+namespace tesseract {
+void Tesseract::reject_edge_blobs(WERD_RES *word) {
+ TBOX word_box = word->word->bounding_box();
+ // Use the box_word as it is already denormed back to image coordinates.
+ int blobcount = word->box_word->length();
+
+ if (word_box.left() < tessedit_image_border ||
+ word_box.bottom() < tessedit_image_border ||
+ word_box.right() + tessedit_image_border > ImageWidth() - 1 ||
+ word_box.top() + tessedit_image_border > ImageHeight() - 1) {
+ ASSERT_HOST(word->reject_map.length() == blobcount);
+ for (int blobindex = 0; blobindex < blobcount; blobindex++) {
+ TBOX blob_box = word->box_word->BlobBox(blobindex);
+ if (blob_box.left() < tessedit_image_border ||
+ blob_box.bottom() < tessedit_image_border ||
+ blob_box.right() + tessedit_image_border > ImageWidth() - 1 ||
+ blob_box.top() + tessedit_image_border > ImageHeight() - 1) {
+ word->reject_map[blobindex].setrej_edge_char();
+ // Close to edge
}
}
}
}
-
/**********************************************************************
* one_ell_conflict()
*
* Identify words where there is a potential I/l/1 error.
* - A bundle of contextual heuristics!
**********************************************************************/
-namespace tesseract {
BOOL8 Tesseract::one_ell_conflict(WERD_RES *word_res, BOOL8 update_map) {
const char *word;
const char *lengths;
@@ -868,548 +703,27 @@ BOOL8 Tesseract::test_ambig_word( //test for ambiguity
return ambig;
}
-/*************************************************************************
- * char_ambiguities()
- *
- * Return a pointer to a string containing the full conflict set of characters
- * which includes the specified character, if there is one. If the specified
- * character is not a member of a conflict set, return NULL.
- * (NOTE that a character is assumed to be a member of only ONE conflict set.)
- *************************************************************************/
-const char *Tesseract::char_ambiguities(char c) {
- static STRING_CLIST conflict_sets;
- static BOOL8 read_conflict_sets = FALSE;
- STRING_C_IT cs_it(&conflict_sets);
- const char *cs;
- STRING cs_file_name;
- FILE *cs_file;
- char buff[1024];
-
- if (!read_conflict_sets) {
- cs_file_name = datadir + "confsets";
- if (!(cs_file = fopen (cs_file_name.string (), "r"))) {
- CANTOPENFILE.error ("char_ambiguities", EXIT, "%s %d",
- cs_file_name.string (), errno);
- }
- while (fscanf (cs_file, "%s", buff) == 1) {
- cs_it.add_after_then_move (new STRING (buff));
- }
- fclose (cs_file);
- read_conflict_sets = TRUE;
- cs_it.move_to_first ();
- if (tessedit_rejection_debug) {
- for (cs_it.mark_cycle_pt ();
- !cs_it.cycled_list (); cs_it.forward ()) {
- tprintf ("\"%s\"\n", cs_it.data ()->string ());
- }
- }
- }
-
- cs_it.move_to_first ();
- for (cs_it.mark_cycle_pt (); !cs_it.cycled_list (); cs_it.forward ()) {
- cs = cs_it.data ()->string ();
- if (strchr (cs, c) != NULL)
- return cs;
- }
- return NULL;
-}
-
-/*************************************************************************
- * nn_recover_rejects()
- * Generate the nn_reject_map - a copy of the current reject map, but dont
- * reject previously rejected chars if the NN matcher agrees with the best
- * choice.
- *************************************************************************/
-
-void Tesseract::nn_recover_rejects(WERD_RES *word, ROW *row) {
- //copy for debug
- REJMAP old_map = word->reject_map;
- /*
- NOTE THAT THIS IS RELATIVELY INEFFICIENT AS THE WHOLE OF THE WERD IS
- MATCHED BY THE NN MATCHER. IF COULD EASILY BE RESTRICTED TO JUST THE
- REJECT CHARACTERS (Though initial use is when words are total rejects
- anyway).
- */
-
- set_global_subsubloc_code(SUBSUBLOC_NN);
- nn_match_word(word, row);
-
- if (no_unrej_1Il)
- dont_allow_1Il(word);
- if (no_unrej_dubious_chars)
- dont_allow_dubious_chars(word);
-
- if (rej_mostly_reject_mode == 1)
- reject_mostly_rejects(word);
- /*
- IF there are no unrejected alphanumerics AND
- The word is not an acceptable single non alphanum char word AND
- The word is not an acceptable repeated non alphanum char word
- THEN Reject whole word
- */
- if (no_unrej_no_alphanum_wds &&
- (count_alphanums (word) < 1) &&
- !((word->best_choice->unichar_lengths().length () == 1) &&
- STRING(ok_single_ch_non_alphanum_wds).contains(
- word->best_choice->unichar_string()[0]))
- && !repeated_nonalphanum_wd (word, row))
-
- word->reject_map.rej_word_no_alphanums ();
-
- #ifndef SECURE_NAMES
-
- if (nn_debug) {
- tprintf ("\nTess: \"%s\" MAP ",
- word->best_choice->unichar_string().string());
- old_map.print (stdout);
- tprintf ("->");
- word->reject_map.print (stdout);
- tprintf ("\n");
- }
- #endif
- set_global_subsubloc_code(SUBSUBLOC_OTHER);
-}
-
-void Tesseract::nn_match_word( //Match a word
- WERD_RES *word,
- ROW *row) {
- PIXROW_LIST *pixrow_list;
- PIXROW_IT pixrow_it;
- IMAGELINE *imlines; //lines of the image
- TBOX pix_box; //box of imlines extent
-#ifndef GRAPHICS_DISABLED
- ScrollView* win = NULL;
-#endif
- IMAGE clip_image;
- IMAGE scaled_image;
- float baseline_pos;
- inT16 net_image_size;
- inT16 clip_image_size;
- WERD copy_outword; // copy to denorm
- inT16 i;
-
- const char *word_string;
- const char *word_string_lengths;
- BOOL8 word_in_dict; //Tess wd in dict
- BOOL8 checked_dict_word; //Tess wd definitely in dict
- BOOL8 sensible_word; //OK char string
- BOOL8 centre; //Not at word end chs
- BOOL8 good_quality_word;
- inT16 char_quality;
- inT16 accepted_char_quality;
-
- inT16 conf_level; //0:REJECT
- //1:DODGY ACCEPT
- //2:DICT ACCEPT
- //3:CLEAR ACCEPT
- inT16 first_alphanum_index_;
- inT16 first_alphanum_offset_;
-
- word_string = word->best_choice->unichar_string().string();
- word_string_lengths = word->best_choice->unichar_lengths().string();
- first_alphanum_index_ = first_alphanum_index (word_string,
- word_string_lengths);
- first_alphanum_offset_ = first_alphanum_offset (word_string,
- word_string_lengths);
- word_in_dict = ((word->best_choice->permuter () == SYSTEM_DAWG_PERM) ||
- (word->best_choice->permuter () == FREQ_DAWG_PERM) ||
- (word->best_choice->permuter () == USER_DAWG_PERM));
- checked_dict_word = word_in_dict &&
- (safe_dict_word(*(word->best_choice)) > 0);
- sensible_word = acceptable_word_string (word_string, word_string_lengths) !=
- AC_UNACCEPTABLE;
-
- word_char_quality(word, row, &char_quality, &accepted_char_quality);
- good_quality_word =
- word->best_choice->unichar_lengths().length () == char_quality;
-
- #ifndef SECURE_NAMES
- if (nn_reject_debug) {
- tprintf ("Dict: %c Checked Dict: %c Sensible: %c Quality: %c\n",
- word_in_dict ? 'T' : 'F',
- checked_dict_word ? 'T' : 'F',
- sensible_word ? 'T' : 'F', good_quality_word ? 'T' : 'F');
- }
- #endif
-
- if (word->best_choice->unichar_lengths().length () !=
- word->outword->blob_list ()->length ()) {
- #ifndef SECURE_NAMES
- tprintf ("nn_match_word ASSERT FAIL String:\"%s\"; #Blobs=%d\n",
- word->best_choice->unichar_string().string (),
- word->outword->blob_list ()->length ());
- #endif
- err_exit();
- }
-
- copy_outword = *(word->outword);
- copy_outword.baseline_denormalise (&word->denorm);
- /*
- For each character, generate and match a new image, containing JUST the
- character we have clipped, centered in the image, on a white background.
- Note that we MUST have a square image so that we can scale it uniformly in
- x and y. We base the size on x_height as this can be found fairly reliably.
- */
- net_image_size = (net_image_width > net_image_height) ?
- net_image_width : net_image_height;
- clip_image_size = (inT16) floor (0.5 +
- net_image_size * word->x_height /
- net_image_x_height);
- if ((clip_image_size <= 1) || (net_image_size <= 1)) {
- return;
- }
-
- /*
- Get the image of the word and the pix positions of each char
- */
- char_clip_word(©_outword, page_image, pixrow_list, imlines, pix_box);
-#ifndef GRAPHICS_DISABLED
- if (show_char_clipping) {
- win = display_clip_image (©_outword, page_image,
- pixrow_list, pix_box);
- }
-#endif
- pixrow_it.set_to_list (pixrow_list);
- pixrow_it.move_to_first ();
- for (pixrow_it.mark_cycle_pt (), i = 0;
- !pixrow_it.cycled_list (); pixrow_it.forward (), i++) {
- if (pixrow_it.data ()->
- bad_box (page_image.get_xsize (), page_image.get_ysize ()))
- continue;
- clip_image.create (clip_image_size, clip_image_size, 1);
- //make bin imge
- if (!copy_outword.flag (W_INVERSE))
- invert_image(&clip_image); //white background for black on white
- pixrow_it.data ()->char_clip_image (imlines, pix_box, row,
- clip_image, baseline_pos);
- if (copy_outword.flag (W_INVERSE))
- invert_image(&clip_image); //invert white on black for scaling &NN
- scaled_image.create (net_image_size, net_image_size, 1);
- scale_image(clip_image, scaled_image);
- baseline_pos *= net_image_size / clip_image_size;
- //scale with im
- centre = !pixrow_it.at_first () && !pixrow_it.at_last ();
-
- conf_level = nn_match_char (scaled_image, baseline_pos,
- word_in_dict, checked_dict_word,
- sensible_word, centre,
- good_quality_word, word_string[i]);
- if (word->reject_map[i].recoverable ()) {
- if ((i == first_alphanum_index_) &&
- word_string_lengths[first_alphanum_index_] == 1 &&
- ((word_string[first_alphanum_offset_] == 'I') ||
- (word_string[first_alphanum_offset_] == 'i'))) {
- if (conf_level >= nn_conf_initial_i_level)
- word->reject_map[i].setrej_nn_accept ();
- //un-reject char
- }
- else if (conf_level > 0)
- //un-reject char
- word->reject_map[i].setrej_nn_accept ();
- }
-#ifndef GRAPHICS_DISABLED
- if (show_char_clipping)
- display_images(clip_image, scaled_image);
-#endif
- clip_image.destroy();
- scaled_image.destroy();
- }
-
- delete[]imlines; // Free array of imlines
- delete pixrow_list;
-
-#ifndef GRAPHICS_DISABLED
- if (show_char_clipping) {
-// destroy_window(win);
-// win->Destroy();
- delete win;
- }
-#endif
-}
-} // namespace tesseract
-
-
-/*************************************************************************
- * nn_match_char()
- * Call Neural Net matcher to match a single character, given a scaled,
- * square image
- *************************************************************************/
-
-inT16 nn_match_char( //of character
- 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 conf_level; //0..2
- inT32 row;
- inT32 col;
- inT32 y_size = scaled_image.get_ysize ();
- inT32 start_y = y_size - (y_size - net_image_height) / 2 - 1;
- inT32 end_y = start_y - net_image_height + 1;
- IMAGELINE imline;
- float *input_vector;
- float *input_vec_ptr;
- char top;
- float top_score;
- char next;
- float next_score;
- inT16 input_nodes = (net_image_height * net_image_width) + net_bl_nodes;
- inT16 j;
-
- input_vector = (float *) alloc_mem (input_nodes * sizeof (float));
- input_vec_ptr = input_vector;
-
- invert_image(&scaled_image); //cos nns work better
- for (row = start_y; row >= end_y; row--) {
- scaled_image.fast_get_line (0, row, net_image_width, &imline);
- for (col = 0; col < net_image_width; col++)
- *input_vec_ptr++ = imline.pixels[col];
- }
- /*
- The bit map presented to the net may be shorter than the image, so shift
- the coord to be relative to the bitmap portion.
- */
- baseline_pos -= (y_size - net_image_height) / 2.0;
- /*
- Baseline pos is 0 if below bitmap, 1 if above and in proportion otherwise.
- This is represented to the net as a set of bl_nodes, an initial proportion
- of which are set to 1.0, indicating the level of the baseline. The
- remainder are 0.0
- */
-
- if (baseline_pos < 0)
- baseline_pos = 0;
- else if (baseline_pos >= net_image_height)
- baseline_pos = net_image_height + 1;
- else
- baseline_pos = baseline_pos + 1;
- baseline_pos = baseline_pos / (net_image_height + 1);
-
- if (net_bl_nodes > 0) {
- baseline_pos *= 1.7; //Use a wider range
- if (net_bl_nodes > 1) {
- /* Multi-node baseline representation */
- for (j = 0; j < net_bl_nodes; j++) {
- if (baseline_pos > ((float) j / net_bl_nodes))
- *input_vec_ptr++ = 1.0;
- else
- *input_vec_ptr++ = 0.0;
- }
- }
- else {
- /* Single node baseline */
- *input_vec_ptr++ = baseline_pos;
- }
- }
-
- callnet(input_vector, &top, &top_score, &next, &next_score);
- conf_level = evaluate_net_match (top, top_score, next, next_score,
- tess_ch, dict_word, checked_dict_word,
- sensible_word, centre, good_quality_word);
- #ifndef SECURE_NAMES
- if (nn_reject_debug) {
- tprintf ("top:\"%c\" %4.2f next:\"%c\" %4.2f TESS:\"%c\" Conf: %d\n",
- top, top_score, next, next_score, tess_ch, conf_level);
- }
- #endif
- free_mem(input_vector);
- return conf_level;
-}
-
-
-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) {
- inT16 accept_level; //0 Very clearly matched
- //1 Clearly top
- //2 Top but poor match
- //3 Next & poor top match
- //4 Next but good top match
- //5 No chance
- BOOL8 good_top_choice;
- BOOL8 excellent_top_choice;
- BOOL8 confusion_match = FALSE;
- BOOL8 dodgy_char = !isalnum (tess_ch);
-
- good_top_choice = (top_score > nn_reject_threshold) &&
- (nn_reject_head_and_shoulders * top_score > next_score);
-
- excellent_top_choice = good_top_choice &&
- (top_score > nn_dodgy_char_threshold);
-
- if (top == tess_ch) {
- if (excellent_top_choice)
- accept_level = 0;
- else if (good_top_choice)
- accept_level = 1; //Top correct and well matched
- else
- accept_level = 2; //Top correct but poor match
- }
- else if ((nn_conf_1Il &&
- STRING (conflict_set_I_l_1).contains (tess_ch) &&
- STRING (conflict_set_I_l_1).contains (top)) ||
- (nn_conf_hyphen &&
- STRING (conflict_set_hyphen).contains (tess_ch) &&
- STRING (conflict_set_hyphen).contains (top)) ||
- (nn_conf_Ss &&
- STRING (conflict_set_S_s).contains (tess_ch) &&
- STRING (conflict_set_S_s).contains (top))) {
- confusion_match = TRUE;
- if (good_top_choice)
- accept_level = 1; //Good top confusion
- else
- accept_level = 2; //Poor top confusion
- }
- else if ((nn_conf_1Il &&
- STRING (conflict_set_I_l_1).contains (tess_ch) &&
- STRING (conflict_set_I_l_1).contains (next)) ||
- (nn_conf_hyphen &&
- STRING (conflict_set_hyphen).contains (tess_ch) &&
- STRING (conflict_set_hyphen).contains (next)) ||
- (nn_conf_Ss &&
- STRING (conflict_set_S_s).contains (tess_ch) &&
- STRING (conflict_set_S_s).contains (next))) {
- confusion_match = TRUE;
- if (!good_top_choice)
- accept_level = 3; //Next confusion and top match dodgy
- else
- accept_level = 4; //Next confusion and good top match
- }
- else if (next == tess_ch) {
- if (!good_top_choice)
- accept_level = 3; //Next match and top match dodgy
- else
- accept_level = 4; //Next match and good top match
- }
- else
- accept_level = 5;
-
- /* Could allow some match flexibility here sS$ etc */
-
- /* Now set confirmation level according to how much we can believe the tess
- char. */
-
- if ((accept_level == 0) && !confusion_match)
- return 3;
-
- if ((accept_level <= 1) &&
- (!nn_conf_strict_on_dodgy_chs || !dodgy_char) && !confusion_match)
- return 3;
-
- if ((accept_level == 2) &&
- !confusion_match && !dodgy_char &&
- good_quality_word &&
- dict_word &&
- (checked_dict_word || !nn_double_check_dict) && sensible_word)
- return 2;
-
- if (confusion_match &&
- (accept_level <= nn_conf_accept_level) &&
- (good_quality_word ||
- (!nn_conf_test_good_qual &&
- !STRING (conflict_set_I_l_1).contains (tess_ch))) &&
- (dict_word || !nn_conf_test_dict) &&
- (checked_dict_word || !nn_conf_double_check_dict) &&
- (sensible_word || !nn_conf_test_sensible))
- return 1;
-
- if (!confusion_match &&
- nn_lax &&
- (accept_level == 3) &&
- (good_quality_word || !nn_conf_test_good_qual) &&
- (dict_word || !nn_conf_test_dict) &&
- (sensible_word || !nn_conf_test_sensible))
- return 1;
- else
- return 0;
-}
-
-
-/*************************************************************************
- * dont_allow_dubious_chars()
- * Let Rejects "eat" into adjacent "dubious" chars. I.e those prone to be wrong
- * if adjacent to a reject.
- *************************************************************************/
-void dont_allow_dubious_chars(WERD_RES *word) {
- int i = 0;
- int offset = 0;
- int rej_pos;
- int word_len = word->reject_map.length ();
-
- while (i < word_len) {
- /* Find next reject */
-
- while ((i < word_len) && (word->reject_map[i].accepted ()))
- {
- offset += word->best_choice->unichar_lengths()[i];
- i++;
- }
-
- if (i < word_len) {
- rej_pos = i;
-
- /* Reject dubious chars to the left */
- i--;
- offset -= word->best_choice->unichar_lengths()[i];
- while ((i >= 0) &&
- STRING(dubious_chars_left_of_reject).contains(
- word->best_choice->unichar_string()[offset])) {
- word->reject_map[i--].setrej_dubious ();
- offset -= word->best_choice->unichar_lengths()[i];
- }
-
- /* Skip adjacent rejects */
-
- for (i = rej_pos;
- (i < word_len) && (word->reject_map[i].rejected ());
- offset += word->best_choice->unichar_lengths()[i++]);
-
- /* Reject dubious chars to the right */
-
- while ((i < word_len) &&
- STRING(dubious_chars_right_of_reject).contains(
- word->best_choice->unichar_string()[offset])) {
- offset += word->best_choice->unichar_lengths()[i];
- word->reject_map[i++].setrej_dubious ();
- }
- }
- }
-}
-
/*************************************************************************
* dont_allow_1Il()
* Dont unreject LONE accepted 1Il conflict set chars
*************************************************************************/
-namespace tesseract {
void Tesseract::dont_allow_1Il(WERD_RES *word) {
int i = 0;
int offset;
- int word_len = word->reject_map.length ();
- const char *s = word->best_choice->unichar_string().string ();
- const char *lengths = word->best_choice->unichar_lengths().string ();
+ int word_len = word->reject_map.length();
+ const char *s = word->best_choice->unichar_string().string();
+ const char *lengths = word->best_choice->unichar_lengths().string();
BOOL8 accepted_1Il = FALSE;
for (i = 0, offset = 0; i < word_len;
offset += word->best_choice->unichar_lengths()[i++]) {
- if (word->reject_map[i].accepted ()) {
- if (STRING (conflict_set_I_l_1).contains (s[offset]))
+ if (word->reject_map[i].accepted()) {
+ if (STRING(conflict_set_I_l_1).contains(s[offset])) {
accepted_1Il = TRUE;
- else {
- if (unicharset.get_isalpha (s + offset, lengths[i]) ||
- unicharset.get_isdigit (s + offset, lengths[i]))
+ } else {
+ if (unicharset.get_isalpha(s + offset, lengths[i]) ||
+ unicharset.get_isdigit(s + offset, lengths[i]))
return; // >=1 non 1Il ch accepted
}
}
@@ -1419,15 +733,14 @@ void Tesseract::dont_allow_1Il(WERD_RES *word) {
for (i = 0, offset = 0; i < word_len;
offset += word->best_choice->unichar_lengths()[i++]) {
- if (STRING (conflict_set_I_l_1).contains (s[offset]) &&
- word->reject_map[i].accepted ())
- word->reject_map[i].setrej_postNN_1Il ();
+ if (STRING(conflict_set_I_l_1).contains(s[offset]) &&
+ word->reject_map[i].accepted())
+ word->reject_map[i].setrej_postNN_1Il();
}
}
-inT16 Tesseract::count_alphanums( //how many alphanums
- WERD_RES *word_res) {
+inT16 Tesseract::count_alphanums(WERD_RES *word_res) {
int count = 0;
const WERD_CHOICE *best_choice = word_res->best_choice;
for (int i = 0; i < word_res->reject_map.length(); ++i) {
@@ -1439,34 +752,33 @@ inT16 Tesseract::count_alphanums( //how many alphanums
}
return count;
}
-} // namespace tesseract
-void reject_mostly_rejects( //rej all if most rejectd
- WERD_RES *word) {
+// reject all if most rejected.
+void Tesseract::reject_mostly_rejects(WERD_RES *word) {
/* Reject the whole of the word if the fraction of rejects exceeds a limit */
- if ((float) word->reject_map.reject_count () / word->reject_map.length () >=
+ if ((float) word->reject_map.reject_count() / word->reject_map.length() >=
rej_whole_of_mostly_reject_word_fract)
- word->reject_map.rej_word_mostly_rej ();
+ word->reject_map.rej_word_mostly_rej();
}
-namespace tesseract {
BOOL8 Tesseract::repeated_nonalphanum_wd(WERD_RES *word, ROW *row) {
inT16 char_quality;
inT16 accepted_char_quality;
- if (word->best_choice->unichar_lengths().length () <= 1)
+ if (word->best_choice->unichar_lengths().length() <= 1)
return FALSE;
- if (!STRING (ok_repeated_ch_non_alphanum_wds).
- contains (word->best_choice->unichar_string()[0]))
+ if (!STRING(ok_repeated_ch_non_alphanum_wds).
+ contains(word->best_choice->unichar_string()[0]))
return FALSE;
- if (!repeated_ch_string (word->best_choice->unichar_string().string (),
- word->best_choice->unichar_lengths().string ()))
- return FALSE;
+ UNICHAR_ID uch_id = word->best_choice->unichar_id(0);
+ for (int i = 1; i < word->best_choice->length(); ++i) {
+ if (word->best_choice->unichar_id(i) != uch_id) return FALSE;
+ }
word_char_quality(word, row, &char_quality, &accepted_char_quality);
@@ -1477,36 +789,18 @@ BOOL8 Tesseract::repeated_nonalphanum_wd(WERD_RES *word, ROW *row) {
return FALSE;
}
-BOOL8 Tesseract::repeated_ch_string(const char *rep_ch_str,
- const char *lengths) {
- UNICHAR_ID c;
-
- if ((rep_ch_str == NULL) || (*rep_ch_str == '\0')) {
- return FALSE;
- }
-
- c = unicharset.unichar_to_id(rep_ch_str, *lengths);
- rep_ch_str += *(lengths++);
- while (*rep_ch_str != '\0' &&
- unicharset.unichar_to_id(rep_ch_str, *lengths) == c) {
- rep_ch_str++;
- }
- if (*rep_ch_str == '\0')
- return TRUE;
- return FALSE;
-}
-
-
inT16 Tesseract::safe_dict_word(const WERD_CHOICE &word) {
int dict_word_type = dict_word(word);
return dict_word_type == DOC_DAWG_PERM ? 0 : dict_word_type;
}
-
+// Note: After running this function word_res->best_choice->blob_choices()
+// might not contain the right BLOB_CHOICE coresponding to each character
+// in word_res->best_choice. However, the length of blob_choices and
+// word_res->best_choice will remain the same.
void Tesseract::flip_hyphens(WERD_RES *word_res) {
WERD_CHOICE *best_choice = word_res->best_choice;
int i;
- PBLOB_IT outword_it;
int prev_right = -9999;
int next_left;
TBOX out_box;
@@ -1515,17 +809,16 @@ void Tesseract::flip_hyphens(WERD_RES *word_res) {
if (tessedit_lower_flip_hyphen <= 1)
return;
- outword_it.set_to_list(word_res->outword->blob_list());
+ TBLOB* blob = word_res->rebuild_word->blobs;
UNICHAR_ID unichar_dash = unicharset.unichar_to_id("-");
bool modified = false;
- for (i = 0, outword_it.mark_cycle_pt();
- i < best_choice->length() && !outword_it.cycled_list();
- ++i, outword_it.forward()) {
- out_box = outword_it.data()->bounding_box();
- if (outword_it.at_last())
+ for (i = 0; i < best_choice->length() && blob != NULL; ++i,
+ blob = blob->next) {
+ out_box = blob->bounding_box();
+ if (blob->next == NULL)
next_left = 9999;
else
- next_left = outword_it.data_relative(1)->bounding_box().left();
+ next_left = blob->next->bounding_box().left();
// Dont touch small or touching blobs - it is too dangerous.
if ((out_box.width() > 8 * word_res->denorm.scale()) &&
(out_box.left() > prev_right) && (out_box.right() < next_left)) {
@@ -1564,25 +857,26 @@ void Tesseract::flip_hyphens(WERD_RES *word_res) {
}
}
+// Note: After running this function word_res->best_choice->blob_choices()
+// might not contain the right BLOB_CHOICE coresponding to each character
+// in word_res->best_choice. However, the length of blob_choices and
+// word_res->best_choice will remain the same.
void Tesseract::flip_0O(WERD_RES *word_res) {
WERD_CHOICE *best_choice = word_res->best_choice;
int i;
- PBLOB_IT outword_it;
TBOX out_box;
if (!tessedit_flip_0O)
return;
- outword_it.set_to_list(word_res->outword->blob_list ());
-
- for (i = 0, outword_it.mark_cycle_pt ();
- i < best_choice->length() && !outword_it.cycled_list ();
- ++i, outword_it.forward ()) {
+ TBLOB* blob = word_res->rebuild_word->blobs;
+ for (i = 0; i < best_choice->length() && blob != NULL; ++i,
+ blob = blob->next) {
if (unicharset.get_isupper(best_choice->unichar_id(i)) ||
unicharset.get_isdigit(best_choice->unichar_id(i))) {
- out_box = outword_it.data()->bounding_box ();
- if ((out_box.top() < bln_baseline_offset + bln_x_height) ||
- (out_box.bottom() > bln_baseline_offset + bln_x_height / 4))
+ out_box = blob->bounding_box();
+ if ((out_box.top() < kBlnBaselineOffset + kBlnXHeight) ||
+ (out_box.bottom() > kBlnBaselineOffset + kBlnXHeight / 4))
return; //Beware words with sub/superscripts
}
}
@@ -1593,7 +887,7 @@ void Tesseract::flip_0O(WERD_RES *word_res) {
return; // 0 or O are not present/enabled in unicharset
}
bool modified = false;
- for (i = 1; i < best_choice->length(); ++i, outword_it.forward ()) {
+ for (i = 1; i < best_choice->length(); ++i) {
if (best_choice->unichar_id(i) == unichar_0 ||
best_choice->unichar_id(i) == unichar_O) {
/* A0A */
diff --git a/ccmain/reject.h b/ccmain/reject.h
index acf14dab4f..d3aa1ec5fd 100644
--- a/ccmain/reject.h
+++ b/ccmain/reject.h
@@ -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);
diff --git a/ccmain/tessbox.cpp b/ccmain/tessbox.cpp
index b2952f4d9c..9c13da4c80 100644
--- a/ccmain/tessbox.cpp
+++ b/ccmain/tessbox.cpp
@@ -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);
}
diff --git a/ccmain/tessbox.h b/ccmain/tessbox.h
index c755c31c72..b031007d77 100644
--- a/ccmain/tessbox.h
+++ b/ccmain/tessbox.h
@@ -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
diff --git a/ccmain/tessedit.cpp b/ccmain/tessedit.cpp
index 258d3e94db..d2db3a8d2f 100644
--- a/ccmain/tessedit.cpp
+++ b/ccmain/tessedit.cpp
@@ -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(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
diff --git a/ccmain/tessedit.h b/ccmain/tessedit.h
index affbdc159c..e19e5ddee6 100644
--- a/ccmain/tessedit.h
+++ b/ccmain/tessedit.h
@@ -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
diff --git a/ccmain/tesseract_cube_combiner.cpp b/ccmain/tesseract_cube_combiner.cpp
new file mode 100644
index 0000000000..87e68a17a4
--- /dev/null
+++ b/ccmain/tesseract_cube_combiner.cpp
@@ -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
+#include
+#include
+#include
+
+#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 *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(
+ 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 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];
+}
+}
diff --git a/ccmain/tesseract_cube_combiner.h b/ccmain/tesseract_cube_combiner.h
new file mode 100644
index 0000000000..4773f8293e
--- /dev/null
+++ b/ccmain/tesseract_cube_combiner.h
@@ -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
+#include
+#include "pageres.h"
+
+#ifdef __MSW32__
+#include
+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 *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
diff --git a/ccmain/tesseractclass.cpp b/ccmain/tesseractclass.cpp
index 1c70ac4ddc..5a43617879 100644
--- a/ccmain/tesseractclass.cpp
+++ b/ccmain/tesseractclass.cpp
@@ -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() {
diff --git a/ccmain/tesseractclass.h b/ccmain/tesseractclass.h
index 8203a36750..5474d9ceb8 100644
--- a/ccmain/tesseractclass.h
+++ b/ccmain/tesseractclass.h
@@ -21,17 +21,18 @@
#ifndef TESSERACT_CCMAIN_TESSERACTCLASS_H__
#define TESSERACT_CCMAIN_TESSERACTCLASS_H__
-#include "varable.h"
+#include "allheaders.h"
+#include "params.h"
#include "wordrec.h"
#include "ocrclass.h"
#include "control.h"
#include "docqual.h"
+#include "textord.h"
-class CHAR_SAMPLES_LIST;
-class CHAR_SAMPLE_LIST;
class PAGE_RES;
class PAGE_RES_IT;
class BLOCK_LIST;
+class CharSamp;
class TO_BLOCK_LIST;
class IMAGE;
class WERD_RES;
@@ -42,6 +43,7 @@ struct Pix;
class WERD_CHOICE;
class WERD;
class BLOB_CHOICE_LIST_CLIST;
+struct OSResults;
// Top-level class for all tesseract global instance data.
@@ -55,22 +57,76 @@ class BLOB_CHOICE_LIST_CLIST;
// know about the content of a higher-level directory.
// The following scheme will grant the easiest access to lower-level
// global members without creating a cyclic dependency:
-// ccmain inherits wordrec, includes textord as a member
-// wordrec inherits classify
-// classify inherits ccstruct, includes dict as a member
-// ccstruct inherits c_util, includes image as a member
-// c_util inherits cc_util
-// textord has a pointer to ccstruct, but doesn't own it.
-// dict has a pointer to ccstruct, but doesn't own it.
+//
+// Class Hierarchy (^ = inheritance):
+//
+// CCUtil (ccutil/ccutil.h)
+// ^ Members include: UNICHARSET
+// CUtil (cutil/cutil_class.h)
+// ^ Members include: TBLOB*, TEXTBLOCK*
+// CCStruct (ccstruct/ccstruct.h)
+// ^ Members include: Image
+// Classify (classify/classify.h)
+// ^ Members include: Dict
+// WordRec (wordrec/wordrec.h)
+// ^ Members include: WERD*, DENORM*
+// Tesseract (ccmain/tesseractclass.h)
+// Members include: Pix*, CubeRecoContext*,
+// TesseractCubeCombiner*
+//
+// Other important classes:
+//
+// TessBaseAPI (api/baseapi.h)
+// Members include: BLOCK_LIST*, PAGE_RES*,
+// Tesseract*, ImageThresholder*
+// Dict (dict/dict.h)
+// Members include: Image* (private)
//
// NOTE: that each level contains members that correspond to global
// data that is defined (and used) at that level, not necessarily where
// the type is defined so for instance:
-// BOOL_VAR (textord_show_blobs, FALSE, "Display unsorted blobs");
+// BOOL_VAR_H(textord_show_blobs, false, "Display unsorted blobs");
// goes inside the Textord class, not the cc_util class.
namespace tesseract {
+class CubeLineObject;
+class CubeObject;
+class CubeRecoContext;
+class TesseractCubeCombiner;
+
+// A collection of various variables for statistics and debugging.
+struct TesseractStats {
+ TesseractStats()
+ : adaption_word_number(0),
+ doc_blob_quality(0),
+ doc_outline_errs(0),
+ doc_char_quality(0),
+ good_char_count(0),
+ doc_good_char_quality(0),
+ word_count(0),
+ dict_words(0),
+ tilde_crunch_written(false),
+ last_char_was_newline(true),
+ last_char_was_tilde(false),
+ write_results_empty_block(true) {}
+
+ inT32 adaption_word_number;
+ inT16 doc_blob_quality;
+ inT16 doc_outline_errs;
+ inT16 doc_char_quality;
+ inT16 good_char_count;
+ inT16 doc_good_char_quality;
+ inT32 word_count; // count of word in the document
+ inT32 dict_words; // number of dicitionary words in the document
+ STRING dump_words_str; // accumulator used by dump_words()
+ // Flags used by write_results()
+ bool tilde_crunch_written;
+ bool last_char_was_newline;
+ bool last_char_was_tilde;
+ bool write_results_empty_block;
+};
+
class Tesseract : public Wordrec {
public:
Tesseract();
@@ -90,89 +146,112 @@ class Tesseract : public Wordrec {
Pix* pix_binary() const {
return pix_binary_;
}
+ Pix* pix_grey() const {
+ return pix_grey_;
+ }
+ void set_pix_grey(Pix* grey_pix) {
+ pix_grey_ = grey_pix;
+ }
+ int ImageWidth() const {
+ return pixGetWidth(pix_binary_);
+ }
+ int ImageHeight() const {
+ return pixGetHeight(pix_binary_);
+ }
+
+ const Textord& textord() const {
+ return textord_;
+ }
+ Textord* mutable_textord() {
+ return &textord_;
+ }
+
+ bool right_to_left() const {
+ return right_to_left_;
+ }
void SetBlackAndWhitelist();
- int SegmentPage(const STRING* input_file,
- IMAGE* image, BLOCK_LIST* blocks);
- int AutoPageSeg(int width, int height, int resolution,
- bool single_column, IMAGE* image,
- BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks);
+
+ int SegmentPage(const STRING* input_file, BLOCK_LIST* blocks,
+ Tesseract* osd_tess, OSResults* osr);
+ void SetupWordScripts(BLOCK_LIST* blocks);
+ int AutoPageSeg(int resolution, bool single_column,
+ bool osd, bool only_osd,
+ BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks,
+ Tesseract* osd_tess, OSResults* osr);
//// control.h /////////////////////////////////////////////////////////
- void recog_all_words( //process words
- PAGE_RES *page_res, //page structure
- //progress monitor
- volatile ETEXT_DESC *monitor,
- TBOX *target_word_box=0L,
- inT16 dopasses=0
- );
+ bool ProcessTargetWord(const TBOX& word_box, const TBOX& target_word_box,
+ const char* word_config, int pass);
+ void recog_all_words(PAGE_RES* page_res,
+ ETEXT_DESC* monitor,
+ const TBOX* target_word_box,
+ const char* word_config,
+ int dopasses);
void classify_word_pass1( //recog one word
WERD_RES *word, //word to do
ROW *row,
- BLOCK* block,
- BOOL8 cluster_adapt,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting);
- void recog_pseudo_word( //recognize blobs
- BLOCK_LIST *block_list, //blocks to check
+ BLOCK* block);
+ void recog_pseudo_word(PAGE_RES* page_res, // blocks to check
TBOX &selection_box);
- // This method returns all the blobs in the specified blocks.
- // It's the caller's responsibility to destroy the returned list.
- C_BLOB_LIST* get_blobs_from_blocks(BLOCK_LIST* blocks // blocks to look at.
- );
-
- // This method can be used to perform word-level training using box files.
- // TODO: this can be modified to perform training in general case too.
- void train_word_level_with_boxes(
- const STRING& box_file, // File with boxes.
- const STRING& out_file, // Output file.
- BLOCK_LIST* blocks // Blocks to use.
- );
- void fix_rep_char(WERD_RES *word);
- void fix_quotes( //make double quotes
- WERD_CHOICE *choice, //choice to fix
- WERD *word, //word to do //char choices
+ void fix_rep_char(PAGE_RES_IT* page_res_it);
+ void ExplodeRepeatedWord(BLOB_CHOICE* best_choice, PAGE_RES_IT* page_res_it);
+
+ // Callback helper for fix_quotes returns a double quote if both
+ // arguments are quote, otherwise INVALID_UNICHAR_ID.
+ UNICHAR_ID BothQuotes(UNICHAR_ID id1, UNICHAR_ID id2);
+ void fix_quotes(WERD_RES* word_res,
BLOB_CHOICE_LIST_CLIST *blob_choices);
ACCEPTABLE_WERD_TYPE acceptable_word_string(const char *s,
const char *lengths);
void match_word_pass2( //recog one word
WERD_RES *word, //word to do
ROW *row,
- BLOCK* block,
- float x_height);
+ BLOCK* block);
void classify_word_pass2( //word to do
WERD_RES *word,
BLOCK* block,
ROW *row);
- BOOL8 recog_interactive( //recognize blobs
- BLOCK *block, //block
- ROW *row, //row of word
- WERD *word //word to recognize
- );
- void fix_hyphens( //crunch double hyphens
- WERD_CHOICE *choice, //choice to fix
- WERD *word, //word to do //char choices
+ void ReportXhtFixResult(bool accept_new_word, float new_x_ht,
+ WERD_RES* word, WERD_RES* new_word);
+ bool RunOldFixXht(WERD_RES *word, BLOCK* block, ROW *row);
+ bool TrainedXheightFix(WERD_RES *word, BLOCK* block, ROW *row);
+ BOOL8 recog_interactive(BLOCK* block, ROW* row, WERD_RES* word_res);
+
+ // Callback helper for fix_hyphens returns UNICHAR_ID of - if both
+ // arguments are hyphen, otherwise INVALID_UNICHAR_ID.
+ UNICHAR_ID BothHyphens(UNICHAR_ID id1, UNICHAR_ID id2);
+ // Callback helper for fix_hyphens returns true if box1 and box2 overlap
+ // (assuming both on the same textline, are in order and a chopped em dash.)
+ bool HyphenBoxesOverlap(const TBOX& box1, const TBOX& box2);
+ void fix_hyphens(WERD_RES* word_res,
BLOB_CHOICE_LIST_CLIST *blob_choices);
void set_word_fonts(
WERD_RES *word, // word to adapt to
BLOB_CHOICE_LIST_CLIST *blob_choices); // detailed results
void font_recognition_pass( //good chars in word
PAGE_RES_IT &page_res_it);
-
+ BOOL8 check_debug_pt(WERD_RES *word, int location);
+ //// cube_control.cpp ///////////////////////////////////////////////////
+ bool init_cube_objects(bool load_combiner,
+ TessdataManager *tessdata_manager);
+ void run_cube(PAGE_RES *page_res);
+ void cube_recognize(CubeObject *cube_obj, PAGE_RES_IT *page_res_it);
+ void fill_werd_res(const BoxWord& cube_box_word,
+ WERD_CHOICE* cube_werd_choice,
+ const char* cube_best_str,
+ PAGE_RES_IT *page_res_it);
+ bool extract_cube_state(CubeObject* cube_obj, int* num_chars,
+ Boxa** char_boxes, CharSamp*** char_samples);
+ bool create_cube_box_word(Boxa *char_boxes, int num_chars,
+ TBOX word_box, BoxWord* box_word);
//// output.h //////////////////////////////////////////////////////////
- void output_pass( //Tess output pass //send to api
- PAGE_RES_IT &page_res_it,
- BOOL8 write_to_shm,
- TBOX *target_word_box);
- FILE *open_outfile( //open .map & .unlv file
- const char *extension);
- void write_results( //output a word
- 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
+ void output_pass(PAGE_RES_IT &page_res_it, const TBOX *target_word_box);
+ void write_results(PAGE_RES_IT &page_res_it, // full info
+ char newline_type, // type of newline
+ BOOL8 force_eol // override tilde crunch?
);
void set_unlv_suspects(WERD_RES *word);
UNICHAR_ID get_rep_char(WERD_RES *word); // what char is repeated?
@@ -181,53 +260,55 @@ class Tesseract : public Wordrec {
inT16 count_alphanums(const WERD_CHOICE &word);
inT16 count_alphas(const WERD_CHOICE &word);
//// tessedit.h ////////////////////////////////////////////////////////
- void read_config_file(const char *filename, bool global_only);
+ void read_config_file(const char *filename, bool init_only);
int init_tesseract(const char *arg0,
const char *textbase,
const char *language,
+ OcrEngineMode oem,
char **configs,
int configs_size,
- bool configs_global_only);
+ bool configs_init_only);
+ int init_tesseract(const char *datapath,
+ const char *language,
+ OcrEngineMode oem) {
+ return init_tesseract(datapath, NULL, language, oem, NULL, 0, false);
+ }
int init_tesseract_lm(const char *arg0,
const char *textbase,
const char *language);
- // Initializes the tesseract classifier without loading language models.
- int init_tesseract_classifier(const char *arg0,
- const char *textbase,
- const char *language,
- char **configs,
- int configs_size,
- bool configs_global_only);
-
void recognize_page(STRING& image_name);
void end_tesseract();
bool init_tesseract_lang_data(const char *arg0,
const char *textbase,
const char *language,
+ OcrEngineMode oem,
char **configs,
int configs_size,
- bool configs_global_only);
+ bool configs_init_only);
//// pgedit.h //////////////////////////////////////////////////////////
SVMenuNode *build_menu_new();
- void pgeditor_main(BLOCK_LIST *blocks);
+ void pgeditor_main(int width, int height, PAGE_RES* page_res);
void process_image_event( // action in image win
const SVEvent &event);
- void pgeditor_read_file( // of serialised file
- STRING &filename,
- BLOCK_LIST *blocks // block list to add to
- );
- void do_new_source( // serialise
- );
BOOL8 process_cmd_win_event( // UI command semantics
inT32 cmd_event, // which menu item?
char *new_value // any prompt data
);
+ void debug_word(PAGE_RES* page_res, const TBOX &selection_box);
+ void do_re_display(
+ BOOL8 (tesseract::Tesseract::*word_painter)(BLOCK* block,
+ ROW* row,
+ WERD_RES* word_res));
+ BOOL8 word_display(BLOCK* block, ROW* row, WERD_RES* word_res);
+ BOOL8 word_bln_display(BLOCK* block, ROW* row, WERD_RES* word_res);
+ BOOL8 word_blank_and_set_display(BLOCK* block, ROW* row, WERD_RES* word_res);
+ BOOL8 word_set_display(BLOCK* block, ROW* row, WERD_RES* word_res);
+ BOOL8 word_dumper(BLOCK* block, ROW* row, WERD_RES* word_res);
//// reject.h //////////////////////////////////////////////////////////
- const char *char_ambiguities(char c);
void make_reject_map( //make rej map for wd //detailed results
WERD_RES *word,
BLOB_CHOICE_LIST_CLIST *blob_choices,
@@ -246,8 +327,6 @@ class Tesseract : public Wordrec {
void dont_allow_1Il(WERD_RES *word);
inT16 count_alphanums( //how many alphanums
WERD_RES *word);
- BOOL8 repeated_ch_string(const char *rep_ch_str,
- const char *lengths);
void flip_0O(WERD_RES *word);
BOOL8 non_0_digit(UNICHAR_ID unichar_id);
BOOL8 non_O_upper(UNICHAR_ID unichar_id);
@@ -263,77 +342,21 @@ class Tesseract : public Wordrec {
inT16 pass);
inT16 safe_dict_word(const WERD_CHOICE &word);
void flip_hyphens(WERD_RES *word);
+ void reject_I_1_L(WERD_RES *word);
+ void reject_edge_blobs(WERD_RES *word);
+ void reject_mostly_rejects(WERD_RES *word);
//// adaptions.h ///////////////////////////////////////////////////////
- void adapt_to_good_ems(WERD_RES *word,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting);
- void adapt_to_good_samples(WERD_RES *word,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting);
BOOL8 word_adaptable( //should we adapt?
WERD_RES *word,
uinT16 mode);
- void reject_suspect_ems(WERD_RES *word);
- void collect_ems_for_adaption(WERD_RES *word,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting);
- void collect_characters_for_adaption(WERD_RES *word,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting);
- void check_wait_list(CHAR_SAMPLE_LIST *chars_waiting,
- CHAR_SAMPLE *sample,
- CHAR_SAMPLES *best_cluster);
- void cluster_sample(CHAR_SAMPLE *sample,
- CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting);
- void complete_clustering(CHAR_SAMPLES_LIST *char_clusters,
- CHAR_SAMPLE_LIST *chars_waiting);
//// tfacepp.cpp ///////////////////////////////////////////////////////
- WERD_CHOICE *recog_word_recursive( //recog one owrd
- WERD *word, //word to do
- DENORM *denorm, //de-normaliser
- //matcher function
- POLY_MATCHER matcher,
- //tester function
- POLY_TESTER tester,
- //trainer function
- POLY_TESTER trainer,
- 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 *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
- WERD_CHOICE *&raw_choice, //raw result
- //list of blob lists
- BLOB_CHOICE_LIST_CLIST *blob_choices,
- WERD *&outword //bln word output
- );
- WERD_CHOICE *split_and_recog_word( //recog one owrd
- WERD *word, //word to do
- DENORM *denorm, //de-normaliser
- //matcher function
- POLY_MATCHER matcher,
- //tester function
- POLY_TESTER tester,
- //trainer function
- POLY_TESTER trainer,
- 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
- );
+ void recog_word_recursive(WERD_RES* word,
+ BLOB_CHOICE_LIST_CLIST *blob_choices);
+ void recog_word(WERD_RES *word,
+ BLOB_CHOICE_LIST_CLIST *blob_choices);
+ void split_and_recog_word(WERD_RES* word,
+ BLOB_CHOICE_LIST_CLIST *blob_choices);
//// fixspace.cpp ///////////////////////////////////////////////////////
BOOL8 digit_or_numeric_punct(WERD_RES *word, int char_position);
inT16 eval_word_spacing(WERD_RES_LIST &word_res_list);
@@ -345,10 +368,17 @@ class Tesseract : public Wordrec {
ROW *row,
BLOCK* block);
void fix_sp_fp_word(WERD_RES_IT &word_res_it, ROW *row, BLOCK* block);
- void fix_fuzzy_spaces( //find fuzzy words
- volatile ETEXT_DESC *monitor, //progress monitor
- inT32 word_count, //count of words in doc
+ void fix_fuzzy_spaces( //find fuzzy words
+ ETEXT_DESC *monitor, //progress monitor
+ inT32 word_count, //count of words in doc
PAGE_RES *page_res);
+ void dump_words(WERD_RES_LIST &perm, inT16 score,
+ inT16 mode, BOOL8 improved);
+ BOOL8 uniformly_spaced(WERD_RES *word);
+ BOOL8 fixspace_thinks_word_done(WERD_RES *word);
+ inT16 worst_noise_blob(WERD_RES *word_res, float *worst_noise_score);
+ float blob_noise_score(TBLOB *blob);
+ void break_noisiest_blob_word(WERD_RES_LIST &words);
//// docqual.cpp ////////////////////////////////////////////////////////
GARBAGE_LEVEL garbage_word(WERD_RES *word, BOOL8 ok_dict_word);
BOOL8 potential_word_crunch(WERD_RES *word,
@@ -363,176 +393,422 @@ class Tesseract : public Wordrec {
void quality_based_rejection(PAGE_RES_IT &page_res_it,
BOOL8 good_quality_doc);
void 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 BothSpaces(UNICHAR_ID id1, UNICHAR_ID id2);
void merge_tess_fails(WERD_RES *word_res);
void tilde_delete(PAGE_RES_IT &page_res_it);
- void insert_rej_cblobs(WERD_RES *word);
+ inT16 word_blob_quality(WERD_RES *word, ROW *row);
+ void word_char_quality(WERD_RES *word, ROW *row, inT16 *match_count,
+ inT16 *accepted_match_count);
+ void unrej_good_chs(WERD_RES *word, ROW *row);
+ inT16 count_outline_errs(char c, inT16 outline_count);
+ inT16 word_outline_errs(WERD_RES *word);
+ BOOL8 terrible_word_crunch(WERD_RES *word, GARBAGE_LEVEL garbage_level);
+ CRUNCH_MODE word_deletable(WERD_RES *word, inT16 &delete_mode);
+ inT16 failure_count(WERD_RES *word);
+ BOOL8 noise_outlines(TWERD *word);
//// pagewalk.cpp ///////////////////////////////////////////////////////
void
process_selected_words (
- BLOCK_LIST * block_list, //blocks to check
+ PAGE_RES* page_res, // blocks to check
//function to call
TBOX & selection_box,
- BOOL8 (tesseract::Tesseract::*word_processor) (
- BLOCK *,
- ROW *,
- WERD *));
+ BOOL8 (tesseract::Tesseract::*word_processor) (BLOCK* block,
+ ROW* row,
+ WERD_RES* word_res));
//// tessbox.cpp ///////////////////////////////////////////////////////
void tess_add_doc_word( //test acceptability
WERD_CHOICE *word_choice //after context
);
- void tess_adapter( //adapt to word
- WERD *word, //bln word
- DENORM *denorm, //de-normalise
- const WERD_CHOICE& choice, //string for word
- const WERD_CHOICE& raw_choice, //before context
- const char *rejmap //reject map
- );
- WERD_CHOICE *test_segment_pass2( //recog one word
- WERD *word, //bln word to do
- DENORM *denorm, //de-normaliser
- POLY_MATCHER matcher, //matcher function
- POLY_TESTER tester, //tester function
- //raw result
- WERD_CHOICE *&raw_choice,
- //list of blob lists
- BLOB_CHOICE_LIST_CLIST *blob_choices,
- WERD *&outword //bln word output
- );
- WERD_CHOICE *tess_segment_pass1( //recog one word
- WERD *word, //bln word to do
- DENORM *denorm, //de-normaliser
- POLY_MATCHER matcher, //matcher function
- //raw result
- WERD_CHOICE *&raw_choice,
- //list of blob lists
- BLOB_CHOICE_LIST_CLIST *blob_choices,
- WERD *&outword //bln word output
- );
- WERD_CHOICE *tess_segment_pass2( //recog one word
- WERD *word, //bln word to do
- DENORM *denorm, //de-normaliser
- POLY_MATCHER matcher, //matcher function
- //raw result
- WERD_CHOICE *&raw_choice,
- //list of blob lists
- BLOB_CHOICE_LIST_CLIST *blob_choices,
- WERD *&outword //bln word output
- );
- WERD_CHOICE *correct_segment_pass2( //recog one word
- WERD *word, //bln word to do
- DENORM *denorm, //de-normaliser
- POLY_MATCHER matcher, //matcher function
- POLY_TESTER tester, //tester function
- //raw result
- WERD_CHOICE *&raw_choice,
- //list of blob lists
- BLOB_CHOICE_LIST_CLIST *blob_choices,
- WERD *&outword //bln word output
- );
- void tess_default_matcher( //call tess
- PBLOB *pblob, //previous blob
- PBLOB *blob, //blob to match
- PBLOB *nblob, //next blob
- WERD *word, //word it came from
- DENORM *denorm, //de-normaliser
- BLOB_CHOICE_LIST *ratings, //list of results
- const char* script
- );
- void tess_bn_matcher( //call tess
- PBLOB *pblob, //previous blob
- PBLOB *blob, //blob to match
- PBLOB *nblob, //next blob
- WERD *word, //word it came from
- DENORM *denorm, //de-normaliser
- BLOB_CHOICE_LIST *ratings //list of results
- );
- void tess_cn_matcher( //call tess
- PBLOB *pblob, //previous blob
- PBLOB *blob, //blob to match
- PBLOB *nblob, //next blob
- WERD *word, //word it came from
- DENORM *denorm, //de-normaliser
- BLOB_CHOICE_LIST *ratings, //list of results
- // Sorted array of CP_RESULT_STRUCT from class pruner.
- CLASS_PRUNER_RESULTS cpresults
- );
- BOOL8 tess_adaptable_word( //test adaptability
- WERD *word, //word to test
- WERD_CHOICE *word_choice, //after context
- WERD_CHOICE *raw_choice //before context
- );
+ void tess_segment_pass1(WERD_RES *word,
+ BLOB_CHOICE_LIST_CLIST *blob_choices);
+ void tess_segment_pass2(WERD_RES *word,
+ BLOB_CHOICE_LIST_CLIST *blob_choices);
BOOL8 tess_acceptable_word( //test acceptability
WERD_CHOICE *word_choice, //after context
WERD_CHOICE *raw_choice //before context
);
//// applybox.cpp //////////////////////////////////////////////////////
- void apply_box_testing(BLOCK_LIST *block_list);
- void apply_boxes(const STRING& fname,
- BLOCK_LIST *block_list //real blocks
- );
- // converts an array of boxes to a block list
- int Boxes2BlockList(int box_cnt, TBOX *boxes, BLOCK_LIST *block_list,
- bool right2left);
- //// blobcmp.cpp ///////////////////////////////////////////////////////
- float compare_tess_blobs(TBLOB *blob1,
- TEXTROW *row1,
- TBLOB *blob2,
- TEXTROW *row2);
- //// paircmp.cpp ///////////////////////////////////////////////////////
- float compare_bln_blobs( //match 2 blobs
- PBLOB *blob1, //first blob
- DENORM *denorm1,
- PBLOB *blob2, //other blob
- DENORM *denorm2);
- float compare_blobs( //match 2 blobs
- PBLOB *blob1, //first blob
- ROW *row1, //row it came from
- PBLOB *blob2, //other blob
- ROW *row2);
- BOOL8 compare_blob_pairs( //blob processor
- BLOCK *,
- ROW *row, //row it came from
- WERD *,
- PBLOB *blob //blob to compare
- );
- //// fixxht.cpp ///////////////////////////////////////////////////////
- void check_block_occ(WERD_RES *word_res);
+ // Applies the box file based on the image name fname, and resegments
+ // the words in the block_list (page), with:
+ // blob-mode: one blob per line in the box file, words as input.
+ // word/line-mode: one blob per space-delimited unit after the #, and one word
+ // per line in the box file. (See comment above for box file format.)
+ // If find_segmentation is true, (word/line mode) then the classifier is used
+ // to re-segment words/lines to match the space-delimited truth string for
+ // each box. In this case, the input box may be for a word or even a whole
+ // text line, and the output words will contain multiple blobs corresponding
+ // to the space-delimited input string.
+ // With find_segmentation false, no classifier is needed, but the chopper
+ // can still be used to correctly segment touching characters with the help
+ // of the input boxes.
+ // In the returned PAGE_RES, the WERD_RES are setup as they would be returned
+ // from normal classification, ie. with a word, chopped_word, rebuild_word,
+ // seam_array, denorm, box_word, and best_state, but NO best_choice or
+ // raw_choice, as they would require a UNICHARSET, which we aim to avoid.
+ // Instead, the correct_text member of WERD_RES is set, and this may be later
+ // converted to a best_choice using CorrectClassifyWords. CorrectClassifyWords
+ // is not required before calling ApplyBoxTraining.
+ PAGE_RES* ApplyBoxes(const STRING& fname, bool find_segmentation,
+ BLOCK_LIST *block_list);
+ // Builds a PAGE_RES from the block_list in the way required for ApplyBoxes:
+ // All fuzzy spaces are removed, and all the words are maximally chopped.
+ PAGE_RES* SetupApplyBoxes(BLOCK_LIST *block_list);
+ // Tests the chopper by exhaustively running chop_one_blob.
+ // The word_res will contain filled chopped_word, seam_array, denorm,
+ // box_word and best_state for the maximally chopped word.
+ void MaximallyChopWord(BLOCK* block, ROW* row, WERD_RES* word_res);
+ // Gather consecutive blobs that match the given box into the best_state
+ // and corresponding correct_text.
+ // Fights over which box owns which blobs are settled by pre-chopping and
+ // applying the blobs to box or next_box with the least non-overlap.
+ // Returns false if the box was in error, which can only be caused by
+ // failing to find an appropriate blob for a box.
+ // This means that occasionally, blobs may be incorrectly segmented if the
+ // chopper fails to find a suitable chop point.
+ bool ResegmentCharBox(PAGE_RES* page_res,
+ const TBOX& box, const TBOX& next_box,
+ const char* correct_text);
+ // Consume all source blobs that strongly overlap the given box,
+ // putting them into a new word, with the correct_text label.
+ // Fights over which box owns which blobs are settled by
+ // applying the blobs to box or next_box with the least non-overlap.
+ // Returns false if the box was in error, which can only be caused by
+ // failing to find an overlapping blob for a box.
+ bool ResegmentWordBox(BLOCK_LIST *block_list,
+ const TBOX& box, const TBOX& next_box,
+ const char* correct_text);
+ // Resegments the words by running the classifier in an attempt to find the
+ // correct segmentation that produces the required string.
+ void ReSegmentByClassification(PAGE_RES* page_res);
+ // Converts the space-delimited string of utf8 text to a vector of UNICHAR_ID.
+ // Returns false if an invalid UNICHAR_ID is encountered.
+ bool ConvertStringToUnichars(const char* utf8,
+ GenericVector* class_ids);
+ // Resegments the word to achieve the target_text from the classifier.
+ // Returns false if the re-segmentation fails.
+ // Uses brute-force combination of upto kMaxGroupSize adjacent blobs, and
+ // applies a full search on the classifier results to find the best classified
+ // segmentation. As a compromise to obtain better recall, 1-1 ambigiguity
+ // substitutions ARE used.
+ bool FindSegmentation(const GenericVector& target_text,
+ WERD_RES* word_res);
+ // Recursive helper to find a match to the target_text (from text_index
+ // position) in the choices (from choices_pos position).
+ // Choices is an array of GenericVectors, of length choices_length, with each
+ // element representing a starting position in the word, and the
+ // GenericVector holding classification results for a sequence of consecutive
+ // blobs, with index 0 being a single blob, index 1 being 2 blobs etc.
+ void SearchForText(const GenericVector* choices,
+ int choices_pos, int choices_length,
+ const GenericVector& target_text,
+ int text_index,
+ float rating, GenericVector* segmentation,
+ float* best_rating, GenericVector* best_segmentation);
+ // Counts up the labelled words and the blobs within.
+ // Deletes all unused or emptied words, counting the unused ones.
+ // Resets W_BOL and W_EOL flags correctly.
+ // Builds the rebuild_word and rebuilds the box_word.
+ void TidyUp(PAGE_RES* page_res);
+ // Logs a bad box by line in the box file and box coords.
+ void ReportFailedBox(int boxfile_lineno, TBOX box, const char *box_ch,
+ const char *err_msg);
+ // Creates a fake best_choice entry in each WERD_RES with the correct text.
+ void CorrectClassifyWords(PAGE_RES* page_res);
+ // Call LearnWord to extract features for labelled blobs within each word.
+ // Features are written to the given filename.
+ void ApplyBoxTraining(const STRING& filename, PAGE_RES* page_res);
+
+ //// fixxht.cpp ///////////////////////////////////////////////////////
+ // Returns the number of misfit blob tops in this word.
+ int CountMisfitTops(WERD_RES *word_res);
+ // Returns a new x-height in pixels (original image coords) that is
+ // maximally compatible with the result in word_res.
+ // Returns 0.0f if no x-height is found that is better than the current
+ // estimate.
+ float ComputeCompatibleXheight(WERD_RES *word_res);
//// Data members ///////////////////////////////////////////////////////
+ // TODO(ocr-team): Remove obsolete parameters.
BOOL_VAR_H(tessedit_resegment_from_boxes, false,
"Take segmentation and labeling from box file");
+ BOOL_VAR_H(tessedit_resegment_from_line_boxes, false,
+ "Conversion of word/line box file to char box file");
BOOL_VAR_H(tessedit_train_from_boxes, false,
"Generate training data from boxed chars");
+ BOOL_VAR_H(tessedit_make_boxes_from_boxes, false,
+ "Generate more boxes from boxed chars");
BOOL_VAR_H(tessedit_dump_pageseg_images, false,
- "Dump itermediate images made during page segmentation");
- INT_VAR_H(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_VAR_H(tessedit_accuracyvspeed, 0,
- "Accuracy V Speed tradeoff: 0 fastest, 100 most accurate"
- " (Values from AccuracyVSpeed enum in baseapi.h)");
- BOOL_VAR_H(tessedit_train_from_boxes_word_level, false,
- "Generate training data from boxed chars at word level.");
+ "Dump intermediate images made during page segmentation");
+ INT_VAR_H(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)");
+ INT_VAR_H(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)");
STRING_VAR_H(tessedit_char_blacklist, "",
"Blacklist of chars not to recognize");
STRING_VAR_H(tessedit_char_whitelist, "",
"Whitelist of chars to recognize");
- BOOL_VAR_H(global_tessedit_ambigs_training, false,
+ BOOL_VAR_H(tessedit_ambigs_training, false,
"Perform training for ambiguities");
+ STRING_VAR_H(tessedit_write_params_to_file, "",
+ "Write all parameters to the given file.");
+ BOOL_VAR_H(tessedit_adapt_to_char_fragments, true,
+ "Adapt to words that contain "
+ " a character composed form fragments");
+ BOOL_VAR_H(tessedit_adaption_debug, false,
+ "Generate and print debug information for adaption");
+ BOOL_VAR_H(applybox_rebalance, true, "Drop dead");
+ INT_VAR_H(applybox_debug, 1, "Debug level");
+ INT_VAR_H(applybox_page, 0, "Page number to apply boxes from");
+ STRING_VAR_H(applybox_test_exclusions, "", "Chars ignored for testing");
+ double_VAR_H(applybox_error_band, 0.15, "Err band as fract of xht");
+ STRING_VAR_H(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");
+ BOOL_VAR_H(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.");
+ BOOL_VAR_H(applybox_learn_ngrams_mode, false,
+ "Each bounding box is assumed to contain ngrams. Only"
+ " learn the ngrams whose outlines overlap horizontally.");
+ BOOL_VAR_H(tessedit_print_text, false, "Write text to stdout");
+ BOOL_VAR_H(tessedit_draw_words, false, "Draw source words");
+ BOOL_VAR_H(tessedit_draw_outwords, false, "Draw output words");
+ BOOL_VAR_H(tessedit_training_tess, false, "Call Tess to learn blobs");
+ BOOL_VAR_H(tessedit_dump_choices, false, "Dump char choices");
+ BOOL_VAR_H(tessedit_fix_fuzzy_spaces, true,
+ "Try to improve fuzzy spaces");
+ BOOL_VAR_H(tessedit_unrej_any_wd, false,
+ "Dont bother with word plausibility");
+ BOOL_VAR_H(tessedit_fix_hyphens, true, "Crunch double hyphens?");
+ BOOL_VAR_H(tessedit_redo_xheight, true, "Check/Correct x-height");
+ BOOL_VAR_H(tessedit_enable_doc_dict, true,
+ "Add words to the document dictionary");
+ BOOL_VAR_H(tessedit_debug_fonts, false, "Output font info per char");
+ BOOL_VAR_H(tessedit_debug_block_rejection, false, "Block and Row stats");
+ INT_VAR_H(debug_x_ht_level, 0, "Reestimate debug");
+ BOOL_VAR_H(debug_acceptable_wds, false, "Dump word pass/fail chk");
+ STRING_VAR_H(chs_leading_punct, "('`\"", "Leading punctuation");
+ STRING_VAR_H(chs_trailing_punct1, ").,;:?!", "1st Trailing punctuation");
+ STRING_VAR_H(chs_trailing_punct2, ")'`\"", "2nd Trailing punctuation");
+ double_VAR_H(quality_rej_pc, 0.08, "good_quality_doc lte rejection limit");
+ double_VAR_H(quality_blob_pc, 0.0, "good_quality_doc gte good blobs limit");
+ double_VAR_H(quality_outline_pc, 1.0,
+ "good_quality_doc lte outline error limit");
+ double_VAR_H(quality_char_pc, 0.95, "good_quality_doc gte good char limit");
+ INT_VAR_H(quality_min_initial_alphas_reqd, 2, "alphas in a good word");
+ BOOL_VAR_H(tessedit_tess_adapt_to_rejmap, false,
+ "Use reject map to control Tesseract adaption");
+ INT_VAR_H(tessedit_tess_adaption_mode, 0x27,
+ "Adaptation decision algorithm for tess");
+ BOOL_VAR_H(tessedit_minimal_rej_pass1, false,
+ "Do minimal rejection on pass 1 output");
+ BOOL_VAR_H(tessedit_test_adaption, false, "Test adaption criteria");
+ BOOL_VAR_H(tessedit_matcher_log, false, "Log matcher activity");
+ INT_VAR_H(tessedit_test_adaption_mode, 3,
+ "Adaptation decision algorithm for tess");
+ BOOL_VAR_H(save_best_choices, false,
+ "Save the results of the recognition step"
+ " (blob_choices) within the corresponding WERD_CHOICE");
+ BOOL_VAR_H(test_pt, false, "Test for point");
+ double_VAR_H(test_pt_x, 99999.99, "xcoord");
+ double_VAR_H(test_pt_y, 99999.99, "ycoord");
+ INT_VAR_H(cube_debug_level, 1, "Print cube debug info.");
+ STRING_VAR_H(outlines_odd, "%| ", "Non standard number of outlines");
+ STRING_VAR_H(outlines_2, "ij!?%\":;", "Non standard number of outlines");
+ BOOL_VAR_H(docqual_excuse_outline_errs, false,
+ "Allow outline errs in unrejection?");
+ BOOL_VAR_H(tessedit_good_quality_unrej, true,
+ "Reduce rejection on good docs");
+ BOOL_VAR_H(tessedit_use_reject_spaces, true, "Reject spaces?");
+ double_VAR_H(tessedit_reject_doc_percent, 65.00,
+ "%rej allowed before rej whole doc");
+ double_VAR_H(tessedit_reject_block_percent, 45.00,
+ "%rej allowed before rej whole block");
+ double_VAR_H(tessedit_reject_row_percent, 40.00,
+ "%rej allowed before rej whole row");
+ double_VAR_H(tessedit_whole_wd_rej_row_percent, 70.00,
+ "Number of row rejects in whole word rejects"
+ "which prevents whole row rejection");
+ BOOL_VAR_H(tessedit_preserve_blk_rej_perfect_wds, true,
+ "Only rej partially rejected words in block rejection");
+ BOOL_VAR_H(tessedit_preserve_row_rej_perfect_wds, true,
+ "Only rej partially rejected words in row rejection");
+ BOOL_VAR_H(tessedit_dont_blkrej_good_wds, false,
+ "Use word segmentation quality metric");
+ BOOL_VAR_H(tessedit_dont_rowrej_good_wds, false,
+ "Use word segmentation quality metric");
+ INT_VAR_H(tessedit_preserve_min_wd_len, 2,
+ "Only preserve wds longer than this");
+ BOOL_VAR_H(tessedit_row_rej_good_docs, true,
+ "Apply row rejection to good docs");
+ double_VAR_H(tessedit_good_doc_still_rowrej_wd, 1.1,
+ "rej good doc wd if more than this fraction rejected");
+ BOOL_VAR_H(tessedit_reject_bad_qual_wds, true,
+ "Reject all bad quality wds");
+ BOOL_VAR_H(tessedit_debug_doc_rejection, false, "Page stats");
+ BOOL_VAR_H(tessedit_debug_quality_metrics, false,
+ "Output data to debug file");
+ BOOL_VAR_H(bland_unrej, false, "unrej potential with no chekcs");
+ double_VAR_H(quality_rowrej_pc, 1.1,
+ "good_quality_doc gte good char limit");
+ BOOL_VAR_H(unlv_tilde_crunching, true,
+ "Mark v.bad words for tilde crunch");
+ BOOL_VAR_H(crunch_early_merge_tess_fails, true, "Before word crunch?");
+ BOOL_VAR_H(crunch_early_convert_bad_unlv_chs, false, "Take out ~^ early?");
+ double_VAR_H(crunch_terrible_rating, 80.0, "crunch rating lt this");
+ BOOL_VAR_H(crunch_terrible_garbage, true, "As it says");
+ double_VAR_H(crunch_poor_garbage_cert, -9.0,
+ "crunch garbage cert lt this");
+ double_VAR_H(crunch_poor_garbage_rate, 60, "crunch garbage rating lt this");
+ double_VAR_H(crunch_pot_poor_rate, 40, "POTENTIAL crunch rating lt this");
+ double_VAR_H(crunch_pot_poor_cert, -8.0, "POTENTIAL crunch cert lt this");
+ BOOL_VAR_H(crunch_pot_garbage, true, "POTENTIAL crunch garbage");
+ double_VAR_H(crunch_del_rating, 60, "POTENTIAL crunch rating lt this");
+ double_VAR_H(crunch_del_cert, -10.0, "POTENTIAL crunch cert lt this");
+ double_VAR_H(crunch_del_min_ht, 0.7, "Del if word ht lt xht x this");
+ double_VAR_H(crunch_del_max_ht, 3.0, "Del if word ht gt xht x this");
+ double_VAR_H(crunch_del_min_width, 3.0, "Del if word width lt xht x this");
+ double_VAR_H(crunch_del_high_word, 1.5,
+ "Del if word gt xht x this above bl");
+ double_VAR_H(crunch_del_low_word, 0.5, "Del if word gt xht x this below bl");
+ double_VAR_H(crunch_small_outlines_size, 0.6, "Small if lt xht x this");
+ INT_VAR_H(crunch_rating_max, 10, "For adj length in rating per ch");
+ INT_VAR_H(crunch_pot_indicators, 1, "How many potential indicators needed");
+ BOOL_VAR_H(crunch_leave_ok_strings, true, "Dont touch sensible strings");
+ BOOL_VAR_H(crunch_accept_ok, true, "Use acceptability in okstring");
+ BOOL_VAR_H(crunch_leave_accept_strings, false,
+ "Dont pot crunch sensible strings");
+ BOOL_VAR_H(crunch_include_numerals, false, "Fiddle alpha figures");
+ INT_VAR_H(crunch_leave_lc_strings, 4,
+ "Dont crunch words with long lower case strings");
+ INT_VAR_H(crunch_leave_uc_strings, 4,
+ "Dont crunch words with long lower case strings");
+ INT_VAR_H(crunch_long_repetitions, 3, "Crunch words with long repetitions");
+ INT_VAR_H(crunch_debug, 0, "As it says");
+ INT_VAR_H(fixsp_non_noise_limit, 1,
+ "How many non-noise blbs either side?");
+ double_VAR_H(fixsp_small_outlines_size, 0.28, "Small if lt xht x this");
+ BOOL_VAR_H(tessedit_prefer_joined_punct, false, "Reward punctation joins");
+ INT_VAR_H(fixsp_done_mode, 1, "What constitues done for spacing");
+ INT_VAR_H(debug_fix_space_level, 0, "Contextual fixspace debug");
+ STRING_VAR_H(numeric_punctuation, ".,",
+ "Punct. chs expected WITHIN numbers");
+ INT_VAR_H(x_ht_acceptance_tolerance, 8,
+ "Max allowed deviation of blob top outside of font data");
+ INT_VAR_H(x_ht_min_change, 8, "Min change in xht before actually trying it");
+ BOOL_VAR_H(tessedit_write_block_separators, false,
+ "Write block separators in output");
+ BOOL_VAR_H(tessedit_write_raw_output, false,
+ "Write raw stuff to name.raw");
+ BOOL_VAR_H(tessedit_write_output, false, "Write text to name.txt");
+ BOOL_VAR_H(tessedit_write_ratings, false,
+ "Return ratings in IPEOCRAPI data");
+ BOOL_VAR_H(tessedit_write_rep_codes, false,
+ "Write repetition char code");
+ BOOL_VAR_H(tessedit_write_unlv, false, "Write .unlv output file");
+ BOOL_VAR_H(tessedit_create_hocr, false, "Write .html hOCR output file");
+ STRING_VAR_H(unrecognised_char, "|",
+ "Output char for unidentified blobs");
+ INT_VAR_H(suspect_level, 99, "Suspect marker level");
+ INT_VAR_H(suspect_space_level, 100,
+ "Min suspect level for rejecting spaces");
+ INT_VAR_H(suspect_short_words, 2,
+ "Dont Suspect dict wds longer than this");
+ BOOL_VAR_H(suspect_constrain_1Il, false, "UNLV keep 1Il chars rejected");
+ double_VAR_H(suspect_rating_per_ch, 999.9, "Dont touch bad rating limit");
+ double_VAR_H(suspect_accept_rating, -999.9, "Accept good rating limit");
+ BOOL_VAR_H(tessedit_minimal_rejection, false, "Only reject tess failures");
+ BOOL_VAR_H(tessedit_zero_rejection, false, "Dont reject ANYTHING");
+ BOOL_VAR_H(tessedit_word_for_word, false,
+ "Make output have exactly one word per WERD");
+ BOOL_VAR_H(tessedit_zero_kelvin_rejection, false,
+ "Dont reject ANYTHING AT ALL");
+ BOOL_VAR_H(tessedit_consistent_reps, true, "Force all rep chars the same");
+ INT_VAR_H(tessedit_reject_mode, 0, "Rejection algorithm");
+ INT_VAR_H(tessedit_ok_mode, 5, "Acceptance decision algorithm");
+ BOOL_VAR_H(tessedit_rejection_debug, false, "Adaption debug");
+ BOOL_VAR_H(tessedit_flip_0O, true, "Contextual 0O O0 flips");
+ double_VAR_H(tessedit_lower_flip_hyphen, 1.5,
+ "Aspect ratio dot/hyphen test");
+ double_VAR_H(tessedit_upper_flip_hyphen, 1.8,
+ "Aspect ratio dot/hyphen test");
+ BOOL_VAR_H(rej_trust_doc_dawg, false, "Use DOC dawg in 11l conf. detector");
+ BOOL_VAR_H(rej_1Il_use_dict_word, false, "Use dictword test");
+ BOOL_VAR_H(rej_1Il_trust_permuter_type, true, "Dont double check");
+ BOOL_VAR_H(rej_use_tess_accepted, true, "Individual rejection control");
+ BOOL_VAR_H(rej_use_tess_blanks, true, "Individual rejection control");
+ BOOL_VAR_H(rej_use_good_perm, true, "Individual rejection control");
+ BOOL_VAR_H(rej_use_sensible_wd, false, "Extend permuter check");
+ BOOL_VAR_H(rej_alphas_in_number_perm, false, "Extend permuter check");
+ double_VAR_H(rej_whole_of_mostly_reject_word_fract, 0.85, "if >this fract");
+ INT_VAR_H(tessedit_image_border, 2, "Rej blbs near image edge limit");
+ STRING_VAR_H(ok_repeated_ch_non_alphanum_wds, "-?*\075",
+ "Allow NN to unrej");
+ STRING_VAR_H(conflict_set_I_l_1, "Il1[]", "Il1 conflict set");
+ INT_VAR_H(min_sane_x_ht_pixels, 8, "Reject any x-ht lt or eq than this");
+ BOOL_VAR_H(tessedit_create_boxfile, false, "Output text with boxes");
+ BOOL_VAR_H(tessedit_read_image, true, "Ensure the image is read");
+ INT_VAR_H(tessedit_serial_unlv, 0,
+ "0->Whole page, 1->serial no adapt, 2->serial with adapt");
+ INT_VAR_H(tessedit_page_number, -1,
+ "-1 -> All pages, else specifc page to process");
+ BOOL_VAR_H(tessedit_write_images, false, "Capture the image from the IPE");
+ BOOL_VAR_H(interactive_mode, false, "Run interactively?");
+ STRING_VAR_H(file_type, ".tif", "Filename extension");
+ INT_VAR_H(testedit_match_debug, 0, "Integer match debug ctrl");
+ BOOL_VAR_H(tessedit_override_permuter, true, "According to dict_word");
+ INT_VAR_H(tessdata_manager_debug_level, 0,
+ "Debug level for TessdataManager functions.");
+ // Min acceptable orientation margin (difference in scores between top and 2nd
+ // choice in OSResults::orientations) to believe the page orientation.
+ double_VAR_H(min_orientation_margin, 12.0,
+ "Min acceptable orientation margin");
+
//// ambigsrecog.cpp /////////////////////////////////////////////////////////
- FILE *init_ambigs_training(const STRING &fname);
- void ambigs_training_segmented(const STRING &fname,
- PAGE_RES *page_res,
- volatile ETEXT_DESC *monitor,
- FILE *output_file);
- void ambigs_classify_and_output(PAGE_RES_IT *page_res_it,
+ FILE *init_recog_training(const STRING &fname);
+ void recog_training_segmented(const STRING &fname,
+ PAGE_RES *page_res,
+ volatile ETEXT_DESC *monitor,
+ FILE *output_file);
+ void ambigs_classify_and_output(WERD_RES *werd_res,
+ ROW_RES *row_res,
+ BLOCK_RES *block_res,
const char *label,
FILE *output_file);
+
+ inline CubeRecoContext *GetCubeRecoContext() { return cube_cntxt_; }
+
private:
+ // The filename of a backup config file. If not null, then we currently
+ // have a temporary debug config file loaded, and backup_config_file_
+ // will be loaded, and set to null when debug is complete.
+ const char* backup_config_file_;
+ // The filename of a config file to read when processing a debug word.
+ STRING word_config_;
Pix* pix_binary_;
+ Pix* pix_grey_;
+ // The boolean records if the currently set
+ // pix_binary_ member has been modified due to any processing so that this
+ // may hurt Cube's recognition phase.
+ bool orig_image_changed_;
+ // Page segmentation/layout
+ Textord textord_;
+ // True if the primary language uses right_to_left reading order.
+ bool right_to_left_;
FCOORD deskew_;
FCOORD reskew_;
- bool hindi_image_;
+ TesseractStats stats_;
+ // Cube objects.
+ CubeRecoContext* cube_cntxt_;
+ TesseractCubeCombiner *tess_cube_combiner_;
};
} // namespace tesseract
diff --git a/ccmain/tessio.h b/ccmain/tessio.h
deleted file mode 100644
index c48ec0f6b7..0000000000
--- a/ccmain/tessio.h
+++ /dev/null
@@ -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
-#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
diff --git a/ccmain/tessvars.cpp b/ccmain/tessvars.cpp
index 62bedf4aec..ee938010cf 100644
--- a/ccmain/tessvars.cpp
+++ b/ccmain/tessvars.cpp
@@ -17,22 +17,9 @@
*
**********************************************************************/
-#include "mfcpch.h"
-#include "tessvars.h"
-
-#define EXTERN
+#include
-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");
+#include "mfcpch.h"
+#include "tessvars.h"
-EXTERN IMAGE page_image; //image of page
-EXTERN FILE *debug_fp = stderr; //write debug stuff here
+FILE *debug_fp = stderr; // write debug stuff here
diff --git a/ccmain/tessvars.h b/ccmain/tessvars.h
index d0f0ec259b..eeaa358573 100644
--- a/ccmain/tessvars.h
+++ b/ccmain/tessvars.h
@@ -20,29 +20,10 @@
#ifndef TESSVARS_H
#define TESSVARS_H
-#include "varable.h"
+#include
+
#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
diff --git a/ccmain/tfacep.h b/ccmain/tfacep.h
index 6041f7bee6..80c9bd8fad 100644
--- a/ccmain/tfacep.h
+++ b/ccmain/tfacep.h
@@ -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
diff --git a/ccmain/tfacepp.cpp b/ccmain/tfacepp.cpp
index 57c085e4e5..72c3661baa 100644
--- a/ccmain/tfacepp.cpp
+++ b/ccmain/tfacepp.cpp
@@ -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 ());
- }
- 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());
+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());
}
- 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
-
- 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 {
- 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));
-
- outword = make_ed_word (tessword, word); // convert word
- if (outword == NULL) {
- outword = word->poly_copy (denorm->row ()->x_height ());
- }
- delete_word(tessword); // get rid of it
- word_length = outword->blob_list()->length(); // no of blobs
+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);
- // 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));
- }
- delete tess_ratings;
+ // 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;
- // 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);
+ 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(" ");
+ 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);
+ }
- // 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));
+ // 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");
}
- 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");
- }
+ 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);
+ }
+ 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());
}
-
- 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 ());
-
- 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;
+ ASSERT_HOST(best_end != NULL);
+
+ // 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(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
diff --git a/ccmain/tfacepp.h b/ccmain/tfacepp.h
index b72c8b7016..c9fd31a325 100644
--- a/ccmain/tfacepp.h
+++ b/ccmain/tfacepp.h
@@ -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
diff --git a/ccmain/thresholder.cpp b/ccmain/thresholder.cpp
index f647d22941..e8d9807f3c 100644
--- a/ccmain/thresholder.cpp
+++ b/ccmain/thresholder.cpp
@@ -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.
diff --git a/ccmain/thresholder.h b/ccmain/thresholder.h
index 7022a46b17..7d11f64528 100644
--- a/ccmain/thresholder.h
+++ b/ccmain/thresholder.h
@@ -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__
-
diff --git a/ccmain/tstruct.cpp b/ccmain/tstruct.cpp
index 4c15ca4529..9f6074e8d2 100644
--- a/ccmain/tstruct.cpp
+++ b/ccmain/tstruct.cpp
@@ -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(src_width / denorm->scale() + 0.5);
- src_height = static_cast(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(median_size.x()), tolerance) &&
- NearlyEqual(src_height, static_cast(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;
diff --git a/ccmain/tstruct.h b/ccmain/tstruct.h
index ccacaa86f3..204f94c53c 100644
--- a/ccmain/tstruct.h
+++ b/ccmain/tstruct.h
@@ -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
diff --git a/ccmain/varabled.h b/ccmain/varabled.h
deleted file mode 100644
index 0a5a034900..0000000000
--- a/ccmain/varabled.h
+++ /dev/null
@@ -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
diff --git a/ccmain/werdit.cpp b/ccmain/werdit.cpp
index e6fd01518a..34e0581760 100644
--- a/ccmain/werdit.cpp
+++ b/ccmain/werdit.cpp
@@ -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
-
- 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
- }
- }
+ 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 (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;
diff --git a/ccmain/werdit.h b/ccmain/werdit.h
index e509685fdd..b3f6181d6e 100644
--- a/ccmain/werdit.h
+++ b/ccmain/werdit.h
@@ -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
diff --git a/ccstruct/Makefile.am b/ccstruct/Makefile.am
index 49ba051e74..5b6ef71de4 100644
--- a/ccstruct/Makefile.am
+++ b/ccstruct/Makefile.am
@@ -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)
diff --git a/ccstruct/Makefile.in b/ccstruct/Makefile.in
index d101e209a3..6cb1deda83 100644
--- a/ccstruct/Makefile.in
+++ b/ccstruct/Makefile.in
@@ -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@
diff --git a/ccstruct/blobbox.cpp b/ccstruct/blobbox.cpp
index 1872cb0221..f8dff991ab 100644
--- a/ccstruct/blobbox.cpp
+++ b/ccstruct/blobbox.cpp
@@ -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(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(left),
+ static_cast(right), no_rotation,
+ bottom, top);
+ } else {
+ find_blob_limits(blob_ptr, static_cast(left),
+ static_cast(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
-
diff --git a/ccstruct/blobbox.h b/ccstruct/blobbox.h
index 79c2cc3013..63056af308 100644
--- a/ccstruct/blobbox.h
+++ b/ccstruct/blobbox.h
@@ -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(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(srcblob->area());
}
-
- void rotate_box(FCOORD vec) {
- box.rotate(vec);
+ static BLOBNBOX* RealBlob(C_OUTLINE* outline) {
+ C_BLOB* blob = new C_BLOB(outline);
+ return new BLOBNBOX(blob);
}
+
+ 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_;
+ }
+ 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 noise_flag() const {
- return noise_flag_;
+ 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(widths.median() + 0.5),
+ static_cast(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
diff --git a/ccstruct/blobs.cpp b/ccstruct/blobs.cpp
index 73fd09e11b..420e4e4d61 100644
--- a/ccstruct/blobs.cpp
+++ b/ccstruct/blobs.cpp
@@ -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(floor(pt->pos.x * rot.x() -
+ pt->pos.y * rot.y() + 0.5));
+ pt->pos.y = static_cast(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(floor(pt->pos.x * factor + 0.5));
+ pt->pos.y = static_cast(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(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(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;
-
- if (edgepts == NULL)
- return;
-
- this_edge = edgepts;
- do {
- next_edge = this_edge->next;
- oldedgept(this_edge);
- this_edge = 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;
+
+ TESSLINE *outline = blob->outlines;
+ blob->outlines = NULL;
+ int location_prod = CROSS(location, vertical);
+
+ 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;
}
diff --git a/ccstruct/blobs.h b/ccstruct/blobs.h
index 16c64b423a..c55ed56890 100644
--- a/ccstruct/blobs.h
+++ b/ccstruct/blobs.h
@@ -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);
-
-void delete_word(TWERD *word);
-
-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));
+bool divisible_blob(TBLOB *blob, bool italic_blob, TPOINT* location);
-void delete_word
- _ARGS((TWERD *word));
+void divide_blobs(TBLOB *blob, TBLOB *other_blob, bool italic_blob,
+ const TPOINT& location);
-void delete_edgepts
- _ARGS((EDGEPT *edgepts));
-#undef _ARGS
-*/
#endif
diff --git a/ccstruct/blread.h b/ccstruct/blread.h
index 5500a76ffa..3969d00230 100644
--- a/ccstruct/blread.h
+++ b/ccstruct/blread.h
@@ -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
diff --git a/ccstruct/boxword.cpp b/ccstruct/boxword.cpp
new file mode 100644
index 0000000000..4ce33e4609
--- /dev/null
+++ b/ccstruct/boxword.cpp
@@ -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(floor(denormed.x() + 0.5)));
+ pos.set_y(static_cast(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(original_box.left(), box.left(), kBoxClipTolerance))
+ box.set_left(original_box.left());
+ if (NearlyEqual(original_box.right(), box.right(),
+ kBoxClipTolerance))
+ box.set_right(original_box.right());
+ if (NearlyEqual(original_box.top(), box.top(), kBoxClipTolerance))
+ box.set_top(original_box.top());
+ if (NearlyEqual(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* 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.
+
+
diff --git a/ccstruct/boxword.h b/ccstruct/boxword.h
new file mode 100644
index 0000000000..b8fb18ef9e
--- /dev/null
+++ b/ccstruct/boxword.h
@@ -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* 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 boxes_;
+};
+
+} // namespace tesseract.
+
+
+#endif // TESSERACT_CSTRUCT_BOXWORD_H__
diff --git a/ccstruct/callcpp.cpp b/ccstruct/callcpp.cpp
index 9894bcead0..c0cc089735 100644
--- a/ccstruct/callcpp.cpp
+++ b/ccstruct/callcpp.cpp
@@ -18,20 +18,18 @@
**********************************************************************/
#include "mfcpch.h"
-#include "errcode.h"
+#include "errcode.h"
#ifdef __UNIX__
-#include
+#include
#include
#endif
-#include
-#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
+#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;
}
-
-
-//};
diff --git a/ccstruct/ccstruct.cpp b/ccstruct/ccstruct.cpp
index a3934d9ce6..013463d55d 100644
--- a/ccstruct/ccstruct.cpp
+++ b/ccstruct/ccstruct.cpp
@@ -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) {
}
diff --git a/ccstruct/ccstruct.h b/ccstruct/ccstruct.h
index b143c19983..9564c39f3e 100644
--- a/ccstruct/ccstruct.h
+++ b/ccstruct/ccstruct.h
@@ -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__
diff --git a/ccstruct/coutln.cpp b/ccstruct/coutln.cpp
index 04f0af11f2..68b2460ff2 100644
--- a/ccstruct/coutln.cpp
+++ b/ccstruct/coutln.cpp
@@ -18,11 +18,12 @@
**********************************************************************/
#include "mfcpch.h"
-#include
+#include
#ifdef __UNIX__
-#include
+#include
#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
diff --git a/ccstruct/coutln.h b/ccstruct/coutln.h
index 1174e6ae0a..4aa46ebb72 100644
--- a/ccstruct/coutln.h
+++ b/ccstruct/coutln.h
@@ -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
diff --git a/ccstruct/detlinefit.cpp b/ccstruct/detlinefit.cpp
index 2fed662209..1bd6533563 100644
--- a/ccstruct/detlinefit.cpp
+++ b/ccstruct/detlinefit.cpp
@@ -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(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(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(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(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(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(p1);
diff --git a/ccstruct/detlinefit.h b/ccstruct/detlinefit.h
index 6a2279b4c4..9f43098ca5 100644
--- a/ccstruct/detlinefit.h
+++ b/ccstruct/detlinefit.h
@@ -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);
diff --git a/ccstruct/dppoint.cpp b/ccstruct/dppoint.cpp
new file mode 100644
index 0000000000..7325c9cb1e
--- /dev/null
+++ b/ccstruct/dppoint.cpp
@@ -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.
+
diff --git a/ccstruct/dppoint.h b/ccstruct/dppoint.h
new file mode 100644
index 0000000000..fd87bb9127
--- /dev/null
+++ b/ccstruct/dppoint.h
@@ -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__
+
diff --git a/ccstruct/labls.cpp b/ccstruct/labls.cpp
deleted file mode 100644
index af01c30c3c..0000000000
--- a/ccstruct/labls.cpp
+++ /dev/null
@@ -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
-};
diff --git a/ccstruct/labls.h b/ccstruct/labls.h
deleted file mode 100644
index ece7190a45..0000000000
--- a/ccstruct/labls.h
+++ /dev/null
@@ -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
diff --git a/ccstruct/linlsq.cpp b/ccstruct/linlsq.cpp
index 1f7bbd0c37..ad8b64e371 100644
--- a/ccstruct/linlsq.cpp
+++ b/ccstruct/linlsq.cpp
@@ -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;
diff --git a/ccstruct/linlsq.h b/ccstruct/linlsq.h
index 13a5db6937..ffe6900ac1 100644
--- a/ccstruct/linlsq.h
+++ b/ccstruct/linlsq.h
@@ -22,7 +22,7 @@
#include "points.h"
#include "mod128.h"
-#include "varable.h"
+#include "params.h"
class LLSQ
{
diff --git a/ccstruct/lmedsq.cpp b/ccstruct/lmedsq.cpp
deleted file mode 100644
index 544a57ea80..0000000000
--- a/ccstruct/lmedsq.cpp
+++ /dev/null
@@ -1,458 +0,0 @@
-/**********************************************************************
- * File: lmedsq.cpp (Formerly lms.c)
- * Description: Code for the LMS class.
- * Author: Ray Smith
- * Created: Fri Aug 7 09:30:53 BST 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
-#include "statistc.h"
-#include "memry.h"
-#include "statistc.h"
-#include "lmedsq.h"
-
-// Include automatically generated configuration file if running autoconf.
-#ifdef HAVE_CONFIG_H
-#include "config_auto.h"
-#endif
-
-#define EXTERN
-
-EXTERN INT_VAR (lms_line_trials, 12, "Number of linew fits to do");
-#define SEED1 0x1234 //default seeds
-#define SEED2 0x5678
-#define SEED3 0x9abc
-#define LMS_MAX_FAILURES 3
-
-#ifndef __UNIX__
-uinT32 nrand48( //get random number
- uinT16 *seeds //seeds to use
- ) {
- static uinT32 seed = 0; //only seed
-
- if (seed == 0) {
- seed = seeds[0] ^ (seeds[1] << 8) ^ (seeds[2] << 16);
- srand(seed);
- }
- //make 32 bit one
- return rand () | (rand () << 16);
-}
-#endif
-
-/**********************************************************************
- * LMS::LMS
- *
- * Construct a LMS class, given the max no of samples to be given
- **********************************************************************/
-
-LMS::LMS ( //constructor
-inT32 size //samplesize
-):samplesize (size) {
- samplecount = 0;
- a = 0;
- m = 0.0f;
- c = 0.0f;
- samples = (FCOORD *) alloc_mem (size * sizeof (FCOORD));
- errors = (float *) alloc_mem (size * sizeof (float));
- line_error = 0.0f;
- fitted = FALSE;
-}
-
-
-/**********************************************************************
- * LMS::~LMS
- *
- * Destruct a LMS class.
- **********************************************************************/
-
-LMS::~LMS ( //constructor
-) {
- free_mem(samples);
- free_mem(errors);
-}
-
-
-/**********************************************************************
- * LMS::clear
- *
- * Clear samples from array.
- **********************************************************************/
-
-void LMS::clear() { //clear sample
- samplecount = 0;
- fitted = FALSE;
-}
-
-
-/**********************************************************************
- * LMS::add
- *
- * Add another sample. More than the constructed number will be ignored.
- **********************************************************************/
-
-void LMS::add( //add sample
- FCOORD sample //sample coords
- ) {
- if (samplecount < samplesize)
- //save it
- samples[samplecount++] = sample;
- fitted = FALSE;
-}
-
-
-/**********************************************************************
- * LMS::fit
- *
- * Fit a line to the given sample points.
- **********************************************************************/
-
-void LMS::fit( //fit sample
- float &out_m, //output line
- float &out_c) {
- inT32 index; //of median
- inT32 trials; //no of medians
- float test_m, test_c; //candidate line
- float test_error; //error of test line
-
- switch (samplecount) {
- case 0:
- m = 0.0f; //no info
- c = 0.0f;
- line_error = 0.0f;
- break;
-
- case 1:
- m = 0.0f;
- c = samples[0].y (); //horiz thru pt
- line_error = 0.0f;
- break;
-
- case 2:
- if (samples[0].x () != samples[1].x ()) {
- m = (samples[1].y () - samples[0].y ())
- / (samples[1].x () - samples[0].x ());
- c = samples[0].y () - m * samples[0].x ();
- }
- else {
- m = 0.0f;
- c = (samples[0].y () + samples[1].y ()) / 2;
- }
- line_error = 0.0f;
- break;
-
- default:
- pick_line(m, c); //use pts at random
- compute_errors(m, c); //from given line
- index = choose_nth_item (samplecount / 2, errors, samplecount);
- line_error = errors[index];
- for (trials = 1; trials < lms_line_trials; trials++) {
- //random again
- pick_line(test_m, test_c);
- compute_errors(test_m, test_c);
- index = choose_nth_item (samplecount / 2, errors, samplecount);
- test_error = errors[index];
- if (test_error < line_error) {
- //find least median
- line_error = test_error;
- m = test_m;
- c = test_c;
- }
- }
- }
- fitted = TRUE;
- out_m = m;
- out_c = c;
- a = 0;
-}
-
-
-/**********************************************************************
- * LMS::fit_quadratic
- *
- * Fit a quadratic to the given sample points.
- **********************************************************************/
-
-void LMS::fit_quadratic( //fit sample
- float outlier_threshold, //min outlier size
- double &out_a, //x squared
- float &out_b, //output line
- float &out_c) {
- inT32 trials; //no of medians
- double test_a;
- float test_b, test_c; //candidate line
- float test_error; //error of test line
-
- if (samplecount < 3) {
- out_a = 0;
- fit(out_b, out_c);
- return;
- }
- pick_quadratic(a, m, c);
- line_error = compute_quadratic_errors (outlier_threshold, a, m, c);
- for (trials = 1; trials < lms_line_trials * 2; trials++) {
- pick_quadratic(test_a, test_b, test_c);
- test_error = compute_quadratic_errors (outlier_threshold,
- test_a, test_b, test_c);
- if (test_error < line_error) {
- line_error = test_error; //find least median
- a = test_a;
- m = test_b;
- c = test_c;
- }
- }
- fitted = TRUE;
- out_a = a;
- out_b = m;
- out_c = c;
-}
-
-
-/**********************************************************************
- * LMS::constrained_fit
- *
- * Fit a line to the given sample points.
- * The line must have the given gradient.
- **********************************************************************/
-
-void LMS::constrained_fit( //fit sample
- float fixed_m, //forced gradient
- float &out_c) {
- inT32 index; //of median
- inT32 trials; //no of medians
- float test_c; //candidate line
- static uinT16 seeds[3] = { SEED1, SEED2, SEED3 };
- //for nrand
- float test_error; //error of test line
-
- m = fixed_m;
- switch (samplecount) {
- case 0:
- c = 0.0f;
- line_error = 0.0f;
- break;
-
- case 1:
- //horiz thru pt
- c = samples[0].y () - m * samples[0].x ();
- line_error = 0.0f;
- break;
-
- case 2:
- c = (samples[0].y () + samples[1].y ()
- - m * (samples[0].x () + samples[1].x ())) / 2;
- line_error = m * samples[0].x () + c - samples[0].y ();
- line_error *= line_error;
- break;
-
- default:
- index = (inT32) nrand48 (seeds) % samplecount;
- //compute line
- c = samples[index].y () - m * samples[index].x ();
- compute_errors(m, c); //from given line
- index = choose_nth_item (samplecount / 2, errors, samplecount);
- line_error = errors[index];
- for (trials = 1; trials < lms_line_trials; trials++) {
- index = (inT32) nrand48 (seeds) % samplecount;
- test_c = samples[index].y () - m * samples[index].x ();
- //compute line
- compute_errors(m, test_c);
- index = choose_nth_item (samplecount / 2, errors, samplecount);
- test_error = errors[index];
- if (test_error < line_error) {
- //find least median
- line_error = test_error;
- c = test_c;
- }
- }
- }
- fitted = TRUE;
- out_c = c;
- a = 0;
-}
-
-
-/**********************************************************************
- * LMS::pick_line
- *
- * Fit a line to a random pair of sample points.
- **********************************************************************/
-
-void LMS::pick_line( //fit sample
- float &line_m, //output gradient
- float &line_c) {
- inT16 trial_count; //no of attempts
- static uinT16 seeds[3] = { SEED1, SEED2, SEED3 };
- //for nrand
- inT32 index1; //picked point
- inT32 index2; //picked point
-
- trial_count = 0;
- do {
- index1 = (inT32) nrand48 (seeds) % samplecount;
- index2 = (inT32) nrand48 (seeds) % samplecount;
- line_m = samples[index2].x () - samples[index1].x ();
- trial_count++;
- }
- while (line_m == 0 && trial_count < LMS_MAX_FAILURES);
- if (line_m == 0) {
- line_c = (samples[index2].y () + samples[index1].y ()) / 2;
- }
- else {
- line_m = (samples[index2].y () - samples[index1].y ()) / line_m;
- line_c = samples[index1].y () - samples[index1].x () * line_m;
- }
-}
-
-
-/**********************************************************************
- * LMS::pick_quadratic
- *
- * Fit a quadratic to a random triplet of sample points.
- **********************************************************************/
-
-void LMS::pick_quadratic( //fit sample
- double &line_a, //x suaread
- float &line_m, //output gradient
- float &line_c) {
- inT16 trial_count; //no of attempts
- static uinT16 seeds[3] = { SEED1, SEED2, SEED3 };
- //for nrand
- inT32 index1; //picked point
- inT32 index2; //picked point
- inT32 index3;
- FCOORD x1x2; //vector
- FCOORD x1x3;
- FCOORD x3x2;
- double bottom; //of a
-
- trial_count = 0;
- do {
- if (trial_count >= LMS_MAX_FAILURES - 1) {
- index1 = 0;
- index2 = samplecount / 2;
- index3 = samplecount - 1;
- }
- else {
- index1 = (inT32) nrand48 (seeds) % samplecount;
- index2 = (inT32) nrand48 (seeds) % samplecount;
- index3 = (inT32) nrand48 (seeds) % samplecount;
- }
- x1x2 = samples[index2] - samples[index1];
- x1x3 = samples[index3] - samples[index1];
- x3x2 = samples[index2] - samples[index3];
- bottom = x1x2.x () * x1x3.x () * x3x2.x ();
- trial_count++;
- }
- while (bottom == 0 && trial_count < LMS_MAX_FAILURES);
- if (bottom == 0) {
- line_a = 0;
- pick_line(line_m, line_c);
- }
- else {
- line_a = x1x3 * x1x2 / bottom;
- line_m = x1x2.y () - line_a * x1x2.x ()
- * (samples[index2].x () + samples[index1].x ());
- line_m /= x1x2.x ();
- line_c = samples[index1].y () - samples[index1].x ()
- * (samples[index1].x () * line_a + line_m);
- }
-}
-
-
-/**********************************************************************
- * LMS::compute_errors
- *
- * Compute the squared error from all the points.
- **********************************************************************/
-
-void LMS::compute_errors( //fit sample
- float line_m, //input gradient
- float line_c) {
- inT32 index; //picked point
-
- for (index = 0; index < samplecount; index++) {
- errors[index] =
- line_m * samples[index].x () + line_c - samples[index].y ();
- errors[index] *= errors[index];
- }
-}
-
-
-/**********************************************************************
- * LMS::compute_quadratic_errors
- *
- * Compute the squared error from all the points.
- **********************************************************************/
-
-float LMS::compute_quadratic_errors( //fit sample
- float outlier_threshold, //min outlier
- double line_a,
- float line_m, //input gradient
- float line_c) {
- inT32 outlier_count; //total outliers
- inT32 index; //picked point
- inT32 error_count; //no in total
- double total_error; //summed squares
-
- total_error = 0;
- outlier_count = 0;
- error_count = 0;
- for (index = 0; index < samplecount; index++) {
- errors[error_count] = line_c + samples[index].x ()
- * (line_m + samples[index].x () * line_a) - samples[index].y ();
- errors[error_count] *= errors[error_count];
- if (errors[error_count] > outlier_threshold) {
- outlier_count++;
- errors[samplecount - outlier_count] = errors[error_count];
- }
- else {
- total_error += errors[error_count++];
- }
- }
- if (outlier_count * 3 < error_count)
- return total_error / error_count;
- else {
- index = choose_nth_item (outlier_count / 2,
- errors + samplecount - outlier_count,
- outlier_count);
- //median outlier
- return errors[samplecount - outlier_count + index];
- }
-}
-
-
-/**********************************************************************
- * LMS::plot
- *
- * Plot the fitted line of a LMS.
- **********************************************************************/
-
-#ifndef GRAPHICS_DISABLED
-void LMS::plot( //plot fit
- ScrollView* win, //window
- ScrollView::Color colour //colour to draw in
- ) {
- if (fitted) {
- win->Pen(colour);
- win->SetCursor(samples[0].x (),
- c + samples[0].x () * (m + samples[0].x () * a));
- win->DrawTo(samples[samplecount - 1].x (),
- c + samples[samplecount - 1].x () * (m +
- samples[samplecount -
- 1].x () * a));
- }
-}
-#endif
diff --git a/ccstruct/lmedsq.h b/ccstruct/lmedsq.h
deleted file mode 100644
index cf12f9766d..0000000000
--- a/ccstruct/lmedsq.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/**********************************************************************
- * File: lmedsq.h (Formerly lms.h)
- * Description: Code for the LMS class.
- * Author: Ray Smith
- * Created: Fri Aug 7 09:30:53 BST 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.
- *
- **********************************************************************/
-
-#ifndef LMEDSQ_H
-#define LMEDSQ_H
-
-#include "points.h"
-#include "varable.h"
-#include "scrollview.h"
-#include "notdll.h"
-
-class LMS
-{
- public:
- LMS( //constructor
- inT32 size); //no of samples
- ~LMS (); //destructor
- void clear(); //clear samples
- void add( //add sample
- FCOORD sample); //sample coords
- void fit( //generate fit
- float &m, //output line
- float &c);
- void constrained_fit( //fixed gradient
- float fixed_m, //forced gradient
- float &out_c); //output line
- void fit_quadratic( //easy quadratic
- float outlier_threshold, //min outlier
- double &a, //x squared
- float &b, //x
- float &c); //constant
- void plot( //plot fit
- ScrollView* win, //window
- ScrollView::Color colour); //colour to draw in
- float error() { //get error
- return fitted ? line_error : -1;
- }
-
- private:
-
- void pick_line( //random choice
- float &m, //output line
- float &c);
- void pick_quadratic( //random choice
- double &a, //output curve
- float &b,
- float &c);
- void compute_errors( //find errors
- float m, //from line
- float c);
- //find errors
- float compute_quadratic_errors(float outlier_threshold, //min outlier
- double a, //from curve
- float m,
- float c);
-
- BOOL8 fitted; //line parts valid
- inT32 samplesize; //max samples
- inT32 samplecount; //current sample size
- FCOORD *samples; //array of samples
- float *errors; //error distances
- double a; //x squared
- float m; //line gradient
- float c;
- float line_error; //error of fit
-};
-extern INT_VAR_H (lms_line_trials, 12, "Number of linew fits to do");
-#endif
diff --git a/wordrec/matrix.cpp b/ccstruct/matrix.cpp
similarity index 61%
rename from wordrec/matrix.cpp
rename to ccstruct/matrix.cpp
index cee6b96feb..82b27d7e3b 100644
--- a/wordrec/matrix.cpp
+++ b/ccstruct/matrix.cpp
@@ -27,35 +27,34 @@
----------------------------------------------------------------------*/
#include "matrix.h"
+#include "callcpp.h"
#include "ratngs.h"
+#include "tprintf.h"
#include "unicharset.h"
-#include "callcpp.h"
// Print the best guesses out of the match rating matrix.
-void MATRIX::print(const UNICHARSET ¤t_unicharset) {
- cprintf("Ratings Matrix (top choices)\n");
-
- /* Do each diagonal */
- for (int spread = 0; spread < this->dimension(); spread++) {
- /* For each spot */
- for (int x = 0; x < this->dimension() - spread; x++) {
- /* Process one square */
- BLOB_CHOICE_LIST *rating = this->get(x, x + spread);
+void MATRIX::print(const UNICHARSET &unicharset) {
+ tprintf("Ratings Matrix (top choices)\n");
+ int row, col;
+ for (col = 0; col < this->dimension(); ++col) tprintf("\t%d", col);
+ tprintf("\n");
+ for (row = 0; row < this->dimension(); ++row) {
+ for (col = 0; col <= row; ++col) {
+ if (col == 0) tprintf("%d\t", row);
+ BLOB_CHOICE_LIST *rating = this->get(col, row);
if (rating != NOT_CLASSIFIED) {
- cprintf("\t[%d,%d] : ", x, x + spread);
- // Print first 3 BLOB_CHOICES from ratings.
- BLOB_CHOICE_IT rating_it;
- rating_it.set_to_list(rating);
- int count = 0;
- for (rating_it.mark_cycle_pt();
- count < 3 && !rating_it.cycled_list();
- ++count, rating_it.forward()) {
- UNICHAR_ID unichar_id = rating_it.data()->unichar_id();
- cprintf("%-10s%4.0f%s", current_unicharset.id_to_unichar(unichar_id),
- rating_it.data()->rating(),
- (!rating_it.at_last() && count+1 < 3) ? "\t|\t" : "\n");
+ BLOB_CHOICE_IT b_it(rating);
+ int counter = 0;
+ for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
+ tprintf("%s ", unicharset.id_to_unichar(b_it.data()->unichar_id()));
+ ++counter;
+ if (counter == 3) break;
}
+ tprintf("\t");
+ } else {
+ tprintf(" \t");
}
}
+ tprintf("\n");
}
}
diff --git a/wordrec/matrix.h b/ccstruct/matrix.h
similarity index 83%
rename from wordrec/matrix.h
rename to ccstruct/matrix.h
index 5317e02e2d..feed46572e 100644
--- a/wordrec/matrix.h
+++ b/ccstruct/matrix.h
@@ -22,13 +22,13 @@
** limitations under the License.
*
*********************************************************************************/
-#ifndef MATRIX_H
-#define MATRIX_H
+#ifndef TESSERACT_CCSTRUCT_MATRIX_H__
+#define TESSERACT_CCSTRUCT_MATRIX_H__
#include "ratngs.h"
#include "unicharset.h"
-static BLOB_CHOICE_LIST* NOT_CLASSIFIED = NULL;
+#define NOT_CLASSIFIED reinterpret_cast(NULL)
// A generic class to store a matrix with entries of type T.
template
@@ -86,7 +86,22 @@ class MATRIX : public GENERIC_MATRIX {
MATRIX(int dimension) : GENERIC_MATRIX(dimension,
NOT_CLASSIFIED) {}
// Print a shortened version of the contents of the matrix.
- void print(const UNICHARSET ¤t_unicharset);
+ void print(const UNICHARSET &unicharset);
};
-#endif
+struct MATRIX_COORD {
+ static void Delete(void *arg) {
+ MATRIX_COORD *c = static_cast(arg);
+ delete c;
+ }
+ MATRIX_COORD(int c, int r): col(c), row(r) {}
+ ~MATRIX_COORD() {}
+ bool Valid(const MATRIX &m) const {
+ return (col >= 0 && row >= 0 &&
+ col < m.dimension() && row < m.dimension());
+ }
+ int col;
+ int row;
+};
+
+#endif // TESSERACT_CCSTRUCT_MATRIX_H__
diff --git a/ccstruct/mod128.cpp b/ccstruct/mod128.cpp
index 72fd917812..40b138f6a1 100644
--- a/ccstruct/mod128.cpp
+++ b/ccstruct/mod128.cpp
@@ -20,7 +20,7 @@
#include "mfcpch.h" //precompiled headers
#include "mod128.h"
-static inT16 idirtab[] = {
+const inT16 idirtab[] = {
1000, 0, 998, 49, 995, 98, 989, 146,
980, 195, 970, 242, 956, 290, 941, 336,
923, 382, 903, 427, 881, 471, 857, 514,
@@ -55,7 +55,7 @@ static inT16 idirtab[] = {
980, -195, 989, -146, 995, -98, 998, -49
};
-static ICOORD *dirtab = (ICOORD *) idirtab;
+const ICOORD *dirtab = (ICOORD *) idirtab;
/**********************************************************************
* DIR128::DIR128
diff --git a/ccstruct/normalis.cpp b/ccstruct/normalis.cpp
index 5d33e86198..40ec8dfc8b 100644
--- a/ccstruct/normalis.cpp
+++ b/ccstruct/normalis.cpp
@@ -70,16 +70,20 @@ float DENORM::scale_at_x(float src_x) const { // In normalized coords.
float DENORM::yshift_at_x(float src_x) const { // In normalized coords.
if (segments != 0) {
const DENORM_SEG* seg = binary_search_segment(src_x);
- if (seg->ycoord == -MAX_INT32) {
- if (base_is_row)
- return source_row->base_line(x(src_x));
- else
- return m * x(src_x) + c;
- } else {
+ if (seg->ycoord != -MAX_INT32) {
return seg->ycoord;
}
}
- return source_row->base_line(x(src_x));
+ return yshift_at_orig_x(x(src_x));
+}
+
+// Returns the y-shift at the original (un-normalized) x, assuming
+// no segments.
+float DENORM::yshift_at_orig_x(float orig_x) const {
+ if (base_is_row && source_row != NULL)
+ return source_row->base_line(orig_x);
+ else
+ return m * orig_x + c;
}
/**********************************************************************
@@ -124,24 +128,12 @@ DENORM::DENORM(float x, //from same pieces
DENORM_SEG *seg_pts, //actual segments
BOOL8 using_row, //as baseline
ROW *src) {
+ Init();
x_centre = x; //just copy
scale_factor = scaling;
source_row = src;
- if (seg_count > 0) {
- segs = new DENORM_SEG[seg_count];
- for (segments = 0; segments < seg_count; segments++)
- segs[segments] = seg_pts[segments];
- // It is possible, if infrequent that the segments may be out of order.
- // since we are searching with a binary search, keep them in order.
- qsort(segs, segments, sizeof(DENORM_SEG),
- reinterpret_cast(
- &compare_seg_by_xstart));
- }
- else {
- segments = 0;
- segs = NULL;
- }
- base_is_row = using_row;
+ set_segments(seg_pts, seg_count);
+ base_is_row = src != NULL && using_row;
m = line_m;
c = line_c;
block_ = NULL;
@@ -155,24 +147,33 @@ DENORM::DENORM(const DENORM &src) {
}
-DENORM & DENORM::operator= (const DENORM & src) {
+DENORM & DENORM::operator=(const DENORM & src) {
x_centre = src.x_centre;
scale_factor = src.scale_factor;
source_row = src.source_row;
- if (segments > 0)
- delete[]segs;
- if (src.segments > 0) {
- segs = new DENORM_SEG[src.segments];
- for (segments = 0; segments < src.segments; segments++)
- segs[segments] = src.segs[segments];
- }
- else {
- segments = 0;
- segs = NULL;
- }
+ set_segments(src.segs, src.segments);
base_is_row = src.base_is_row;
m = src.m;
c = src.c;
block_ = src.block_;
return *this;
}
+
+void DENORM::set_segments(const DENORM_SEG* src_segs, int seg_count) {
+ if (segments > 0)
+ delete [] segs;
+ if (seg_count > 0) {
+ segs = new DENORM_SEG[seg_count];
+ for (segments = 0; segments < seg_count; segments++)
+ segs[segments] = src_segs[segments];
+ // It is possible, if infrequent that the segments may be out of order.
+ // since we are searching with a binary search, keep them in order.
+ qsort(segs, segments, sizeof(DENORM_SEG),
+ reinterpret_cast(
+ &compare_seg_by_xstart));
+ } else {
+ segments = 0;
+ segs = NULL;
+ }
+}
+
diff --git a/ccstruct/normalis.h b/ccstruct/normalis.h
index 870c201390..7f7e7cadaf 100644
--- a/ccstruct/normalis.h
+++ b/ccstruct/normalis.h
@@ -20,102 +20,109 @@
#ifndef NORMALIS_H
#define NORMALIS_H
-#include
+#include
+#include "host.h"
class ROW; //forward decl
class BLOCK;
-class DENORM_SEG
-{
- public:
- DENORM_SEG() {}
+class DENORM_SEG {
+ public:
+ DENORM_SEG() {}
- inT32 xstart; // start of segment
- inT32 ycoord; // y at segment
- float scale_factor; // normalized_x/scale_factor + x_center == original_x
+ inT32 xstart; // start of segment
+ inT32 ycoord; // y at segment
+ float scale_factor; // normalized_x/scale_factor + x_center == original_x
};
-class DENORM
-{
- public:
- DENORM() { //constructor
- source_row = NULL;
- x_centre = 0.0f;
- scale_factor = 1.0f;
- segments = 0;
- segs = NULL;
- base_is_row = TRUE;
- m = c = 0;
- block_ = NULL;
- }
- DENORM( //constructor
- float x, //from same pieces
- float scaling,
- ROW *src) {
- x_centre = x; //just copy
- scale_factor = scaling;
- source_row = src;
- segments = 0;
- segs = NULL;
- base_is_row = TRUE;
- m = c = 0;
- block_ = NULL;
- }
- DENORM( // constructor
- float x, // from same pieces
- float scaling,
- double line_m, // default line: y = mx + c
- double line_c,
- inT16 seg_count, // no of segments
- DENORM_SEG *seg_pts, // actual segments
- BOOL8 using_row, // as baseline
- ROW *src);
- DENORM(const DENORM &);
- DENORM & operator= (const DENORM &);
- ~DENORM() {
- if (segments > 0)
- delete[]segs;
- }
+class DENORM {
+ public:
+ DENORM() {
+ Init();
+ }
+ DENORM(float x, // from same pieces
+ float scaling,
+ ROW *src) {
+ Init();
+ x_centre = x; //just copy
+ scale_factor = scaling;
+ source_row = src;
+ base_is_row = src != NULL;
+ }
+ DENORM(float x, // from same pieces
+ float scaling,
+ double line_m, // default line: y = mx + c
+ double line_c,
+ inT16 seg_count, // no of segments
+ DENORM_SEG *seg_pts, // actual segments
+ BOOL8 using_row, // as baseline
+ ROW *src);
+ DENORM(const DENORM &);
+ DENORM& operator=(const DENORM&);
+ ~DENORM() {
+ if (segments > 0)
+ delete[] segs;
+ }
- // Return the original x coordinate of the middle of the word
- // (mapped to 0 in normalized coordinates).
- float origin() const { return x_centre; }
+ // Setup default values.
+ void Init() {
+ base_is_row = false;
+ segments = 0;
+ m = c = 0.0;
+ x_centre = 0.0f;
+ scale_factor = 1.0f;
+ source_row = NULL;
+ segs = NULL;
+ block_ = NULL;
+ }
- float scale() const { //get scale
- return scale_factor;
- }
- ROW *row() const { //get row
- return source_row;
- }
- const BLOCK* block() const {
- return block_;
- }
- void set_block(const BLOCK* block) {
- block_ = block;
- }
+ // Return the original x coordinate of the middle of the word
+ // (mapped to 0 in normalized coordinates).
+ float origin() const { return x_centre; }
- // normalized x -> original x
- float x(float src_x) const;
+ float scale() const { //get scale
+ return scale_factor;
+ }
+ ROW *row() const { //get row
+ return source_row;
+ }
+ void set_row(ROW* row) {
+ source_row = row;
+ }
+ const BLOCK* block() const {
+ return block_;
+ }
+ void set_block(const BLOCK* block) {
+ block_ = block;
+ }
- // Given a (y coordinate, x center of segment) in normalized coordinates,
- // return the original y coordinate.
- float y(float src_y, float src_x_centre) const;
+ // normalized x -> original x
+ float x(float src_x) const;
- float scale_at_x( // Return scaling at this coord.
- float src_x) const;
- float yshift_at_x( // Return yshift at this coord.
- float src_x) const;
+ // Given a (y coordinate, x center of segment) in normalized coordinates,
+ // return the original y coordinate.
+ float y(float src_y, float src_x_centre) const;
- private:
- const DENORM_SEG *binary_search_segment(float src_x) const;
+ float scale_at_x( // Return scaling at this coord.
+ float src_x) const;
+ float yshift_at_x( // Return yshift at this coord.
+ float src_x) const;
+ // Returns the y-shift at the original (un-normalized) x, assuming
+ // no segments.
+ float yshift_at_orig_x(float orig_x) const;
- BOOL8 base_is_row; // using row baseline?
- inT16 segments; // no of segments
- double c, m; // baseline: y = mx + c
- float x_centre; // middle of word in original coordinates
- float scale_factor; // normalized_x/scale_factor + x_center == original_x
- ROW *source_row; // row it came from
- DENORM_SEG *segs; // array of segments
- const BLOCK* block_; // Block the word came from.
+ void set_segments(const DENORM_SEG* new_segs, int seg_count);
+
+ private:
+ const DENORM_SEG *binary_search_segment(float src_x) const;
+
+ BOOL8 base_is_row; // using row baseline?
+ inT16 segments; // no of segments
+ double c, m; // baseline: y = mx + c
+ float x_centre; // middle of word in original coordinates
+ float scale_factor; // normalized_x/scale_factor + x_center == original_x
+ ROW *source_row; // row it came from
+ DENORM_SEG *segs; // array of segments
+ const BLOCK* block_; // Block the word came from.
};
#endif
diff --git a/ccstruct/ocrblock.cpp b/ccstruct/ocrblock.cpp
index 86d14f4d0e..56e05b5aee 100644
--- a/ccstruct/ocrblock.cpp
+++ b/ccstruct/ocrblock.cpp
@@ -47,6 +47,7 @@ BLOCK::BLOCK(const char *name, //< filename
ICOORDELT_IT right_it = &rightside;
proportional = prop;
+ right_to_left_ = false;
kerning = kern;
spacing = space;
font_class = -1; //not assigned
@@ -217,3 +218,117 @@ const BLOCK & source //from this
skew_ = source.skew_;
return *this;
}
+
+/**********************************************************************
+ * PrintSegmentationStats
+ *
+ * Prints segmentation stats for the given block list.
+ **********************************************************************/
+
+void PrintSegmentationStats(BLOCK_LIST* block_list) {
+ int num_blocks = 0;
+ int num_rows = 0;
+ int num_words = 0;
+ int num_blobs = 0;
+ BLOCK_IT block_it(block_list);
+ for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
+ BLOCK* block = block_it.data();
+ ++num_blocks;
+ ROW_IT row_it(block->row_list());
+ for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
+ ++num_rows;
+ ROW* row = row_it.data();
+ // Iterate over all werds in the row.
+ WERD_IT werd_it(row->word_list());
+ for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
+ WERD* werd = werd_it.data();
+ ++num_words;
+ num_blobs += werd->cblob_list()->length();
+ }
+ }
+ }
+ tprintf("Block list stats:\nBlocks = %d\nRows = %d\nWords = %d\nBlobs = %d\n",
+ num_blocks, num_rows, num_words, num_blobs);
+}
+
+/**********************************************************************
+ * ExtractBlobsFromSegmentation
+ *
+ * Extracts blobs from the given block list and adds them to the output list.
+ * The block list must have been created by performing a page segmentation.
+ **********************************************************************/
+
+void ExtractBlobsFromSegmentation(BLOCK_LIST* blocks,
+ C_BLOB_LIST* output_blob_list) {
+ C_BLOB_IT return_list_it(output_blob_list);
+ BLOCK_IT block_it(blocks);
+ for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
+ BLOCK* block = block_it.data();
+ ROW_IT row_it(block->row_list());
+ for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
+ ROW* row = row_it.data();
+ // Iterate over all werds in the row.
+ WERD_IT werd_it(row->word_list());
+ for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
+ WERD* werd = werd_it.data();
+ return_list_it.move_to_last();
+ return_list_it.add_list_after(werd->cblob_list());
+ return_list_it.move_to_last();
+ return_list_it.add_list_after(werd->rej_cblob_list());
+ }
+ }
+ }
+}
+
+/**********************************************************************
+ * RefreshWordBlobsFromNewBlobs()
+ *
+ * Refreshes the words in the block_list by using blobs in the
+ * new_blobs list.
+ * Block list must have word segmentation in it.
+ * It consumes the blobs provided in the new_blobs list. The blobs leftover in
+ * the new_blobs list after the call weren't matched to any blobs of the words
+ * in block list.
+ * The output not_found_blobs is a list of blobs from the original segmentation
+ * in the block_list for which no corresponding new blobs were found.
+ **********************************************************************/
+
+void RefreshWordBlobsFromNewBlobs(BLOCK_LIST* block_list,
+ C_BLOB_LIST* new_blobs,
+ C_BLOB_LIST* not_found_blobs) {
+ // Now iterate over all the blobs in the segmentation_block_list_, and just
+ // replace the corresponding c-blobs inside the werds.
+ BLOCK_IT block_it(block_list);
+ for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
+ BLOCK* block = block_it.data();
+ // Iterate over all rows in the block.
+ ROW_IT row_it(block->row_list());
+ for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
+ ROW* row = row_it.data();
+ // Iterate over all werds in the row.
+ WERD_IT werd_it(row->word_list());
+ WERD_LIST new_words;
+ WERD_IT new_words_it(&new_words);
+ for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
+ WERD* werd = werd_it.extract();
+ WERD* new_werd = werd->ConstructWerdWithNewBlobs(new_blobs,
+ not_found_blobs);
+ if (new_werd) {
+ // Insert this new werd into the actual row's werd-list. Remove the
+ // existing one.
+ new_words_it.add_after_then_move(new_werd);
+ delete werd;
+ } else {
+ // Reinsert the older word back, for lack of better options.
+ // This is critical since dropping the words messes up segmentation:
+ // eg. 1st word in the row might otherwise have W_FUZZY_NON turned on.
+ new_words_it.add_after_then_move(werd);
+ }
+ }
+ // Get rid of the old word list & replace it with the new one.
+ row->word_list()->clear();
+ werd_it.move_to_first();
+ werd_it.add_list_after(&new_words);
+ }
+ }
+}
diff --git a/ccstruct/ocrblock.h b/ccstruct/ocrblock.h
index 25db1fdf3d..36d70f7b90 100644
--- a/ccstruct/ocrblock.h
+++ b/ccstruct/ocrblock.h
@@ -37,6 +37,7 @@ class BLOCK:public ELIST_LINK, public PDBLK
: re_rotation_(1.0f, 0.0f),
classify_rotation_(1.0f, 0.0f),
skew_(1.0f, 0.0f) {
+ right_to_left_ = false;
hand_poly = NULL;
}
BLOCK(const char *name, //< filename
@@ -79,6 +80,12 @@ class BLOCK:public ELIST_LINK, public PDBLK
BOOL8 prop() const {
return proportional;
}
+ bool right_to_left() const {
+ return right_to_left_;
+ }
+ void set_right_to_left(bool value) {
+ right_to_left_ = value;
+ }
/// return pitch
inT32 fixed_pitch() const {
return pitch;
@@ -146,6 +153,10 @@ class BLOCK:public ELIST_LINK, public PDBLK
median_size_.set_y(y);
}
+ Pix* render_mask() {
+ return PDBLK::render_mask(re_rotation_);
+ }
+
void rotate(const FCOORD& rotation);
/// decreasing y order
@@ -198,6 +209,7 @@ class BLOCK:public ELIST_LINK, public PDBLK
private:
BOOL8 proportional; //< proportional
+ bool right_to_left_; //< major script is right to left.
inT8 kerning; //< inter blob gap
inT16 spacing; //< inter word gap
inT16 pitch; //< pitch of non-props
@@ -216,4 +228,24 @@ class BLOCK:public ELIST_LINK, public PDBLK
int decreasing_top_order(const void *row1, const void *row2);
+// A function to print segmentation stats for the given block list.
+void PrintSegmentationStats(BLOCK_LIST* block_list);
+
+// Extracts blobs fromo the given block list and adds them to the output list.
+// The block list must have been created by performing a page segmentation.
+void ExtractBlobsFromSegmentation(BLOCK_LIST* blocks,
+ C_BLOB_LIST* output_blob_list);
+
+// Refreshes the words in the block_list by using blobs in the
+// new_blobs list.
+// Block list must have word segmentation in it.
+// It consumes the blobs provided in the new_blobs list. The blobs leftover in
+// the new_blobs list after the call weren't matched to any blobs of the words
+// in block list.
+// The output not_found_blobs is a list of blobs from the original segmentation
+// in the block_list for which no corresponding new blobs were found.
+void RefreshWordBlobsFromNewBlobs(BLOCK_LIST* block_list,
+ C_BLOB_LIST* new_blobs,
+ C_BLOB_LIST* not_found_blobs);
+
#endif
diff --git a/ccstruct/ocrrow.h b/ccstruct/ocrrow.h
index db045e77c4..4a71f6c11b 100644
--- a/ccstruct/ocrrow.h
+++ b/ccstruct/ocrrow.h
@@ -28,7 +28,7 @@ class TO_ROW;
class ROW:public ELIST_LINK
{
- friend void tweak_row_baseline(ROW *);
+ friend void tweak_row_baseline(ROW *, double, double);
public:
ROW() {
} //empty constructor
diff --git a/ccstruct/pageres.cpp b/ccstruct/pageres.cpp
index d678301262..85120c2466 100644
--- a/ccstruct/pageres.cpp
+++ b/ccstruct/pageres.cpp
@@ -22,7 +22,7 @@
#include
#endif
#include "pageres.h"
-#include "notdll.h"
+#include "blobs.h"
ELISTIZE (BLOCK_RES)
CLISTIZE (BLOCK_RES) ELISTIZE (ROW_RES) ELISTIZE (WERD_RES)
@@ -31,9 +31,9 @@ CLISTIZE (BLOCK_RES) ELISTIZE (ROW_RES) ELISTIZE (WERD_RES)
*
* Constructor for page results
*************************************************************************/
-PAGE_RES::PAGE_RES( //recursive construct
- BLOCK_LIST *the_block_list //real page
- ) {
+PAGE_RES::PAGE_RES(
+ BLOCK_LIST *the_block_list,
+ WERD_CHOICE **prev_word_best_choice_ptr) {
BLOCK_IT block_it(the_block_list);
BLOCK_RES_IT block_res_it(&block_res_list);
@@ -41,10 +41,12 @@ PAGE_RES::PAGE_RES( //recursive construct
rej_count = 0;
rejected = FALSE;
- for (block_it.mark_cycle_pt ();
- !block_it.cycled_list (); block_it.forward ()) {
- block_res_it.add_to_end (new BLOCK_RES (block_it.data ()));
+ for (block_it.mark_cycle_pt();
+ !block_it.cycled_list(); block_it.forward()) {
+ block_res_it.add_to_end(new BLOCK_RES(block_it.data()));
}
+
+ prev_word_best_choice = prev_word_best_choice_ptr;
}
@@ -54,9 +56,7 @@ PAGE_RES::PAGE_RES( //recursive construct
* Constructor for BLOCK results
*************************************************************************/
-BLOCK_RES::BLOCK_RES( //recursive construct
- BLOCK *the_block //real BLOCK
- ) {
+BLOCK_RES::BLOCK_RES(BLOCK *the_block) {
ROW_IT row_it (the_block->row_list ());
ROW_RES_IT row_res_it(&row_res_list);
@@ -71,8 +71,9 @@ BLOCK_RES::BLOCK_RES( //recursive construct
block = the_block;
- for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
- row_res_it.add_to_end (new ROW_RES (row_it.data ()));
+ for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
+ row_res_it.add_to_end(new ROW_RES(the_block->right_to_left(),
+ row_it.data()));
}
}
@@ -83,81 +84,108 @@ BLOCK_RES::BLOCK_RES( //recursive construct
* Constructor for ROW results
*************************************************************************/
-ROW_RES::ROW_RES( //recursive construct
- ROW *the_row //real ROW
- ) {
- WERD_IT word_it (the_row->word_list ());
+ROW_RES::ROW_RES(bool right_to_left,
+ ROW *the_row) {
+ WERD_IT word_it(the_row->word_list());
WERD_RES_IT word_res_it(&word_res_list);
- WERD_RES *combo = NULL; //current combination of fuzzies
- WERD_RES *word_res; //current word
+ WERD_RES *combo = NULL; // current combination of fuzzies
+ WERD_RES *word_res; // current word
WERD *copy_word;
char_count = 0;
rej_count = 0;
whole_word_rej_count = 0;
- font_class = -1;
- font_class_score = -1.0;
- bold = FALSE;
- italic = FALSE;
row = the_row;
-
- for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
- word_res = new WERD_RES (word_it.data ());
- word_res->x_height = the_row->x_height();
-
- if (word_res->word->flag (W_FUZZY_NON)) {
- ASSERT_HOST (combo != NULL);
- word_res->part_of_combo = TRUE;
- combo->copy_on (word_res);
- }
- if (word_it.data_relative (1)->flag (W_FUZZY_NON)) {
- if (combo == NULL) {
+ if (right_to_left) {
+ word_it.move_to_last();
+ for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.backward()) {
+ word_res = new WERD_RES(word_it.data());
+ word_res->x_height = the_row->x_height();
+ // A FUZZY_NON marks the beginning of a combo if we are not in one.
+ if (combo == NULL && word_res->word->flag(W_FUZZY_NON)) {
copy_word = new WERD;
//deep copy
- *copy_word = *(word_it.data ());
- combo = new WERD_RES (copy_word);
+ *copy_word = *(word_it.data());
+ combo = new WERD_RES(copy_word);
combo->x_height = the_row->x_height();
combo->combination = TRUE;
- word_res_it.add_to_end (combo);
+ word_res_it.add_to_end(combo);
+ word_res->part_of_combo = TRUE;
+ } else if (combo != NULL) {
+ word_res->part_of_combo = TRUE;
+ combo->copy_on(word_res);
+ // The first non FUZZY_NON is the last word in the combo.
+ if (!word_res->word->flag(W_FUZZY_NON))
+ combo = NULL;
}
- word_res->part_of_combo = TRUE;
+ word_res_it.add_to_end(word_res);
+ }
+ } else {
+ for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
+ word_res = new WERD_RES(word_it.data());
+ word_res->x_height = the_row->x_height();
+
+ if (word_res->word->flag(W_FUZZY_NON)) {
+ ASSERT_HOST(combo != NULL);
+ word_res->part_of_combo = TRUE;
+ combo->copy_on(word_res);
+ }
+ if (word_it.data_relative(1)->flag(W_FUZZY_NON)) {
+ if (combo == NULL) {
+ copy_word = new WERD;
+ //deep copy
+ *copy_word = *(word_it.data());
+ combo = new WERD_RES(copy_word);
+ combo->x_height = the_row->x_height();
+ combo->combination = TRUE;
+ word_res_it.add_to_end(combo);
+ }
+ word_res->part_of_combo = TRUE;
+ } else {
+ combo = NULL;
+ }
+ word_res_it.add_to_end(word_res);
}
- else
- combo = NULL;
- word_res_it.add_to_end (word_res);
}
}
-WERD_RES & WERD_RES::operator= ( //assign word_res
-const WERD_RES & source //from this
-) {
- this->ELIST_LINK::operator= (source);
+WERD_RES& WERD_RES::operator=(const WERD_RES & source) {
+ this->ELIST_LINK::operator=(source);
+ Clear();
if (source.combination) {
word = new WERD;
- *word = *(source.word); //deep copy
+ *word = *(source.word); // deep copy
+ } else {
+ word = source.word; // pt to same word
}
- else
- word = source.word; //pt to same word
-
- if (source.outword != NULL) {
- outword = new WERD;
- *outword = *(source.outword);//deep copy
- }
- else
- outword = NULL;
-
+ if (source.bln_boxes != NULL)
+ bln_boxes = new tesseract::BoxWord(*source.bln_boxes);
+ if (source.chopped_word != NULL)
+ chopped_word = new TWERD(*source.chopped_word);
+ if (source.rebuild_word != NULL)
+ rebuild_word = new TWERD(*source.rebuild_word);
+ // TODO(rays) Do we ever need to copy the seam_array?
denorm = source.denorm;
+ if (source.box_word != NULL)
+ box_word = new tesseract::BoxWord(*source.box_word);
+ best_state = source.best_state;
+ correct_text = source.correct_text;
+
if (source.best_choice != NULL) {
best_choice = new WERD_CHOICE;
*best_choice = *(source.best_choice);
raw_choice = new WERD_CHOICE;
*raw_choice = *(source.raw_choice);
+ best_choice_fontinfo_ids = source.best_choice_fontinfo_ids;
}
else {
best_choice = NULL;
raw_choice = NULL;
+ if (!best_choice_fontinfo_ids.empty()) {
+ best_choice_fontinfo_ids.clear();
+ }
}
if (source.ep_choice != NULL) {
ep_choice = new WERD_CHOICE;
@@ -166,6 +194,15 @@ const WERD_RES & source //from this
else
ep_choice = NULL;
reject_map = source.reject_map;
+ combination = source.combination;
+ part_of_combo = source.part_of_combo;
+ CopySimpleFields(source);
+ return *this;
+}
+
+// Copies basic fields that don't involve pointers that might be useful
+// to copy when making one WERD_RES from another.
+void WERD_RES::CopySimpleFields(const WERD_RES& source) {
tess_failed = source.tess_failed;
tess_accepted = source.tess_accepted;
tess_would_adapt = source.tess_would_adapt;
@@ -181,37 +218,327 @@ const WERD_RES & source //from this
caps_height = source.caps_height;
guessed_x_ht = source.guessed_x_ht;
guessed_caps_ht = source.guessed_caps_ht;
- combination = source.combination;
- part_of_combo = source.part_of_combo;
reject_spaces = source.reject_spaces;
- return *this;
+}
+
+// Sets up the members used in recognition:
+// bln_boxes, chopped_word, seam_array, denorm, best_choice, raw_choice.
+// Returns false if the word is empty and sets up fake results.
+bool WERD_RES::SetupForRecognition(const UNICHARSET& unicharset,
+ bool numeric_mode, ROW *row, BLOCK* block) {
+ ClearResults();
+ if (word->cblob_list()->empty()) {
+ tprintf("Initial word empty!\n");
+ chopped_word = new TWERD;
+ rebuild_word = new TWERD;
+ bln_boxes = new tesseract::BoxWord;
+ box_word = new tesseract::BoxWord;
+ best_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);
+ tess_failed = true;
+ return false;
+ }
+ chopped_word = TWERD::PolygonalCopy(word);
+ chopped_word->Normalize(row, x_height, numeric_mode, &denorm);
+ if (block != NULL)
+ denorm.set_block(block);
+ bln_boxes = tesseract::BoxWord::CopyFromNormalized(NULL, chopped_word);
+ seam_array = start_seam_list(chopped_word->blobs);
+ best_choice = new WERD_CHOICE;
+ best_choice->make_bad();
+ raw_choice = new WERD_CHOICE;
+ raw_choice->make_bad();
+ return true;
+}
+
+// Builds the rebuild_word from the chopped_word and the best_state.
+void WERD_RES::RebuildBestState() {
+ if (rebuild_word != NULL)
+ delete rebuild_word;
+ rebuild_word = new TWERD;
+ TBLOB* prev_blob = NULL;
+ int start = 0;
+ for (int i = 0; i < best_state.size(); ++i) {
+ int length = best_state[i];
+ join_pieces(chopped_word->blobs, seam_array, start, start + length - 1);
+ TBLOB* blob = chopped_word->blobs;
+ for (int i = 0; i < start; ++i)
+ blob = blob->next;
+ TBLOB* copy_blob = new TBLOB(*blob);
+ if (prev_blob == NULL)
+ rebuild_word->blobs = copy_blob;
+ else
+ prev_blob->next = copy_blob;
+ prev_blob = copy_blob;
+ break_pieces(blob, seam_array, start, start + length - 1);
+ start += length;
+ }
+}
+
+// Copies the chopped_word to the rebuild_word, faking a best_state as well.
+// Also sets up the output box_word.
+void WERD_RES::CloneChoppedToRebuild() {
+ if (rebuild_word != NULL)
+ delete rebuild_word;
+ rebuild_word = new TWERD(*chopped_word);
+ SetupBoxWord();
+ int word_len = box_word->length();
+ best_state.reserve(word_len);
+ correct_text.reserve(word_len);
+ for (int i = 0; i < word_len; ++i) {
+ best_state.push_back(1);
+ correct_text.push_back(STRING(""));
+ }
+}
+
+// Sets/replaces the box_word with one made from the rebuild_word.
+void WERD_RES::SetupBoxWord() {
+ if (box_word != NULL)
+ delete box_word;
+ rebuild_word->ComputeBoundingBoxes();
+ box_word = tesseract::BoxWord::CopyFromNormalized(&denorm, rebuild_word);
+ box_word->ClipToOriginalWord(denorm.block(), word);
+}
+
+// Classifies the word with some already-calculated BLOB_CHOICEs.
+// The choices are an array of blob_count pointers to BLOB_CHOICE,
+// providing a single classifier result for each blob.
+// The BLOB_CHOICEs are consumed and the word takes ownership.
+// The number of blobs in the outword must match blob_count.
+void WERD_RES::FakeClassifyWord(const UNICHARSET& unicharset, int blob_count,
+ BLOB_CHOICE** choices) {
+ // Setup the WERD_RES.
+ ASSERT_HOST(box_word != NULL);
+ ASSERT_HOST(blob_count == box_word->length());
+ ASSERT_HOST(best_choice != NULL);
+ BLOB_CHOICE_LIST_CLIST* word_choices = new BLOB_CHOICE_LIST_CLIST;
+ BLOB_CHOICE_LIST_C_IT bc_it(word_choices);
+ for (int c = 0; c < blob_count; ++c) {
+ best_choice->append_unichar_id(
+ choices[c]->unichar_id(), 1,
+ choices[c]->rating(), choices[c]->certainty());
+ BLOB_CHOICE_LIST* choice_list = new BLOB_CHOICE_LIST;
+ BLOB_CHOICE_IT choice_it(choice_list);
+ choice_it.add_after_then_move(choices[c]);
+ bc_it.add_after_then_move(choice_list);
+ }
+ best_choice->set_blob_choices(word_choices);
+ best_choice->populate_unichars(unicharset);
+ delete raw_choice;
+ raw_choice = new WERD_CHOICE(*best_choice);
+ reject_map.initialise(blob_count);
+}
+
+// Copies the best_choice strings to the correct_text for adaption/training.
+void WERD_RES::BestChoiceToCorrectText(const UNICHARSET& unicharset) {
+ correct_text.clear();
+ ASSERT_HOST(best_choice != NULL);
+ for (int i = 0; i < best_choice->length(); ++i) {
+ UNICHAR_ID choice_id = best_choice->unichar_id(i);
+ const char* blob_choice = unicharset.id_to_unichar(choice_id);
+ correct_text.push_back(STRING(blob_choice));
+ }
+}
+
+// Merges 2 adjacent blobs in the result if the permanent callback
+// class_cb returns other than INVALID_UNICHAR_ID, AND the permanent
+// callback box_cb is NULL or returns true, setting the merged blob
+// result to the class returned from class_cb.
+// Returns true if anything was merged.
+bool WERD_RES::ConditionalBlobMerge(
+ const UNICHARSET& unicharset,
+ TessResultCallback2* class_cb,
+ TessResultCallback2* box_cb,
+
+ BLOB_CHOICE_LIST_CLIST *blob_choices) {
+ bool modified = false;
+ for (int i = 0; i + 1 < best_choice->length(); ++i) {
+ UNICHAR_ID new_id = class_cb->Run(best_choice->unichar_id(i),
+ best_choice->unichar_id(i+1));
+ if (new_id != INVALID_UNICHAR_ID &&
+ (box_cb == NULL || box_cb->Run(box_word->BlobBox(i),
+ box_word->BlobBox(i + 1)))) {
+ if (reject_map.length() == best_choice->length())
+ reject_map.remove_pos(i);
+ best_choice->set_unichar_id(new_id, i);
+ best_choice->remove_unichar_id(i + 1);
+ raw_choice->set_unichar_id(new_id, i);
+ raw_choice->remove_unichar_id(i + 1);
+ modified = true;
+ rebuild_word->MergeBlobs(i, i + 2);
+ box_word->MergeBoxes(i, i + 2);
+ if (i + 1 < best_state.length()) {
+ best_state[i] += best_state[i + 1];
+ best_state.remove(i + 1);
+ }
+
+ BLOB_CHOICE_LIST_C_IT blob_choices_it(blob_choices);
+ for (int j = 0; j < i; ++j)
+ blob_choices_it.forward();
+ BLOB_CHOICE_IT it1(blob_choices_it.data()); // first choices
+ BLOB_CHOICE_LIST* target_choices = blob_choices_it.data_relative(1);
+ BLOB_CHOICE_IT it2(target_choices); // second choices
+ float certainty = it2.data()->certainty();
+ float rating = it2.data()->rating();
+ if (it1.data()->certainty() < certainty) {
+ certainty = it1.data()->certainty();
+ rating = it1.data()->rating();
+ target_choices = blob_choices_it.data();
+ blob_choices_it.forward();
+ }
+ delete blob_choices_it.extract(); // get rid of spare
+ // TODO(rays) Fix the choices so they contain the desired result.
+ // Do we really need to ? Only needed for fix_quotes, which should be
+ // going away.
+ }
+ }
+ delete class_cb;
+ delete box_cb;
+ if (modified) {
+ best_choice->populate_unichars(unicharset);
+ raw_choice->populate_unichars(unicharset);
+ }
+ return modified;
}
WERD_RES::~WERD_RES () {
- if (combination)
+ Clear();
+}
+
+void WERD_RES::InitPointers() {
+ word = NULL;
+ bln_boxes = NULL;
+ chopped_word = NULL;
+ rebuild_word = NULL;
+ box_word = NULL;
+ seam_array = NULL;
+ best_choice = NULL;
+ raw_choice = NULL;
+ ep_choice = NULL;
+}
+
+void WERD_RES::Clear() {
+ if (word != NULL && combination)
delete word;
- if (outword != NULL)
- delete outword;
+ word = NULL;
+ ClearResults();
+}
+
+void WERD_RES::ClearResults() {
+ done = false;
+ if (bln_boxes != NULL) {
+ delete bln_boxes;
+ bln_boxes = NULL;
+ }
+ if (chopped_word != NULL) {
+ delete chopped_word;
+ chopped_word = NULL;
+ }
+ if (rebuild_word != NULL) {
+ delete rebuild_word;
+ rebuild_word = NULL;
+ }
+ if (box_word != NULL) {
+ delete box_word;
+ box_word = NULL;
+ }
+ best_state.clear();
+ correct_text.clear();
+ if (seam_array != NULL) {
+ free_seam_list(seam_array);
+ seam_array = NULL;
+ }
if (best_choice != NULL) {
delete best_choice;
delete raw_choice;
+ best_choice = NULL;
+ raw_choice = NULL;
}
if (ep_choice != NULL) {
delete ep_choice;
+ ep_choice = NULL;
}
}
+// Inserts the new_word and a corresponding WERD_RES before the current
+// position. The simple fields of the WERD_RES are copied from clone_res and
+// the resulting WERD_RES is returned for further setup with best_choice etc.
+WERD_RES* PAGE_RES_IT::InsertCloneWord(const WERD_RES& clone_res,
+ WERD* new_word) {
+ // Insert new_word into the ROW.
+ WERD_IT w_it(row()->row->word_list());
+ for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) {
+ WERD* word = w_it.data();
+ if (word == word_res->word)
+ break;
+ }
+ ASSERT_HOST(!w_it.cycled_list());
+ w_it.add_before_then_move(new_word);
+ // Make a WERD_RES for the new_word.
+ WERD_RES* new_res = new WERD_RES(new_word);
+ new_res->CopySimpleFields(clone_res);
+ // Insert into the appropriate place in the ROW_RES.
+ WERD_RES_IT wr_it(&row()->word_res_list);
+ for (wr_it.mark_cycle_pt(); !wr_it.cycled_list(); wr_it.forward()) {
+ WERD_RES* word = wr_it.data();
+ if (word == word_res)
+ break;
+ }
+ ASSERT_HOST(!wr_it.cycled_list());
+ wr_it.add_before_then_move(new_res);
+ if (wr_it.at_first()) {
+ // This is the new first word, so reset the member iterator so it
+ // detects the cycled_list state correctly.
+ ResetWordIterator();
+ }
+ return new_res;
+}
+
+// Deletes the current WERD_RES and its underlying WERD.
+void PAGE_RES_IT::DeleteCurrentWord() {
+ // Check that this word is as we expect. part_of_combos are NEVER iterated
+ // by the normal iterator, so we should never be trying to delete them.
+ ASSERT_HOST(!word_res->part_of_combo);
+ if (!word_res->combination) {
+ // Combinations own their own word, so we won't find the word on the
+ // row's word_list, but it is legitimate to try to delete them.
+ // Delete word from the ROW when not a combination.
+ WERD_IT w_it(row()->row->word_list());
+ for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) {
+ if (w_it.data() == word_res->word) {
+ break;
+ }
+ }
+ ASSERT_HOST(!w_it.cycled_list());
+ delete w_it.extract();
+ }
+ // Remove the WERD_RES for the new_word.
+ // Remove the WORD_RES from the ROW_RES.
+ WERD_RES_IT wr_it(&row()->word_res_list);
+ for (wr_it.mark_cycle_pt(); !wr_it.cycled_list(); wr_it.forward()) {
+ if (wr_it.data() == word_res) {
+ word_res = NULL;
+ break;
+ }
+ }
+ ASSERT_HOST(!wr_it.cycled_list());
+ delete wr_it.extract();
+ ResetWordIterator();
+}
+
/*************************************************************************
* PAGE_RES_IT::restart_page
*
* Set things up at the start of the page
*************************************************************************/
-WERD_RES *PAGE_RES_IT::restart_page() {
- block_res_it.set_to_list (&page_res->block_res_list);
- block_res_it.mark_cycle_pt ();
+WERD_RES *PAGE_RES_IT::start_page(bool empty_ok) {
+ block_res_it.set_to_list(&page_res->block_res_list);
+ block_res_it.mark_cycle_pt();
prev_block_res = NULL;
prev_row_res = NULL;
prev_word_res = NULL;
@@ -221,23 +548,47 @@ WERD_RES *PAGE_RES_IT::restart_page() {
next_block_res = NULL;
next_row_res = NULL;
next_word_res = NULL;
- internal_forward(TRUE);
- return internal_forward (FALSE);
+ internal_forward(true, empty_ok);
+ return internal_forward(false, empty_ok);
}
+// Recovers from operations on the current word, such as in InsertCloneWord
+// and DeleteCurrentWord.
+// Resets the word_res_it so that it is one past the next_word_res, as
+// it should be after internal_forward. If next_row_res != row_res,
+// then the next_word_res is in the next row, so there is no need to do
+// anything, since operations on the current word will not have disturbed
+// the word_res_it.
+void PAGE_RES_IT::ResetWordIterator() {
+ if (row_res == next_row_res) {
+ // Reset the member iterator so it can move forward and detect the
+ // cycled_list state correctly.
+ word_res_it.move_to_first();
+ word_res_it.mark_cycle_pt();
+ while (!word_res_it.cycled_list() && word_res_it.data() != next_word_res)
+ word_res_it.forward();
+ ASSERT_HOST(!word_res_it.cycled_list());
+ word_res_it.forward();
+ }
+}
/*************************************************************************
* PAGE_RES_IT::internal_forward
*
- * Find the next word on the page. Empty blocks and rows are skipped.
+ * Find the next word on the page. If empty_ok is true, then non-text blocks
+ * and text blocks with no text are visited as if they contain a single
+ * imaginary word in a single imaginary row. (word() and row() both return NULL
+ * in such a block and the return value is NULL.)
+ * If empty_ok is false, the old behaviour is maintained. Each real word
+ * is visited and empty and non-text blocks and rows are skipped.
+ * new_block is used to initialize the iterators for a new block.
* The iterator maintains pointers to block, row and word for the previous,
* current and next words. These are correct, regardless of block/row
* boundaries. NULL values denote start and end of the page.
*************************************************************************/
-WERD_RES *PAGE_RES_IT::internal_forward(BOOL8 new_block) {
- BOOL8 found_next_word = FALSE;
- BOOL8 new_row = FALSE;
+WERD_RES *PAGE_RES_IT::internal_forward(bool new_block, bool empty_ok) {
+ bool new_row = false;
prev_block_res = block_res;
prev_row_res = row_res;
@@ -245,44 +596,50 @@ WERD_RES *PAGE_RES_IT::internal_forward(BOOL8 new_block) {
block_res = next_block_res;
row_res = next_row_res;
word_res = next_word_res;
+ next_block_res = NULL;
+ next_row_res = NULL;
+ next_word_res = NULL;
- while (!found_next_word && !block_res_it.cycled_list ()) {
+ while (!block_res_it.cycled_list()) {
if (new_block) {
- new_block = FALSE;
- row_res_it.set_to_list (&block_res_it.data ()->row_res_list);
- row_res_it.mark_cycle_pt ();
- new_row = TRUE;
+ new_block = false;
+ row_res_it.set_to_list(&block_res_it.data()->row_res_list);
+ row_res_it.mark_cycle_pt();
+ if (row_res_it.empty() && empty_ok) {
+ next_block_res = block_res_it.data();
+ break;
+ }
+ new_row = true;
}
- while (!found_next_word && !row_res_it.cycled_list ()) {
+ while (!row_res_it.cycled_list()) {
if (new_row) {
- new_row = FALSE;
- word_res_it.set_to_list (&row_res_it.data ()->word_res_list);
- word_res_it.mark_cycle_pt ();
+ new_row = false;
+ word_res_it.set_to_list(&row_res_it.data()->word_res_list);
+ word_res_it.mark_cycle_pt();
}
- while (!found_next_word && !word_res_it.cycled_list ()) {
- next_block_res = block_res_it.data ();
- next_row_res = row_res_it.data ();
- next_word_res = word_res_it.data ();
- found_next_word = TRUE;
- do {
- word_res_it.forward ();
- }
- while (word_res_it.data ()->part_of_combo);
+ // Skip any part_of_combo words.
+ while (!word_res_it.cycled_list() && word_res_it.data()->part_of_combo)
+ word_res_it.forward();
+ if (!word_res_it.cycled_list()) {
+ next_block_res = block_res_it.data();
+ next_row_res = row_res_it.data();
+ next_word_res = word_res_it.data();
+ word_res_it.forward();
+ goto foundword;
}
- if (!found_next_word) { //end of row reached
- row_res_it.forward ();
- new_row = TRUE;
- }
- }
- if (!found_next_word) { //end of block reached
- block_res_it.forward ();
- new_block = TRUE;
+ // end of row reached
+ row_res_it.forward();
+ new_row = true;
}
+ // end of block reached
+ block_res_it.forward();
+ new_block = true;
}
- if (!found_next_word) { //end of page reached
- next_block_res = NULL;
- next_row_res = NULL;
- next_word_res = NULL;
+ foundword:
+ // Update prev_word_best_choice pointer.
+ if (page_res != NULL && page_res->prev_word_best_choice != NULL) {
+ *page_res->prev_word_best_choice =
+ (new_block || prev_word_res == NULL) ? NULL : prev_word_res->best_choice;
}
return word_res;
}
@@ -291,23 +648,14 @@ WERD_RES *PAGE_RES_IT::internal_forward(BOOL8 new_block) {
/*************************************************************************
* PAGE_RES_IT::forward_block
*
- * Move to the first word of the next block
- * Can be followed by subsequent calls to forward() BUT at the first word in
- * the block, the prev block, row and word are all NULL.
+ * Move to the beginning of the next block, allowing empty blocks.
*************************************************************************/
WERD_RES *PAGE_RES_IT::forward_block() {
- if (block_res == next_block_res) {
- block_res_it.forward ();;
- block_res = NULL;
- row_res = NULL;
- word_res = NULL;
- next_block_res = NULL;
- next_row_res = NULL;
- next_word_res = NULL;
- internal_forward(TRUE);
+ while (block_res == next_block_res) {
+ internal_forward(false, true);
}
- return internal_forward (FALSE);
+ return internal_forward(false, true);
}
diff --git a/ccstruct/pageres.h b/ccstruct/pageres.h
index d1cf4b17a1..bc82a0afb5 100644
--- a/ccstruct/pageres.h
+++ b/ccstruct/pageres.h
@@ -19,14 +19,16 @@
#ifndef PAGERES_H
#define PAGERES_H
-#include "elst.h"
-#include "ocrblock.h"
-#include "ocrrow.h"
-#include "werd.h"
-#include "ratngs.h"
-#include "rejctmap.h"
-#include "notdll.h"
-#include "notdll.h"
+#include "blobs.h"
+#include "boxword.h"
+#include "elst.h"
+#include "genericvector.h"
+#include "ocrblock.h"
+#include "ocrrow.h"
+#include "ratngs.h"
+#include "rejctmap.h"
+#include "seam.h"
+#include "werd.h"
/* Forward declarations */
@@ -40,87 +42,77 @@ ELISTIZEH (ROW_RES)
class WERD_RES;
ELISTIZEH (WERD_RES)
+
/*************************************************************************
* PAGE_RES - Page results
*************************************************************************/
-class PAGE_RES //page result
-{
- public:
- inT32 char_count;
- inT32 rej_count;
- BLOCK_RES_LIST block_res_list;
- BOOL8 rejected;
-
- PAGE_RES() {
- } //empty constructor
-
- PAGE_RES( //simple constructor
- BLOCK_LIST *block_list); //real blocks
-
- ~PAGE_RES () { //destructor
- }
+class PAGE_RES { // page result
+ public:
+ inT32 char_count;
+ inT32 rej_count;
+ BLOCK_RES_LIST block_res_list;
+ BOOL8 rejected;
+ // Updated every time PAGE_RES_IT iterating on this PAGE_RES moves to
+ // the next word. This pointer is not owned by PAGE_RES class.
+ WERD_CHOICE **prev_word_best_choice;
+
+ PAGE_RES() {
+ } // empty constructor
+
+ PAGE_RES(BLOCK_LIST *block_list, // real blocks
+ WERD_CHOICE **prev_word_best_choice_ptr);
+
+ ~PAGE_RES () { // destructor
+ }
};
/*************************************************************************
* BLOCK_RES - Block results
*************************************************************************/
-class BLOCK_RES:public ELIST_LINK
- //page block result
-{
- public:
- BLOCK * block; //real block
- inT32 char_count; //chars in block
- inT32 rej_count; //rejected chars
- inT16 font_class; //
- inT16 row_count;
- float x_height;
- BOOL8 font_assigned; // block already
- // processed
- BOOL8 bold; // all bold
- BOOL8 italic; // all italic
-
- ROW_RES_LIST row_res_list;
-
- BLOCK_RES() {
- } //empty constructor
-
- BLOCK_RES( //simple constructor
- BLOCK *the_block); //real block
-
- ~BLOCK_RES () { //destructor
- }
+class BLOCK_RES:public ELIST_LINK {
+ public:
+ BLOCK * block; // real block
+ inT32 char_count; // chars in block
+ inT32 rej_count; // rejected chars
+ inT16 font_class; //
+ inT16 row_count;
+ float x_height;
+ BOOL8 font_assigned; // block already
+ // processed
+ BOOL8 bold; // all bold
+ BOOL8 italic; // all italic
+
+ ROW_RES_LIST row_res_list;
+
+ BLOCK_RES() {
+ } // empty constructor
+
+ BLOCK_RES(BLOCK *the_block); // real block
+
+ ~BLOCK_RES () { // destructor
+ }
};
/*************************************************************************
* ROW_RES - Row results
*************************************************************************/
-class ROW_RES:public ELIST_LINK //row result
-{
- public:
- ROW * row; //real row
- inT32 char_count; //chars in block
- inT32 rej_count; //rejected chars
- inT32 whole_word_rej_count; //rejs in total rej wds
- WERD_RES_LIST word_res_list;
- float font_class_score;
- inT16 font_class; //
- inT32 italic;
- inT32 bold;
- inT8 font1; //primary font
- inT8 font1_count; //no of voters
- inT8 font2; //secondary font
- inT8 font2_count; //no of voters
-
- ROW_RES() {
- } //empty constructor
-
- ROW_RES( //simple constructor
- ROW *the_row); //real row
-
- ~ROW_RES () { //destructor
- }
+class ROW_RES:public ELIST_LINK {
+ public:
+ ROW * row; // real row
+ inT32 char_count; // chars in block
+ inT32 rej_count; // rejected chars
+ inT32 whole_word_rej_count; // rejs in total rej wds
+ WERD_RES_LIST word_res_list;
+
+ ROW_RES() {
+ } // empty constructor
+
+ ROW_RES(bool right_to_left, ROW *the_row); // real row
+
+ ~ROW_RES() { // destructor
+ }
};
/*************************************************************************
@@ -134,180 +126,293 @@ enum CRUNCH_MODE
CR_DELETE
};
-class WERD_RES:public ELIST_LINK //word result
-{
- public:
- WERD * word; //non-bln real word
- WERD *outword; //bln best choice
- //segmentation
- DENORM denorm; //for use on outword
- WERD_CHOICE *best_choice; //tess output
- WERD_CHOICE *raw_choice; //top choice permuter
- WERD_CHOICE *ep_choice; //ep text
- REJMAP reject_map; //best_choice rejects
- BOOL8 tess_failed;
- /*
- If tess_failed is TRUE, one of the following tests failed when Tess
- returned:
- - The outword blob list was not the same length as the best_choice string;
- - The best_choice string contained ALL blanks;
- - The best_choice string was zero length
- */
- BOOL8 tess_accepted; //Tess thinks its ok?
- BOOL8 tess_would_adapt; //Tess would adapt?
- BOOL8 done; //ready for output?
- inT8 italic;
- inT8 bold;
- inT8 font1; //primary font
- inT8 font1_count; //no of voters
- inT8 font2; //secondary font
- inT8 font2_count; //no of voters
- CRUNCH_MODE unlv_crunch_mode;
- float x_height; //Post match estimate
- float caps_height; //Post match estimate
- BOOL8 guessed_x_ht;
- BOOL8 guessed_caps_ht;
- /*
- To deal with fuzzy spaces we need to be able to combine "words" to form
- combinations when we suspect that the gap is a non-space. The (new) text
- ord code generates separate words for EVERY fuzzy gap - flags in the word
- indicate whether the gap is below the threshold (fuzzy kern) and is thus
- NOT a real word break by default, or above the threshold (fuzzy space) and
- this is a real word break by default.
-
- The WERD_RES list contains all these words PLUS "combination" words built
- out of (copies of) the words split by fuzzy kerns. The separate parts have
- their "part_of_combo" flag set true and should be IGNORED on a default
- reading of the list.
-
- Combination words are FOLLOWED by the sequence of part_of_combo words
- which they combine.
- */
- BOOL8 combination; //of two fuzzy gap wds
- BOOL8 part_of_combo; //part of a combo
- BOOL8 reject_spaces; //Reject spacing?
-
- WERD_RES() {
- } //empty constructor
-
- WERD_RES( //simple constructor
- WERD *the_word) { //real word
- word = the_word;
- outword = NULL;
- best_choice = NULL;
- raw_choice = NULL;
- ep_choice = NULL;
- tess_failed = FALSE;
- tess_accepted = FALSE;
- tess_would_adapt = FALSE;
- done = FALSE;
- unlv_crunch_mode = CR_NONE;
- italic = FALSE;
- bold = FALSE;
- font1 = -1;
- font1_count = 0;
- font2 = -1;
- font2_count = 0;
- x_height = 0.0;
- caps_height = 0.0;
- guessed_x_ht = TRUE;
- guessed_caps_ht = TRUE;
- combination = FALSE;
- part_of_combo = FALSE;
- reject_spaces = FALSE;
- }
- WERD_RES(const WERD_RES &source) {
- *this = source; //see operator=
- }
-
- ~WERD_RES (); //destructor
-
- WERD_RES& operator=(const WERD_RES& source); //from this
-
- static WERD_RES* deep_copy(const WERD_RES* src) {
- return new WERD_RES(*src);
- }
-
- void copy_on( //copy blobs onto word
- WERD_RES *word_res) { //from this word
- word->set_flag (W_EOL, word_res->word->flag (W_EOL));
- word->copy_on (word_res->word);
- }
+// WERD_RES is a collection of publicly accessible members that gathers
+// information about a word result.
+class WERD_RES : public ELIST_LINK {
+ public:
+ // Which word is which?
+ // There are 3 coordinate spaces in use here: a possibly rotated pixel space,
+ // the original image coordinate space, and the BLN space in which the
+ // baseline of a word is at kBlnBaselineOffset, the xheight is kBlnXHeight,
+ // and the x-middle of the word is at 0.
+ // In the rotated pixel space, coordinates correspond to the input image,
+ // but may be rotated about the origin by a multiple of 90 degrees,
+ // and may therefore be negative.
+ // In any case a rotation by denorm.block()->re_rotation() will take them
+ // back to the original image.
+ // The other differences between words all represent different stages of
+ // processing.
+ //
+ // The word is the input C_BLOBs in the rotated pixel space.
+ // word is NOT owned by the WERD_RES unless combination is true.
+ // All the other word pointers ARE owned by the WERD_RES.
+ WERD* word; // Input C_BLOB word.
+ // The bln_boxes contains the bounding boxes (only) of the input word, in the
+ // BLN space. The lengths of word and bln_boxes
+ // match as they are both before any chopping.
+ // TODO(rays) determine if docqual does anything useful and delete bln_boxes
+ // if it doesn't.
+ tesseract::BoxWord* bln_boxes; // BLN input bounding boxes.
+ // The chopped_word is also in BLN space, and represents the fully chopped
+ // character fragments that make up the word.
+ // The length of chopped_word matches length of seam_array + 1 (if set).
+ TWERD* chopped_word; // BLN chopped fragments output.
+ SEAMS seam_array; // Seams matching chopped_word.
+ // The rebuild_word is also in BLN space, but represents the final best
+ // segmentation of the word. Its length is therefore the same as box_word.
+ TWERD* rebuild_word; // BLN best segmented word.
+ // The denorm provides the transformation to get back to the rotated image
+ // coords from the chopped_word/rebuild_word BLN coords.
+ DENORM denorm; // For use on chopped_word.
+ // The box_word is in the original image coordinate space. It is the
+ // bounding boxes of the rebuild_word, after denormalization.
+ // The length of box_word matches rebuild_word, best_state (if set) and
+ // correct_text (if set), as well as best_choice and represents the
+ // number of classified units in the output.
+ tesseract::BoxWord* box_word; // Denormalized output boxes.
+ // The best_state stores the relationship between chopped_word and
+ // rebuild_word. Each blob[i] in rebuild_word is composed of best_state[i]
+ // adjacent blobs in chopped_word. The seams in seam_array are hidden
+ // within a rebuild_word blob and revealed between them.
+ GenericVector best_state; // Number of blobs in each best blob.
+ // The correct_text is used during training and adaption to carry the
+ // text to the training system without the need for a unicharset. There
+ // is one entry in the vector for each blob in rebuild_word and box_word.
+ GenericVector correct_text;
+ WERD_CHOICE *best_choice; // tess output
+ WERD_CHOICE *raw_choice; // top choice permuter
+ WERD_CHOICE *ep_choice; // ep text TODO(rays) delete this.
+ REJMAP reject_map; // best_choice rejects
+ BOOL8 tess_failed;
+ /*
+ If tess_failed is TRUE, one of the following tests failed when Tess
+ returned:
+ - The outword blob list was not the same length as the best_choice string;
+ - The best_choice string contained ALL blanks;
+ - The best_choice string was zero length
+ */
+ BOOL8 tess_accepted; //Tess thinks its ok?
+ BOOL8 tess_would_adapt; //Tess would adapt?
+ BOOL8 done; //ready for output?
+ inT8 italic;
+ inT8 bold;
+ inT8 font1; //primary font
+ inT8 font1_count; //no of voters
+ inT8 font2; //secondary font
+ inT8 font2_count; //no of voters
+ CRUNCH_MODE unlv_crunch_mode;
+ float x_height; //Post match estimate
+ float caps_height; //Post match estimate
+ BOOL8 guessed_x_ht;
+ BOOL8 guessed_caps_ht;
+ /*
+ To deal with fuzzy spaces we need to be able to combine "words" to form
+ combinations when we suspect that the gap is a non-space. The (new) text
+ ord code generates separate words for EVERY fuzzy gap - flags in the word
+ indicate whether the gap is below the threshold (fuzzy kern) and is thus
+ NOT a real word break by default, or above the threshold (fuzzy space) and
+ this is a real word break by default.
+
+ The WERD_RES list contains all these words PLUS "combination" words built
+ out of (copies of) the words split by fuzzy kerns. The separate parts have
+ their "part_of_combo" flag set true and should be IGNORED on a default
+ reading of the list.
+
+ Combination words are FOLLOWED by the sequence of part_of_combo words
+ which they combine.
+ */
+ BOOL8 combination; //of two fuzzy gap wds
+ BOOL8 part_of_combo; //part of a combo
+ BOOL8 reject_spaces; //Reject spacing?
+ // FontInfo ids for each unichar in best_choice.
+ GenericVector best_choice_fontinfo_ids;
+
+ WERD_RES() {
+ InitPointers();
+ }
+ WERD_RES( //simple constructor
+ WERD *the_word) { //real word
+ InitPointers();
+ word = the_word;
+ tess_failed = FALSE;
+ tess_accepted = FALSE;
+ tess_would_adapt = FALSE;
+ done = FALSE;
+ unlv_crunch_mode = CR_NONE;
+ italic = FALSE;
+ bold = FALSE;
+ font1 = -1;
+ font1_count = 0;
+ font2 = -1;
+ font2_count = 0;
+ x_height = 0.0;
+ caps_height = 0.0;
+ guessed_x_ht = TRUE;
+ guessed_caps_ht = TRUE;
+ combination = FALSE;
+ part_of_combo = FALSE;
+ reject_spaces = FALSE;
+ }
+ WERD_RES(const WERD_RES &source) {
+ InitPointers();
+ *this = source; // see operator=
+ }
+
+ ~WERD_RES();
+ void InitPointers();
+ void Clear();
+ void ClearResults();
+
+ WERD_RES& operator=(const WERD_RES& source); //from this
+
+ void CopySimpleFields(const WERD_RES& source);
+
+ // Sets up the members used in recognition:
+ // bln_boxes, chopped_word, seam_array, denorm, best_choice, raw_choice.
+ // Returns false if the word is empty and sets up fake results.
+ bool SetupForRecognition(const UNICHARSET& unicharset,
+ bool numeric_mode, ROW *row, BLOCK* block);
+
+ // Builds the rebuild_word from the chopped_word and the best_state.
+ void RebuildBestState();
+
+ // Copies the chopped_word to the rebuild_word, faking a best_state as well.
+ // Also sets up the output box_word.
+ void CloneChoppedToRebuild();
+
+ // Sets/replaces the box_word with one made from the rebuild_word.
+ void SetupBoxWord();
+
+ // Classifies the word with some already-calculated BLOB_CHOICEs.
+ // The choices are an array of blob_count pointers to BLOB_CHOICE,
+ // providing a single classifier result for each blob.
+ // The BLOB_CHOICEs are consumed and the word takes ownership.
+ // The number of blobs in the outword must match blob_count.
+ void FakeClassifyWord(const UNICHARSET& unicharset, int blob_count,
+ BLOB_CHOICE** choices);
+
+ // Copies the best_choice strings to the correct_text for adaption/training.
+ void BestChoiceToCorrectText(const UNICHARSET& unicharset);
+
+ // Merges 2 adjacent blobs in the result if the permanent callback
+ // class_cb returns other than INVALID_UNICHAR_ID, AND the permanent
+ // callback box_cb is NULL or returns true, setting the merged blob
+ // result to the class returned from class_cb.
+ // Returns true if anything was merged.
+ bool ConditionalBlobMerge(
+ const UNICHARSET& unicharset,
+ TessResultCallback2* class_cb,
+ TessResultCallback2* box_cb,
+ BLOB_CHOICE_LIST_CLIST *blob_choices);
+
+ static WERD_RES* deep_copy(const WERD_RES* src) {
+ return new WERD_RES(*src);
+ }
+
+ // Copy blobs from word_res onto this word (eliminating spaces between).
+ // Since this may be called bidirectionally OR both the BOL and EOL flags.
+ void copy_on(WERD_RES *word_res) { //from this word
+ word->set_flag(W_BOL, word->flag(W_BOL) || word_res->word->flag(W_BOL));
+ word->set_flag(W_EOL, word->flag(W_EOL) || word_res->word->flag(W_EOL));
+ word->copy_on(word_res->word);
+ }
};
/*************************************************************************
* PAGE_RES_IT - Page results iterator
*************************************************************************/
-class PAGE_RES_IT
-{
- public:
- PAGE_RES * page_res; //page being iterated
-
- PAGE_RES_IT() {
- } //empty contructor
-
- PAGE_RES_IT( //empty contructor
- PAGE_RES *the_page_res) { //page result
- page_res = the_page_res;
- restart_page(); //ready to scan
- }
-
- WERD_RES *restart_page(); //get ready
-
- WERD_RES *internal_forward( //get next word
- BOOL8 new_block);
-
- WERD_RES *forward() { //get next word
- return internal_forward (FALSE);
- }
-
- WERD_RES *forward_block(); //get first word in
- //next non-empty block
- WERD_RES *prev_word() { //previous word
- return prev_word_res;
- }
- ROW_RES *prev_row() { //row of prev word
- return prev_row_res;
- }
- BLOCK_RES *prev_block() { //block of prev word
- return prev_block_res;
- }
- WERD_RES *word() { //current word
- return word_res;
- }
- ROW_RES *row() { //row of current word
- return row_res;
- }
- BLOCK_RES *block() { //block of cur. word
- return block_res;
- }
- WERD_RES *next_word() { //next word
- return next_word_res;
- }
- ROW_RES *next_row() { //row of next word
- return next_row_res;
- }
- BLOCK_RES *next_block() { //block of next word
- return next_block_res;
- }
- void rej_stat_word(); //for page/block/row
-
- private:
- WERD_RES * prev_word_res; //previous word
- ROW_RES *prev_row_res; //row of prev word
- BLOCK_RES *prev_block_res; //block of prev word
-
- WERD_RES *word_res; //current word
- ROW_RES *row_res; //row of current word
- BLOCK_RES *block_res; //block of cur. word
-
- WERD_RES *next_word_res; //next word
- ROW_RES *next_row_res; //row of next word
- BLOCK_RES *next_block_res; //block of next word
-
- BLOCK_RES_IT block_res_it; //iterators
- ROW_RES_IT row_res_it;
- WERD_RES_IT word_res_it;
+class PAGE_RES_IT {
+ public:
+ PAGE_RES * page_res; // page being iterated
+
+ PAGE_RES_IT() {
+ } // empty contructor
+
+ PAGE_RES_IT(PAGE_RES *the_page_res) { // page result
+ page_res = the_page_res;
+ restart_page(); // ready to scan
+ }
+
+ WERD_RES *restart_page() {
+ return start_page(false); // Skip empty blocks.
+ }
+ WERD_RES *restart_page_with_empties() {
+ return start_page(true); // Allow empty blocks.
+ }
+ WERD_RES *start_page(bool empty_ok);
+
+ // ============ Methods that mutate the underling structures ===========
+ // Note that these methods will potentially invalidate other PAGE_RES_ITs
+ // and are intended to be used only while a single PAGE_RES_IT is active.
+ // This problem needs to be taken into account if these mutation operators
+ // are ever provided to PageIterator or its subclasses.
+
+ // Inserts the new_word and a corresponding WERD_RES before the current
+ // position. The simple fields of the WERD_RES are copied from clone_res and
+ // the resulting WERD_RES is returned for further setup with best_choice etc.
+ WERD_RES* InsertCloneWord(const WERD_RES& clone_res, WERD* new_word);
+
+ // Deletes the current WERD_RES and its underlying WERD.
+ void DeleteCurrentWord();
+
+ WERD_RES *forward() { // Get next word.
+ return internal_forward(false, false);
+ }
+ // Move forward, but allow empty blocks to show as single NULL words.
+ WERD_RES *forward_with_empties() {
+ return internal_forward(false, true);
+ }
+
+ WERD_RES *forward_block(); // get first word in
+ // next non-empty block
+ WERD_RES *prev_word() const { // previous word
+ return prev_word_res;
+ }
+ ROW_RES *prev_row() const { // row of prev word
+ return prev_row_res;
+ }
+ BLOCK_RES *prev_block() const { // block of prev word
+ return prev_block_res;
+ }
+ WERD_RES *word() const { // current word
+ return word_res;
+ }
+ ROW_RES *row() const { // row of current word
+ return row_res;
+ }
+ BLOCK_RES *block() const { // block of cur. word
+ return block_res;
+ }
+ WERD_RES *next_word() const { // next word
+ return next_word_res;
+ }
+ ROW_RES *next_row() const { // row of next word
+ return next_row_res;
+ }
+ BLOCK_RES *next_block() const { // block of next word
+ return next_block_res;
+ }
+ void rej_stat_word(); // for page/block/row
+
+ private:
+ void ResetWordIterator();
+ WERD_RES *internal_forward(bool new_block, bool empty_ok);
+
+ WERD_RES * prev_word_res; // previous word
+ ROW_RES *prev_row_res; // row of prev word
+ BLOCK_RES *prev_block_res; // block of prev word
+
+ WERD_RES *word_res; // current word
+ ROW_RES *row_res; // row of current word
+ BLOCK_RES *block_res; // block of cur. word
+
+ WERD_RES *next_word_res; // next word
+ ROW_RES *next_row_res; // row of next word
+ BLOCK_RES *next_block_res; // block of next word
+
+ BLOCK_RES_IT block_res_it; // iterators
+ ROW_RES_IT row_res_it;
+ WERD_RES_IT word_res_it;
};
#endif
diff --git a/ccstruct/pdblock.cpp b/ccstruct/pdblock.cpp
index 02a7af8e3b..0b9f490426 100644
--- a/ccstruct/pdblock.cpp
+++ b/ccstruct/pdblock.cpp
@@ -19,6 +19,7 @@
#include "mfcpch.h"
#include
+#include "allheaders.h"
#include "blckerr.h"
#include "pdblock.h"
#include "svshowim.h"
@@ -127,6 +128,48 @@ void PDBLK::move( // reposition block
box.move (vec);
}
+// Returns a binary Pix mask with a 1 pixel for every pixel within the
+// block. Rotates the coordinate system by rerotation prior to rendering.
+Pix* PDBLK::render_mask(const FCOORD& rerotation) {
+ TBOX rotated_box(box);
+ rotated_box.rotate(rerotation);
+ Pix* pix = pixCreate(rotated_box.width(), rotated_box.height(), 1);
+ if (hand_poly != NULL) {
+ // We are going to rotate, so get a deep copy of the points and
+ // make a new POLY_BLOCK with it.
+ ICOORDELT_LIST polygon;
+ polygon.deep_copy(hand_poly->points(), ICOORDELT::deep_copy);
+ POLY_BLOCK image_block(&polygon, hand_poly->isA());
+ image_block.rotate(rerotation);
+ // Block outline is a polygon, so use a PB_LINE_IT to get the
+ // rasterized interior. (Runs of interior pixels on a line.)
+ PB_LINE_IT *lines = new PB_LINE_IT(&image_block);
+ for (int y = box.bottom(); y < box.top(); ++y) {
+ ICOORDELT_LIST* segments = lines->get_line(y);
+ if (!segments->empty()) {
+ ICOORDELT_IT s_it(segments);
+ // Each element of segments is a start x and x size of the
+ // run of interior pixels.
+ for (s_it.mark_cycle_pt(); !s_it.cycled_list(); s_it.forward()) {
+ int start = s_it.data()->x();
+ int xext = s_it.data()->y();
+ // Set the run of pixels to 1.
+ pixRasterop(pix, start - rotated_box.left(),
+ rotated_box.height() - 1 - (y - rotated_box.bottom()),
+ xext, 1, PIX_SET, NULL, 0, 0);
+ }
+ }
+ delete segments;
+ }
+ delete lines;
+ } else {
+ // Just fill the whole block as there is only a bounding box.
+ pixRasterop(pix, 0, 0, rotated_box.width(), rotated_box.height(),
+ PIX_SET, NULL, 0, 0);
+ }
+ return pix;
+}
+
/**********************************************************************
* PDBLK::plot
diff --git a/ccstruct/pdblock.h b/ccstruct/pdblock.h
index 5233d558e5..a3fb54ed4f 100644
--- a/ccstruct/pdblock.h
+++ b/ccstruct/pdblock.h
@@ -1,8 +1,8 @@
/**********************************************************************
* File: pdblock.h (Formerly pdblk.h)
* Description: Page block class definition.
- * Author: Ray Smith
- * Created: Thu Mar 14 17:32:01 GMT 1991
+ * Author: Ray Smith
+ * Created: Thu Mar 14 17:32:01 GMT 1991
*
* (C) Copyright 1991, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,6 +20,7 @@
#ifndef PDBLOCK_H
#define PDBLOCK_H
+#include "clst.h"
#include "img.h"
#include "strngs.h"
#include "polyblk.h"
@@ -27,10 +28,11 @@
#include "hpddef.h" //must be last (handpd.dll)
class DLLSYM PDBLK; //forward decl
+struct Pix;
CLISTIZEH (PDBLK)
///page block
-class DLLSYM PDBLK
+class PDBLK
{
friend class BLOCK_RECT_IT; //< block iterator
@@ -40,19 +42,19 @@ class DLLSYM PDBLK
hand_poly = NULL;
index_ = 0;
}
- ///simple constructor
+ ///simple constructor
PDBLK(inT16 xmin, //< bottom left
inT16 ymin,
inT16 xmax, //< top right
inT16 ymax);
- ///set vertex lists
- ///@param left list of left vertices
- ///@param right list of right vertices
+ ///set vertex lists
+ ///@param left list of left vertices
+ ///@param right list of right vertices
void set_sides(ICOORDELT_LIST *left,
ICOORDELT_LIST *right);
- ///destructor
+ ///destructor
~PDBLK () {
if (hand_poly) delete hand_poly;
}
@@ -60,11 +62,11 @@ class DLLSYM PDBLK
POLY_BLOCK *poly_block() {
return hand_poly;
}
- ///set the poly block
+ ///set the poly block
void set_poly_block(POLY_BLOCK *blk) {
hand_poly = blk;
}
- ///get box
+ ///get box
void bounding_box(ICOORD &bottom_left, //bottom left
ICOORD &top_right) const { //topright
bottom_left = box.botleft ();
@@ -82,28 +84,31 @@ class DLLSYM PDBLK
index_ = value;
}
- ///is pt inside block
+ ///is pt inside block
BOOL8 contains(ICOORD pt);
- /// reposition block
+ /// reposition block
void move(const ICOORD vec); // by vector
- ///draw histogram
- ///@param window window to draw in
- ///@param serial serial number
- ///@param colour colour to draw in
+ // Returns a binary Pix mask with a 1 pixel for every pixel within the
+ // block. Rotates the coordinate system by rerotation prior to rendering.
+ Pix* render_mask(const FCOORD& rerotation);
+ ///draw histogram
+ ///@param window window to draw in
+ ///@param serial serial number
+ ///@param colour colour to draw in
void plot(ScrollView* window,
inT32 serial,
ScrollView::Color colour);
- ///show image
- ///@param image image to show
- ///@param window window to show in
+ ///show image
+ ///@param image image to show
+ ///@param window window to show in
void show(IMAGE *image,
ScrollView* window);
- ///assignment
- ///@param source from this
+ ///assignment
+ ///@param source from this
PDBLK & operator= (const PDBLK & source);
protected:
@@ -121,24 +126,25 @@ class DLLSYM BLOCK_RECT_IT //rectangle iterator
///@param blkptr block to iterate
BLOCK_RECT_IT(PDBLK *blkptr);
+ NEWDELETE2 (BLOCK_RECT_IT)
///start (new) block
- NEWDELETE2 (BLOCK_RECT_IT) void set_to_block (
+ void set_to_block (
PDBLK * blkptr); //block to iterate
- ///start iteration
+ ///start iteration
void start_block();
- ///next rectangle
+ ///next rectangle
void forward();
- ///test end
+ ///test end
BOOL8 cycled_rects() {
return left_it.cycled_list () && right_it.cycled_list ();
}
- ///current rectangle
- ///@param bleft bottom left
- ///@param tright top right
+ ///current rectangle
+ ///@param bleft bottom left
+ ///@param tright top right
void bounding_box(ICOORD &bleft,
ICOORD &tright) {
//bottom left
@@ -166,17 +172,18 @@ class DLLSYM BLOCK_LINE_IT
block = blkptr; //remember block
}
+ NEWDELETE2 (BLOCK_LINE_IT)
///start (new) block
- ///@param blkptr block to start
- NEWDELETE2 (BLOCK_LINE_IT) void set_to_block (PDBLK * blkptr) {
+ ///@param blkptr block to start
+ void set_to_block (PDBLK * blkptr) {
block = blkptr; //remember block
//set iterator
rect_it.set_to_block (blkptr);
}
- ///get a line
- ///@param y line to get
- ///@param xext output extent
+ ///get a line
+ ///@param y line to get
+ ///@param xext output extent
inT16 get_line(inT16 y,
inT16 &xext);
diff --git a/ccstruct/points.h b/ccstruct/points.h
index 31e4fc9337..6284c126ba 100644
--- a/ccstruct/points.h
+++ b/ccstruct/points.h
@@ -1,8 +1,8 @@
/**********************************************************************
* File: points.h (Formerly coords.h)
* Description: Coordinate class definitions.
- * Author: Ray Smith
- * Created: Fri Mar 15 08:32:45 GMT 1991
+ * Author: Ray Smith
+ * Created: Fri Mar 15 08:32:45 GMT 1991
*
* (C) Copyright 1991, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,12 +23,11 @@
#include
#include
#include "elst.h"
-//#include "ipeerr.h"
class FCOORD;
///integer coordinate
-class DLLSYM ICOORD
+class ICOORD
{
friend class FCOORD;
@@ -37,15 +36,15 @@ class DLLSYM ICOORD
ICOORD() {
xcoord = ycoord = 0; //default zero
}
- ///constructor
- ///@param xin x value
- ///@param yin y value
+ ///constructor
+ ///@param xin x value
+ ///@param yin y value
ICOORD(inT16 xin,
inT16 yin) {
xcoord = xin;
ycoord = yin;
}
- ///destructor
+ ///destructor
~ICOORD () {
}
@@ -54,16 +53,16 @@ class DLLSYM ICOORD
{
return xcoord;
}
- ///access_function
+ ///access_function
inT16 y() const {
return ycoord;
}
- ///rewrite function
+ ///rewrite function
void set_x(inT16 xin) {
xcoord = xin; //write new value
}
- ///rewrite function
+ ///rewrite function
void set_y(inT16 yin) { //value to set
ycoord = yin;
}
@@ -71,17 +70,17 @@ class DLLSYM ICOORD
/// Set from the given x,y, shrinking the vector to fit if needed.
void set_with_shrink(int x, int y);
- ///find sq length
+ ///find sq length
float sqlength() const {
return (float) (xcoord * xcoord + ycoord * ycoord);
}
- ///find length
+ ///find length
float length() const {
return (float) sqrt (sqlength ());
}
- ///sq dist between pts
+ ///sq dist between pts
float pt_to_pt_sqdist(const ICOORD &pt) const {
ICOORD gap;
@@ -90,55 +89,55 @@ class DLLSYM ICOORD
return gap.sqlength ();
}
- ///Distance between pts
+ ///Distance between pts
float pt_to_pt_dist(const ICOORD &pt) const {
return (float) sqrt (pt_to_pt_sqdist (pt));
}
- ///find angle
+ ///find angle
float angle() const {
return (float) atan2 ((double) ycoord, (double) xcoord);
}
- ///test equality
+ ///test equality
BOOL8 operator== (const ICOORD & other) {
return xcoord == other.xcoord && ycoord == other.ycoord;
}
- ///test inequality
+ ///test inequality
BOOL8 operator!= (const ICOORD & other) {
return xcoord != other.xcoord || ycoord != other.ycoord;
}
- ///rotate 90 deg anti
+ ///rotate 90 deg anti
friend ICOORD operator! (const ICOORD &);
///unary minus
- friend ICOORD operator- (const ICOORD &);
- ///add
- friend ICOORD operator+ (const ICOORD &, const ICOORD &);
- ///add
+ friend ICOORD operator- (const ICOORD &);
+ ///add
+ friend ICOORD operator+ (const ICOORD &, const ICOORD &);
+ ///add
friend ICOORD & operator+= (ICOORD &, const ICOORD &);
///subtract
- friend ICOORD operator- (const ICOORD &, const ICOORD &);
+ friend ICOORD operator- (const ICOORD &, const ICOORD &);
///subtract
friend ICOORD & operator-= (ICOORD &, const ICOORD &);
- ///scalar product
- friend inT32 operator% (const ICOORD &, const ICOORD &);
- ///cross product
+ ///scalar product
+ friend inT32 operator% (const ICOORD &, const ICOORD &);
+ ///cross product
friend inT32 operator *(const ICOORD &,
const ICOORD &);
- ///multiply
+ ///multiply
friend ICOORD operator *(const ICOORD &,
inT16);
- ///multiply
+ ///multiply
friend ICOORD operator *(inT16,
const ICOORD &);
- ///multiply
+ ///multiply
friend ICOORD & operator*= (ICOORD &, inT16);
///divide
friend ICOORD operator/ (const ICOORD &, inT16);
///divide
friend ICOORD & operator/= (ICOORD &, inT16);
- ///rotate
- ///@param vec by vector
+ ///rotate
+ ///@param vec by vector
void rotate(const FCOORD& vec);
/// Setup for iterating over the pixels in a vector by the well-known
@@ -149,9 +148,9 @@ class DLLSYM ICOORD
void setup_render(ICOORD* major_step, ICOORD* minor_step,
int* major, int* minor) const;
- ///serialise to ascii
+ ///serialise to ascii
void serialise_asc(FILE *f);
- ///serialise from ascii
+ ///serialise from ascii
void de_serialise_asc(FILE *f);
protected:
@@ -166,12 +165,12 @@ class DLLSYM ICOORDELT:public ELIST_LINK, public ICOORD
///empty constructor
ICOORDELT() {
}
- ///constructor from ICOORD
+ ///constructor from ICOORD
ICOORDELT (ICOORD icoord):ICOORD (icoord) {
}
- ///constructor
- ///@param xin x value
- ///@param yin y value
+ ///constructor
+ ///@param xin x value
+ ///@param yin y value
ICOORDELT(inT16 xin,
inT16 yin) {
xcoord = xin;
@@ -181,15 +180,15 @@ class DLLSYM ICOORDELT:public ELIST_LINK, public ICOORD
/* Note that prep_serialise() dump() and de_dump() dont need to do anything
more than terminate recursion. */
- ///set ptrs to counts
+ ///set ptrs to counts
void prep_serialise() const {
}
- ///write external bits
+ ///write external bits
void dump(FILE *) const {
}
- ///read external bits
+ ///read external bits
void de_dump(FILE *) {
}
@@ -202,9 +201,9 @@ class DLLSYM ICOORDELT:public ELIST_LINK, public ICOORD
return elt;
}
- ///serialise to ascii
+ ///serialise to ascii
void serialise_asc(FILE * f);
- ///deserialise from ascii
+ ///deserialise from ascii
void de_serialise_asc(FILE *f);
};
@@ -216,9 +215,9 @@ class DLLSYM FCOORD
///empty constructor
FCOORD() {
}
- ///constructor
- ///@param xvalue x value
- ///@param yvalue y value
+ ///constructor
+ ///@param xvalue x value
+ ///@param yvalue y value
FCOORD(float xvalue,
float yvalue) {
xcoord = xvalue; //set coords
@@ -236,26 +235,26 @@ class DLLSYM FCOORD
float y() const {
return ycoord;
}
- ///rewrite function
+ ///rewrite function
void set_x(float xin) {
xcoord = xin; //write new value
}
- ///rewrite function
+ ///rewrite function
void set_y(float yin) { //value to set
ycoord = yin;
}
- ///find sq length
+ ///find sq length
float sqlength() const {
return xcoord * xcoord + ycoord * ycoord;
}
- ///find length
+ ///find length
float length() const {
return (float) sqrt (sqlength ());
}
- ///sq dist between pts
+ ///sq dist between pts
float pt_to_pt_sqdist(const FCOORD &pt) const {
FCOORD gap;
@@ -264,24 +263,24 @@ class DLLSYM FCOORD
return gap.sqlength ();
}
- ///Distance between pts
+ ///Distance between pts
float pt_to_pt_dist(const FCOORD &pt) const {
return (float) sqrt (pt_to_pt_sqdist (pt));
}
- ///find angle
+ ///find angle
float angle() const {
return (float) atan2 (ycoord, xcoord);
}
- ///Convert to unit vec
+ ///Convert to unit vec
bool normalise();
- ///test equality
+ ///test equality
BOOL8 operator== (const FCOORD & other) {
return xcoord == other.xcoord && ycoord == other.ycoord;
}
- ///test inequality
+ ///test inequality
BOOL8 operator!= (const FCOORD & other) {
return xcoord != other.xcoord || ycoord != other.ycoord;
}
@@ -302,7 +301,7 @@ class DLLSYM FCOORD
///cross product
friend float operator *(const FCOORD &, const FCOORD &);
///multiply
- friend FCOORD operator *(const FCOORD &, float);
+ friend FCOORD operator *(const FCOORD &, float);
///multiply
friend FCOORD operator *(float, const FCOORD &);
@@ -310,8 +309,8 @@ class DLLSYM FCOORD
friend FCOORD & operator*= (FCOORD &, float);
///divide
friend FCOORD operator/ (const FCOORD &, float);
- ///rotate
- ///@param vec by vector
+ ///rotate
+ ///@param vec by vector
void rotate(const FCOORD vec);
///divide
friend FCOORD & operator/= (FCOORD &, float);
diff --git a/ccstruct/polyaprx.cpp b/ccstruct/polyaprx.cpp
index 67d0a0b5c7..e2f671474a 100644
--- a/ccstruct/polyaprx.cpp
+++ b/ccstruct/polyaprx.cpp
@@ -24,7 +24,7 @@
#endif
#define FASTEDGELENGTH 256
#include "polyaprx.h"
-#include "varable.h"
+#include "params.h"
#include "tprintf.h"
#define EXTERN
@@ -33,41 +33,33 @@ EXTERN BOOL_VAR (poly_debug, FALSE, "Debug old poly");
EXTERN BOOL_VAR (poly_wide_objects_better, TRUE,
"More accurate approx on wide things");
-static int par1, par2;
-#define CONVEX 1 /*OUTLINE point is convex */
-#define CONCAVE 2 /*used and set only in edges */
#define FIXED 4 /*OUTLINE point is fixed */
-#define ONHULL 8 /*on convex hull */
#define RUNLENGTH 1 /*length of run */
#define DIR 2 /*direction of run */
-#define CORRECTION 3 /*correction of run */
-//#define MAXSHORT 32767 /*max value of short*/
#define FLAGS 0
#define fixed_dist 20 //really an int_variable
#define approx_dist 15 //really an int_variable
-#define point_diff(p,p1,p2) (p).x = (p1).x - (p2).x ; (p).y = (p1).y - (p2).y
-#define CROSS(a,b) ((a).x * (b).y - (a).y * (b).x)
-#define LENGTH(a) ((a).x * (a).x + (a).y * (a).y)
+const int par1 = 4500 / (approx_dist * approx_dist);
+const int par2 = 6750 / (approx_dist * approx_dist);
-#define DISTANCE(a,b) (((b).x-(a).x) * ((b).x-(a).x) \
- + ((b).y-(a).y) * ((b).y-(a).y))
/**********************************************************************
* tesspoly_outline
*
- * Approximate an outline from c form using the old tess algorithm.
+ * Approximate an outline from chain codes form using the old tess algorithm.
**********************************************************************/
-OUTLINE *tesspoly_outline( //old approximation
- C_OUTLINE *c_outline, //input
- float //xheight
- ) {
+#ifndef NO_PBLOB_POLY
+// TODO(rays) This code is scheduled for deletion, but first all dependencies
+// have to be removed or rewritten. NO_BLOB_POLY is used for finding the
+// dependencies.
+OUTLINE *tesspoly_outline(C_OUTLINE *c_outline) {
EDGEPT *edgept; //converted steps
EDGEPT *startpt; //start of outline
TBOX loop_box; //bounding box
@@ -94,11 +86,11 @@ OUTLINE *tesspoly_outline( //old approximation
edgept = poly2 (edgepts, area);/*2nd approximation */
startpt = edgept;
do {
- pos = FCOORD (edgept->pos.x, edgept->pos.y);
- vec = FCOORD (edgept->vec.x, edgept->vec.y);
- polypt = new POLYPT (pos, vec);
+ 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);
+ poly_it.add_after_then_move(polypt);
edgept = edgept->next;
}
while (edgept != startpt);
@@ -109,6 +101,50 @@ OUTLINE *tesspoly_outline( //old approximation
else
return new OUTLINE(&poly_it);
}
+#endif
+
+TESSLINE* ApproximateOutline(C_OUTLINE* c_outline) {
+ EDGEPT *edgept; // converted steps
+ TBOX loop_box; // bounding box
+ inT32 area; // loop area
+ EDGEPT stack_edgepts[FASTEDGELENGTH]; // converted path
+ EDGEPT* edgepts = stack_edgepts;
+
+ // Use heap memory if the stack buffer is not big enough.
+ if (c_outline->pathlength() > FASTEDGELENGTH)
+ edgepts = new EDGEPT[c_outline->pathlength()];
+
+ loop_box = c_outline->bounding_box();
+ area = loop_box.height();
+ if (!poly_wide_objects_better && loop_box.width() > area)
+ area = loop_box.width();
+ area *= area;
+ edgept = edgesteps_to_edgepts(c_outline, edgepts);
+ fix2(edgepts, area);
+ edgept = poly2 (edgepts, area); // 2nd approximation.
+ EDGEPT* startpt = edgept;
+ EDGEPT* result = NULL;
+ EDGEPT* prev_result = NULL;
+ do {
+ EDGEPT* new_pt = new EDGEPT;
+ new_pt->pos = edgept->pos;
+ new_pt->prev = prev_result;
+ if (prev_result == NULL) {
+ result = new_pt;
+ } else {
+ prev_result->next = new_pt;
+ new_pt->prev = prev_result;
+ }
+ prev_result = new_pt;
+ edgept = edgept->next;
+ }
+ while (edgept != startpt);
+ prev_result->next = result;
+ result->prev = prev_result;
+ if (edgepts != stack_edgepts)
+ delete [] edgepts;
+ return TESSLINE::BuildFromOutlineList(result);
+}
/**********************************************************************
@@ -349,6 +385,11 @@ void fix2( //polygonal approx
break; //already too few
point_diff (d12vec, edgefix1->pos, edgefix2->pos);
d12 = LENGTH (d12vec);
+ // TODO(rays) investigate this change:
+ // Only unfix a point if it is part of a low-curvature section
+ // of outline and the total angle change of the outlines is
+ // less than 90 degrees, ie the scalar product is positive.
+ // if (d12 <= gapmin && SCALAR(edgefix0->vec, edgefix2->vec) > 0) {
if (d12 <= gapmin) {
point_diff (d01vec, edgefix0->pos, edgefix1->pos);
d01 = LENGTH (d01vec);
@@ -357,16 +398,10 @@ void fix2( //polygonal approx
if (d01 > d23) {
edgefix2->flags[FLAGS] &= ~FIXED;
fixed_count--;
- /* if ( plots[EDGE] & PATHS )
- mark(edgefd,edgefix2->pos.x,edgefix2->pos.y,PLUS);
- */
}
else {
edgefix1->flags[FLAGS] &= ~FIXED;
fixed_count--;
- /* if ( plots[EDGE] & PATHS )
- mark(edgefd,edgefix1->pos.x,edgefix1->pos.y,PLUS);
- */
edgefix1 = edgefix2;
}
}
@@ -407,11 +442,6 @@ EDGEPT *poly2( //second poly
if (area < 1200)
area = 1200; /*minimum value */
- /*1200(4) */
- par1 = 4500 / (approx_dist * approx_dist);
- /*1200(6) */
- par2 = 6750 / (approx_dist * approx_dist);
-
loopstart = NULL; /*not found it yet */
edgept = startpt; /*start of loop */
diff --git a/ccstruct/polyaprx.h b/ccstruct/polyaprx.h
index 6e6feaef57..c7e261da2e 100644
--- a/ccstruct/polyaprx.h
+++ b/ccstruct/polyaprx.h
@@ -20,14 +20,13 @@
#ifndef POLYAPRX_H
#define POLYAPRX_H
-#include "tessclas.h"
+#include "blobs.h"
#include "poutline.h"
#include "coutln.h"
-OUTLINE *tesspoly_outline( //old approximation
- C_OUTLINE *c_outline, //input
- float //xheight
- );
+// convert a chain-coded input to the old OUTLINE approximation
+OUTLINE *tesspoly_outline(C_OUTLINE *c_outline);
+TESSLINE* ApproximateOutline(C_OUTLINE *c_outline);
EDGEPT *edgesteps_to_edgepts ( //convert outline
C_OUTLINE * c_outline, //input
EDGEPT edgepts[] //output is array
@@ -44,8 +43,4 @@ void cutline( //recursive refine
EDGEPT *last,
int area /*area of object */
);
-#define fixed_dist 20 //really an int_variable
-#define point_diff(p,p1,p2) (p).x = (p1).x - (p2).x ; (p).y = (p1).y - (p2).y
-#define CROSS(a,b) ((a).x * (b).y - (a).y * (b).x)
-#define LENGTH(a) ((a).x * (a).x + (a).y * (a).y)
#endif
diff --git a/ccstruct/polyblk.cpp b/ccstruct/polyblk.cpp
index e071698fd0..a0ebe96b05 100644
--- a/ccstruct/polyblk.cpp
+++ b/ccstruct/polyblk.cpp
@@ -46,6 +46,19 @@ POLY_BLOCK::POLY_BLOCK(ICOORDELT_LIST *points, PolyBlockType t) {
type = t;
}
+// Initialize from box coordinates.
+POLY_BLOCK::POLY_BLOCK(const TBOX& box, PolyBlockType t) {
+ vertices.clear();
+ ICOORDELT_IT v = &vertices;
+ v.move_to_first();
+ v.add_to_end(new ICOORDELT(box.left(), box.top()));
+ v.add_to_end(new ICOORDELT(box.left(), box.top() + box.height()));
+ v.add_to_end(new ICOORDELT(box.left() + box.width(),
+ box.top() + box.height()));
+ v.add_to_end(new ICOORDELT(box.left(), box.top() + box.height()));
+ compute_bb();
+ type = t;
+}
/**
* @name POLY_BLOCK::compute_bb
@@ -196,7 +209,7 @@ void POLY_BLOCK::rotate(FCOORD rotation) {
* POLY_BLOCK::move
*
* Move the POLY_BLOCK.
- * @param shift cos, sin of angle
+ * @param shift x,y translation vector
*/
void POLY_BLOCK::move(ICOORD shift) {
@@ -393,24 +406,24 @@ void POLY_BLOCK::de_serialise_asc(FILE *f) {
/// Returns a color to draw the given type.
ScrollView::Color POLY_BLOCK::ColorForPolyBlockType(PolyBlockType type) {
+ // Keep kPBColors in sync with PolyBlockType.
const ScrollView::Color kPBColors[PT_COUNT] = {
- ScrollView::WHITE,
- ScrollView::BLUE,
- ScrollView::CYAN,
- ScrollView::MEDIUM_BLUE,
- ScrollView::MAGENTA,
- ScrollView::YELLOW,
- ScrollView::RED,
- ScrollView::MAROON,
- ScrollView::ORANGE,
- ScrollView::GREEN,
- ScrollView::LIME_GREEN,
- ScrollView::DARK_GREEN,
- ScrollView::GREY
+ ScrollView::WHITE, // Type is not yet known. Keep as the 1st element.
+ ScrollView::BLUE, // Text that lives inside a column.
+ ScrollView::CYAN, // Text that spans more than one column.
+ ScrollView::MEDIUM_BLUE, // Text that is in a cross-column pull-out region.
+ ScrollView::MAGENTA, // Partition belonging to a table region.
+ ScrollView::GREEN, // Text-line runs vertically.
+ ScrollView::LIGHT_BLUE, // Text that belongs to an image.
+ ScrollView::RED, // Image that lives inside a column.
+ ScrollView::YELLOW, // Image that spans more than one column.
+ ScrollView::ORANGE, // Image in a cross-column pull-out region.
+ ScrollView::BROWN, // Horizontal Line.
+ ScrollView::DARK_GREEN, // Vertical Line.
+ ScrollView::GREY // Lies outside of any column.
};
if (type >= 0 && type < PT_COUNT) {
return kPBColors[type];
}
return ScrollView::WHITE;
}
-
diff --git a/ccstruct/polyblk.h b/ccstruct/polyblk.h
index aeb6ddb491..0749645c92 100644
--- a/ccstruct/polyblk.h
+++ b/ccstruct/polyblk.h
@@ -19,37 +19,20 @@
#ifndef POLYBLK_H
#define POLYBLK_H
-#include "rect.h"
-#include "points.h"
-#include "scrollview.h"
-#include "elst.h"
+#include "publictypes.h"
+#include "elst.h"
+#include "points.h"
+#include "rect.h"
+#include "scrollview.h"
#include "hpddef.h" // must be last (handpd.dll)
-// Possible types for a POLY_BLOCK or ColPartition. Must be kept in sync with
-// kPBColors. Used extensively by ColPartition, but polyblk is a lower-level
-// file.
-enum PolyBlockType {
- PT_UNKNOWN, // Type is not yet known. Keep as the first element.
- PT_FLOWING_TEXT, // Text that lives inside a column.
- PT_HEADING_TEXT, // Text that spans more than one column.
- PT_PULLOUT_TEXT, // Text that is in a cross-column pull-out region.
- PT_TABLE, // Partition belonging to a table region.
- PT_VERTICAL_TEXT, // Text-line runs vertically.
- PT_FLOWING_IMAGE, // Image that lives inside a column.
- PT_HEADING_IMAGE, // Image that spans more than one column.
- PT_PULLOUT_IMAGE, // Image that is in a cross-column pull-out region.
- PT_FLOWING_LINE, // H-Line that lives inside a column.
- PT_HEADING_LINE, // H-Line that spans more than one column.
- PT_PULLOUT_LINE, // H-Line that is in a cross-column pull-out region.
- PT_NOISE, // Lies outside of any column.
- PT_COUNT
-};
-
class DLLSYM POLY_BLOCK {
public:
POLY_BLOCK() {
}
+ // Initialize from box coordinates.
+ POLY_BLOCK(const TBOX& box, PolyBlockType type);
POLY_BLOCK(ICOORDELT_LIST *points, PolyBlockType type);
~POLY_BLOCK () {
}
@@ -69,7 +52,7 @@ class DLLSYM POLY_BLOCK {
}
bool IsText() const {
- return IsTextType(type);
+ return PTIsTextType(type);
}
// Rotate about the origin by the given rotation. (Analogous to
@@ -112,23 +95,6 @@ class DLLSYM POLY_BLOCK {
// Returns a color to draw the given type.
static ScrollView::Color ColorForPolyBlockType(PolyBlockType type);
- // Returns true if PolyBlockType is of horizontal line type
- static bool IsLineType(PolyBlockType type) {
- return (type == PT_FLOWING_LINE) || (type == PT_HEADING_LINE) ||
- (type == PT_PULLOUT_LINE);
- }
- // Returns true if PolyBlockType is of image type
- static bool IsImageType(PolyBlockType type) {
- return (type == PT_FLOWING_IMAGE) || (type == PT_HEADING_IMAGE) ||
- (type == PT_PULLOUT_IMAGE);
- }
- // Returns true if PolyBlockType is of text type
- static bool IsTextType(PolyBlockType type) {
- return (type == PT_FLOWING_TEXT) || (type == PT_HEADING_TEXT) ||
- (type == PT_PULLOUT_TEXT) || (type == PT_TABLE) ||
- (type == PT_VERTICAL_TEXT);
- }
-
private:
ICOORDELT_LIST vertices; // vertices
TBOX box; // bounding box
diff --git a/ccstruct/polyblob.cpp b/ccstruct/polyblob.cpp
index d080976f39..0cad60d605 100644
--- a/ccstruct/polyblob.cpp
+++ b/ccstruct/polyblob.cpp
@@ -18,7 +18,7 @@
**********************************************************************/
#include "mfcpch.h"
-#include "varable.h"
+#include "params.h"
#include "ocrrow.h"
#include "polyblob.h"
//#include "lapoly.h"
@@ -31,9 +31,6 @@
#define EXTERN
-EXTERN BOOL_VAR (polygon_tess_approximation, TRUE,
-"Do tess poly instead of greyscale");
-
ELISTIZE_S (PBLOB)
/**********************************************************************
* position_outline
@@ -146,56 +143,41 @@ PBLOB::PBLOB( //constructor
/**********************************************************************
* approximate_outline_list
*
- * Convert a list of outlines to polygonal form.
+ * Convert a list of chain-coded outlines (srclist) to polygonal form.
**********************************************************************/
-static void approximate_outline_list( //do list of outlines
- C_OUTLINE_LIST *srclist, //list to convert
- OUTLINE_LIST *destlist, //desstination list
- float xheight //height of line
- ) {
- C_OUTLINE *src_outline; //outline from src list
- OUTLINE *dest_outline; //result
- C_OUTLINE_IT src_it = srclist; //source iterator
- OUTLINE_IT dest_it = destlist; //iterator
+static void approximate_outline_list(C_OUTLINE_LIST *srclist,
+ OUTLINE_LIST *destlist) {
+ C_OUTLINE *src_outline; // outline from src list
+ OUTLINE *dest_outline; // result
+ C_OUTLINE_IT src_it = srclist; // source iterator
+ OUTLINE_IT dest_it = destlist; // iterator
do {
src_outline = src_it.data ();
- // if (polygon_tess_approximation)
- dest_outline = tesspoly_outline (src_outline, xheight);
- // else
- // dest_outline=greypoly_outline(src_outline,xheight);
+ dest_outline = tesspoly_outline(src_outline);
if (dest_outline != NULL) {
- dest_it.add_after_then_move (dest_outline);
- if (!src_outline->child ()->empty ())
- //do child list
- approximate_outline_list (src_outline->child (), dest_outline->child (), xheight);
+ dest_it.add_after_then_move(dest_outline);
+ if (!src_outline->child()->empty())
+ // do child list
+ approximate_outline_list(src_outline->child(), dest_outline->child());
}
- src_it.forward ();
+ src_it.forward();
}
- while (!src_it.at_first ());
+ while (!src_it.at_first());
}
/**********************************************************************
* PBLOB::PBLOB
*
- * Constructor to build a PBLOB from a C_BLOB by polygonal approximation.
+ * Constructor to build a PBLOB (polygonal blob) from a C_BLOB
+ * (chain-coded blob) by polygonal approximation.
**********************************************************************/
-PBLOB::PBLOB( //constructor
- C_BLOB *cblob, //compact blob
- float xheight //height of line
- ) {
- TBOX bbox; //bounding box
-
+PBLOB::PBLOB(C_BLOB *cblob) {
if (!cblob->out_list ()->empty ()) {
- //get bounding box
- bbox = cblob->bounding_box ();
- if (bbox.height () > xheight)
- xheight = bbox.height (); //max of line and blob
- //copy it
- approximate_outline_list (cblob->out_list (), &outlines, xheight);
+ approximate_outline_list (cblob->out_list (), &outlines);
}
}
@@ -253,12 +235,12 @@ PBLOB *PBLOB::baseline_normalise( //normalize blob
float x_centre = (blob_box.left () + blob_box.right ()) / 2.0;
PBLOB *bn_blob; //copied blob
- *denorm = DENORM (x_centre, bln_x_height / row->x_height (), row);
+ *denorm = DENORM (x_centre, kBlnXHeight / row->x_height (), row);
bn_blob = new PBLOB; //get one
*bn_blob = *this; //deep copy
bn_blob->move (FCOORD (-denorm->origin (), -row->base_line (x_centre)));
bn_blob->scale (denorm->scale ());
- bn_blob->move (FCOORD (0.0, bln_baseline_offset));
+ bn_blob->move (FCOORD (0.0, kBlnBaselineOffset));
return bn_blob;
}
@@ -269,18 +251,12 @@ PBLOB *PBLOB::baseline_normalise( //normalize blob
* DeBaseline Normalise the blob properly with the given denorm.
**********************************************************************/
-void PBLOB::baseline_denormalise( // Tess style BL Norm
- const DENORM *denorm //antidote
- ) {
- float blob_x_left; // Left edge of blob.
- TBOX blob_box; //blob bounding box
-
- move(FCOORD (0.0f, 0.0f - bln_baseline_offset));
- blob_box = bounding_box ();
- blob_x_left = blob_box.left ();
- scale (1.0 / denorm->scale_at_x (blob_x_left));
- move (FCOORD (denorm->origin (),
- denorm->yshift_at_x (blob_x_left)));
+void PBLOB::baseline_denormalise(const DENORM *denorm ) {
+ move(FCOORD(0.0f, 0.0f - kBlnBaselineOffset));
+ TBOX blob_box = bounding_box();
+ float blob_x_centre = (blob_box.left() + blob_box.right()) / 2.0f;
+ scale(1.0 / denorm->scale_at_x(blob_x_centre));
+ move(FCOORD(denorm->origin(), denorm->yshift_at_x(blob_x_centre)));
}
diff --git a/ccstruct/polyblob.h b/ccstruct/polyblob.h
index 790657efd9..334ab234ba 100644
--- a/ccstruct/polyblob.h
+++ b/ccstruct/polyblob.h
@@ -30,13 +30,13 @@ const int kBlnBaselineOffset = 64; // offset for baseline normalization
class PBLOB : public ELIST_LINK {
public:
- PBLOB() {
- } //empty constructor
- PBLOB( //constructor
- OUTLINE_LIST *outline_list); //in random order
- PBLOB( //constructor
- C_BLOB *cblob, //polygonal approx
- float xheight);
+ PBLOB() {}
+
+ // Create from a list of polygonal outlines.
+ PBLOB(OUTLINE_LIST *outline_list);
+
+ // Create from a chain-coded form.
+ PBLOB(C_BLOB *cblob);
OUTLINE_LIST *out_list() { //get outline list
return &outlines;
diff --git a/ccstruct/publictypes.cpp b/ccstruct/publictypes.cpp
new file mode 100644
index 0000000000..e6795960b2
--- /dev/null
+++ b/ccstruct/publictypes.cpp
@@ -0,0 +1,38 @@
+///////////////////////////////////////////////////////////////////////
+// File: publictypes.cpp
+// Description: Types used in both the API and internally
+// Author: Ray Smith
+// Created: Wed Mar 03 11:17: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 "publictypes.h"
+
+// String name for each block type. Keep in sync with PolyBlockType.
+const char* kPolyBlockNames[] = {
+ "Unknown",
+ "Flowing Text",
+ "Heading Text",
+ "Pullout Text",
+ "Table",
+ "Vertical Text",
+ "Caption Text",
+ "Flowing Image",
+ "Heading Image",
+ "Pullout Image",
+ "Horizontal Line",
+ "Vertical Line",
+ "Noise",
+ "" // End marker for testing that sizes match.
+};
diff --git a/ccstruct/publictypes.h b/ccstruct/publictypes.h
new file mode 100644
index 0000000000..4f5373a2a8
--- /dev/null
+++ b/ccstruct/publictypes.h
@@ -0,0 +1,148 @@
+///////////////////////////////////////////////////////////////////////
+// File: publictypes.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_CCSTRUCT_PUBLICTYPES_H__
+#define TESSERACT_CCSTRUCT_PUBLICTYPES_H__
+
+// This file contains types that are used both by the API and internally
+// to Tesseract. In order to decouple the API from Tesseract and prevent cyclic
+// dependencies, THIS FILE SHOULD NOT DEPEND ON ANY OTHER PART OF TESSERACT.
+// Restated: It is OK for low-level Tesseract files to include publictypes.h,
+// but not for the low-level tesseract code to include top-level API code.
+// This file should not use other Tesseract types, as that would drag
+// their includes into the API-level.
+// API-level code should include apitypes.h in preference to this file.
+
+// Number of printers' points in an inch. The unit of the pointsize return.
+const int kPointsPerInch = 72;
+
+// Possible types for a POLY_BLOCK or ColPartition.
+// Must be kept in sync with kPBColors in polyblk.cpp and PTIs*Type functions
+// below, as well as kPolyBlockNames in publictypes.cpp.
+// Used extensively by ColPartition, and POLY_BLOCK.
+enum PolyBlockType {
+ PT_UNKNOWN, // Type is not yet known. Keep as the first element.
+ PT_FLOWING_TEXT, // Text that lives inside a column.
+ PT_HEADING_TEXT, // Text that spans more than one column.
+ PT_PULLOUT_TEXT, // Text that is in a cross-column pull-out region.
+ PT_TABLE, // Partition belonging to a table region.
+ PT_VERTICAL_TEXT, // Text-line runs vertically.
+ PT_CAPTION_TEXT, // Text that belongs to an image.
+ PT_FLOWING_IMAGE, // Image that lives inside a column.
+ PT_HEADING_IMAGE, // Image that spans more than one column.
+ PT_PULLOUT_IMAGE, // Image that is in a cross-column pull-out region.
+ PT_HORZ_LINE, // Horizontal Line.
+ PT_VERT_LINE, // Vertical Line.
+ PT_NOISE, // Lies outside of any column.
+ PT_COUNT
+};
+
+
+// Returns true if PolyBlockType is of horizontal line type
+inline bool PTIsLineType(PolyBlockType type) {
+ return type == PT_HORZ_LINE || type == PT_VERT_LINE;
+}
+// Returns true if PolyBlockType is of image type
+inline bool PTIsImageType(PolyBlockType type) {
+ return type == PT_FLOWING_IMAGE || type == PT_HEADING_IMAGE ||
+ type == PT_PULLOUT_IMAGE;
+}
+// Returns true if PolyBlockType is of text type
+inline bool PTIsTextType(PolyBlockType type) {
+ return type == PT_FLOWING_TEXT || type == PT_HEADING_TEXT ||
+ type == PT_PULLOUT_TEXT || type == PT_TABLE ||
+ type == PT_VERTICAL_TEXT || type == PT_CAPTION_TEXT;
+}
+
+// String name for each block type. Keep in sync with PolyBlockType.
+extern const char* kPolyBlockNames[];
+
+namespace tesseract {
+
+// Possible modes for page layout analysis. These *must* be kept in order
+// of decreasing amount of layout analysis to be done, except for OSD_ONLY,
+// so that the inequality test macros below work.
+enum PageSegMode {
+ PSM_OSD_ONLY, ///< Orientation and script detection only.
+ PSM_AUTO_OSD, ///< Automatic page segmentation with orientation and
+ ///< script detection. (OSD)
+ PSM_AUTO, ///< Fully automatic page segmentation, but no OSD.
+ PSM_SINGLE_COLUMN, ///< Assume a single column of text of variable sizes.
+ PSM_SINGLE_BLOCK_VERT_TEXT, ///< Assume a single uniform block of vertically
+ ///< aligned text.
+ 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_CIRCLE_WORD, ///< Treat the image as a single word in a circle.
+ PSM_SINGLE_CHAR, ///< Treat the image as a single character.
+
+ PSM_COUNT ///< Number of enum entries.
+};
+
+// Macros that act on a PageSegMode to determine whether components of
+// layout analysis are enabled.
+// *Depend critically on the order of elements of PageSegMode.*
+#define PSM_OSD_ENABLED(pageseg_mode) ((pageseg_mode) <= PSM_AUTO_OSD)
+#define PSM_COL_FIND_ENABLED(pageseg_mode) \
+ ((pageseg_mode) >= PSM_AUTO_OSD && (pageseg_mode) <= PSM_AUTO)
+#define PSM_BLOCK_FIND_ENABLED(pageseg_mode) \
+ ((pageseg_mode) >= PSM_AUTO_OSD && (pageseg_mode) <= PSM_SINGLE_COLUMN)
+#define PSM_LINE_FIND_ENABLED(pageseg_mode) \
+ ((pageseg_mode) >= PSM_AUTO_OSD && (pageseg_mode) <= PSM_SINGLE_BLOCK)
+#define PSM_WORD_FIND_ENABLED(pageseg_mode) \
+ ((pageseg_mode) >= PSM_AUTO_OSD && (pageseg_mode) <= PSM_SINGLE_LINE)
+
+// enum of the elements of the page hierarchy, used in ResultIterator
+// to provide functions that operate on each level without having to
+// have 5x as many functions.
+// NOTE: At present RIL_PARA and RIL_BLOCK are equivalent as there is
+// no paragraph internally yet.
+// TODO(rays) Add paragraph detection.
+enum PageIteratorLevel {
+ RIL_BLOCK, // Block of text/image/separator line.
+ RIL_PARA, // Paragraph within a block.
+ RIL_TEXTLINE, // Line within a paragraph.
+ RIL_WORD, // Word within a textline.
+ RIL_SYMBOL // Symbol/character within a word.
+};
+
+// When Tesseract/Cube is initialized we can choose to instantiate/load/run
+// only the Tesseract part, only the Cube part or both along with the combiner.
+// The preference of which engine to use is stored in tessedit_ocr_engine_mode.
+//
+// ATTENTION: When modifying this enum, please make sure to make the
+// appropriate changes to all the enums mirroring it (e.g. OCREngine in
+// cityblock/workflow/detection/detection_storage.proto). Such enums will
+// mention the connection to OcrEngineMode in the comments.
+enum OcrEngineMode {
+ OEM_TESSERACT_ONLY, // Run Tesseract only - fastest
+ OEM_CUBE_ONLY, // Run Cube only - better accuracy, but slower
+ OEM_TESSERACT_CUBE_COMBINED, // Run both and combine results - best accuracy
+ OEM_DEFAULT // Specify this mode when calling init_*(),
+ // to indicate that any of the above modes
+ // should be automatically inferred from the
+ // variables in the language-specific config,
+ // command-line configs, or if not specified
+ // in any of the above should be set to the
+ // default OEM_TESSERACT_ONLY.
+};
+
+} // namespace tesseract.
+
+#endif // TESSERACT_CCSTRUCT_PUBLICTYPES_H__
diff --git a/ccstruct/quspline.cpp b/ccstruct/quspline.cpp
index 5fd4e79d05..22609b41c6 100644
--- a/ccstruct/quspline.cpp
+++ b/ccstruct/quspline.cpp
@@ -1,8 +1,8 @@
/**********************************************************************
* File: quspline.cpp (Formerly qspline.c)
* Description: Code for the QSPLINE class.
- * Author: Ray Smith
- * Created: Tue Oct 08 17:16:12 BST 1991
+ * Author: Ray Smith
+ * Created: Tue Oct 08 17:16:12 BST 1991
*
* (C) Copyright 1991, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
@@ -377,9 +377,9 @@ void QSPLINE::plot( //draw it
x = xcoords[segment];
for (step = 0; step <= QSPLINE_PRECISION; step++) {
if (segment == 0 && step == 0)
- window->SetCursor(x, quadratics[segment].y (x));
+ window->SetCursor(x, quadratics[segment].y (x));
else
- window->DrawTo(x, quadratics[segment].y (x));
+ window->DrawTo(x, quadratics[segment].y (x));
x += increment;
}
}
diff --git a/ccstruct/quspline.h b/ccstruct/quspline.h
index 7a16046259..edb81afc47 100644
--- a/ccstruct/quspline.h
+++ b/ccstruct/quspline.h
@@ -37,7 +37,7 @@ class QSPLINE
QSPLINE *,
float);
friend void make_holed_baseline(TBOX *, int, QSPLINE *, QSPLINE *, float);
- friend void tweak_row_baseline(ROW *);
+ friend void tweak_row_baseline(ROW *, double, double);
public:
QSPLINE() { //empty constructor
segments = 0;
diff --git a/ccstruct/ratngs.cpp b/ccstruct/ratngs.cpp
index 30e4db2a8a..c64cc28f44 100644
--- a/ccstruct/ratngs.cpp
+++ b/ccstruct/ratngs.cpp
@@ -18,16 +18,15 @@
**********************************************************************/
#include "mfcpch.h"
-
#include "ratngs.h"
+
#include "callcpp.h"
#include "genericvector.h"
#include "unicharset.h"
-extern FILE *matcher_fp;
-
ELISTIZE (BLOB_CHOICE) CLISTIZE (BLOB_CHOICE_LIST) CLISTIZE (WERD_CHOICE)
-//extern FILE* matcher_fp;
+
+const float WERD_CHOICE::kBadRating = 100000.0;
/**
* BLOB_CHOICE::BLOB_CHOICE
@@ -37,14 +36,17 @@ ELISTIZE (BLOB_CHOICE) CLISTIZE (BLOB_CHOICE_LIST) CLISTIZE (WERD_CHOICE)
BLOB_CHOICE::BLOB_CHOICE(UNICHAR_ID src_unichar_id, //< character id
float src_rating, //< rating
float src_cert, //< certainty
- inT8 src_config, //< config (font)
+ inT16 src_config, //< config (font)
+ inT16 src_config2, //< 2nd choice config.
int src_script_id //< script
) {
unichar_id_ = src_unichar_id;
rating_ = src_rating;
certainty_ = src_cert;
config_ = src_config;
+ config2_ = src_config2;
script_id_ = src_script_id;
+ language_model_state_ = NULL;
}
/**
@@ -57,7 +59,9 @@ BLOB_CHOICE::BLOB_CHOICE(const BLOB_CHOICE &other) {
rating_ = other.rating();
certainty_ = other.certainty();
config_ = other.config();
+ config2_ = other.config2();
script_id_ = other.script_id();
+ language_model_state_ = NULL;
}
/**
@@ -69,12 +73,12 @@ BLOB_CHOICE::BLOB_CHOICE(const BLOB_CHOICE &other) {
WERD_CHOICE::WERD_CHOICE(const char *src_string,
const UNICHARSET &unicharset) {
STRING src_lengths;
- int len = strlen(src_string);
const char *ptr = src_string;
+ const char *end = src_string + strlen(src_string);
int step = unicharset.step(ptr);
- for (; ptr < src_string + len && step > 0;
+ for (; ptr < end && step > 0;
step = unicharset.step(ptr), src_lengths += step, ptr += step);
- if (step != 0 && ptr == src_string + len) {
+ if (step != 0 && ptr == end) {
this->init(src_string, src_lengths.string(),
0.0, 0.0, NO_PERM, unicharset);
} else { // there must have been an invalid unichar in the string
@@ -395,13 +399,8 @@ void print_ratings_list(const char *msg,
BLOB_CHOICE_IT c_it;
c_it.set_to_list(ratings);
for (c_it.mark_cycle_pt(); !c_it.cycled_list(); c_it.forward()) {
- tprintf("r%.2f c%.2f : %d %s",
- c_it.data()->rating(), c_it.data()->certainty(),
- c_it.data()->unichar_id(),
- current_unicharset.debug_str(c_it.data()->unichar_id()).string());
- if (!c_it.at_last()) {
- tprintf("\n");
- }
+ c_it.data()->print(¤t_unicharset);
+ if (!c_it.at_last()) tprintf("\n");
}
tprintf("\n");
fflush(stdout);
@@ -423,11 +422,8 @@ void print_ratings_list(const char *msg, BLOB_CHOICE_LIST *ratings) {
BLOB_CHOICE_IT c_it;
c_it.set_to_list(ratings);
for (c_it.mark_cycle_pt(); !c_it.cycled_list(); c_it.forward()) {
- tprintf("r%.2f c%.2f : %d", c_it.data()->rating(),
- c_it.data()->certainty(), c_it.data()->unichar_id());
- if (!c_it.at_last()) {
- tprintf("\n");
- }
+ c_it.data()->print(NULL);
+ if (!c_it.at_last()) tprintf("\n");
}
tprintf("\n");
fflush(stdout);
@@ -447,9 +443,6 @@ void print_ratings_info(FILE *fp,
BLOB_CHOICE_LIST *ratings,
const UNICHARSET ¤t_unicharset) {
inT32 index; // to list
- inT32 best_index; // to list
- FLOAT32 best_rat; // rating
- FLOAT32 best_cert; // certainty
const char* first_char = NULL; // character
FLOAT32 first_rat; // rating
FLOAT32 first_cert; // certainty
@@ -478,25 +471,12 @@ void print_ratings_info(FILE *fp,
first_rat = -1;
first_cert = -1;
}
- best_index = -1;
- best_rat = -1;
- best_cert = -1;
- for (index = 0, c_it.mark_cycle_pt(); !c_it.cycled_list();
- c_it.forward(), index++) {
- if (strcmp(current_unicharset.id_to_unichar(c_it.data()->unichar_id()),
- blob_answer) == 0) {
- best_index = index;
- best_rat = c_it.data()->rating();
- best_cert = -c_it.data()->certainty();
- }
- }
if (first_char != NULL && (*first_char == '\0' || *first_char == ' '))
first_char = NULL;
if (sec_char != NULL && (*sec_char == '\0' || *sec_char == ' '))
sec_char = NULL;
- fprintf(matcher_fp,
- " " INT32FORMAT " " INT32FORMAT " %g %g %s %g %g %s %g %g\n",
- ratings->length(), best_index, best_rat, best_cert,
+ tprintf(" " INT32FORMAT " %s %g %g %s %g %g\n",
+ ratings->length(),
first_char != NULL ? first_char : "~",
first_rat, first_cert, sec_char != NULL ? sec_char : "~",
sec_rat, sec_cert);
@@ -513,9 +493,9 @@ void print_char_choices_list(const char *msg,
for (int x = 0; x < char_choices.length(); ++x) {
BLOB_CHOICE_IT c_it;
c_it.set_to_list(char_choices.get(x));
- tprintf("char[%d]: %s\n", x,
+ tprintf("\nchar[%d]: %s\n", x,
current_unicharset.debug_str( c_it.data()->unichar_id()).string());
if (detailed)
- print_ratings_list(" ", char_choices.get(x), current_unicharset);
+ print_ratings_list("", char_choices.get(x), current_unicharset);
}
}
diff --git a/ccstruct/ratngs.h b/ccstruct/ratngs.h
index a72de82aef..9855f3f4ca 100644
--- a/ccstruct/ratngs.h
+++ b/ccstruct/ratngs.h
@@ -38,14 +38,17 @@ class BLOB_CHOICE: public ELIST_LINK
rating_ = MAX_FLOAT32;
certainty_ = -MAX_FLOAT32;
script_id_ = -1;
+ language_model_state_ = NULL;
}
BLOB_CHOICE(UNICHAR_ID src_unichar_id, // character id
float src_rating, // rating
float src_cert, // certainty
- inT8 src_config, // config (font)
+ inT16 src_config, // config (font)
+ inT16 src_config2, // 2nd choice config.
int script_id); // script
BLOB_CHOICE(const BLOB_CHOICE &other);
~BLOB_CHOICE() {}
+
UNICHAR_ID unichar_id() const {
return unichar_id_;
}
@@ -55,12 +58,18 @@ class BLOB_CHOICE: public ELIST_LINK
float certainty() const {
return certainty_;
}
- inT8 config() const {
+ inT16 config() const {
return config_;
}
+ inT16 config2() const {
+ return config2_;
+ }
int script_id() const {
return script_id_;
}
+ void *language_model_state() {
+ return language_model_state_;
+ }
void set_unichar_id(UNICHAR_ID newunichar_id) {
unichar_id_ = newunichar_id;
@@ -71,27 +80,44 @@ class BLOB_CHOICE: public ELIST_LINK
void set_certainty(float newrat) {
certainty_ = newrat;
}
- void set_config(inT8 newfont) {
+ void set_config(inT16 newfont) {
config_ = newfont;
}
+ void set_config2(inT16 newfont) {
+ config2_ = newfont;
+ }
void set_script(int newscript_id) {
script_id_ = newscript_id;
}
+ void set_language_model_state(void *language_model_state) {
+ language_model_state_ = language_model_state;
+ }
static BLOB_CHOICE* deep_copy(const BLOB_CHOICE* src) {
BLOB_CHOICE* choice = new BLOB_CHOICE;
*choice = *src;
return choice;
}
+ void print(const UNICHARSET *unicharset) {
+ tprintf("r%.2f c%.2f : %d %s", rating_, certainty_, unichar_id_,
+ (unicharset == NULL) ? "" :
+ unicharset->debug_str(unichar_id_).string());
+ }
NEWDELETE
private:
UNICHAR_ID unichar_id_; // unichar id
- char config_; // char config (font)
+ inT16 config_; // char config (font)
+ inT16 config2_; // 2nd choice config (font)
inT16 junk2_;
float rating_; // size related
float certainty_; // absolute
int script_id_;
+ // Stores language model information about this BLOB_CHOICE. Used during
+ // the segmentation search for BLOB_CHOICEs in BLOB_CHOICE_LISTs that are
+ // recorded in the ratings matrix.
+ // The pointer is owned/managed by the segmentation search.
+ void *language_model_state_;
};
// Make BLOB_CHOICE listable.
@@ -99,21 +125,25 @@ ELISTIZEH (BLOB_CHOICE) CLISTIZEH (BLOB_CHOICE_LIST)
// Permuter codes used in WERD_CHOICEs.
enum PermuterType {
- NO_PERM, // 0
- PUNC_PERM, // 1
- TOP_CHOICE_PERM, // 2
- LOWER_CASE_PERM, // 3
- UPPER_CASE_PERM, // 4
- NUMBER_PERM, // 5
- SYSTEM_DAWG_PERM, // 6
- DOC_DAWG_PERM, // 7
- USER_DAWG_PERM, // 8
- FREQ_DAWG_PERM, // 9
- COMPOUND_PERM, // 10
+ NO_PERM, // 0
+ PUNC_PERM, // 1
+ TOP_CHOICE_PERM, // 2
+ LOWER_CASE_PERM, // 3
+ UPPER_CASE_PERM, // 4
+ NGRAM_PERM, // 5
+ NUMBER_PERM, // 6
+ USER_PATTERN_PERM, // 7
+ SYSTEM_DAWG_PERM, // 8
+ DOC_DAWG_PERM, // 9
+ USER_DAWG_PERM, // 10
+ FREQ_DAWG_PERM, // 11
+ COMPOUND_PERM, // 12
};
class WERD_CHOICE {
public:
+ static const float kBadRating;
+
WERD_CHOICE() { this->init(8); }
WERD_CHOICE(int reserved) { this->init(reserved); }
WERD_CHOICE(const char *src_string,
@@ -168,6 +198,10 @@ class WERD_CHOICE {
assert(index < length_);
unichar_ids_[index] = unichar_id;
}
+ inline void set_fragment_length(char flen, int index) {
+ assert(index < length_);
+ fragment_lengths_[index] = flen;
+ }
inline void set_rating(float new_val) {
rating_ = new_val;
}
@@ -180,6 +214,13 @@ class WERD_CHOICE {
inline void set_fragment_mark(bool new_fragment_mark) {
fragment_mark_ = new_fragment_mark;
}
+ // Note: this function should only be used if all the fields
+ // are populated manually with set_* functions (rather than
+ // (copy)constructors and append_* functions).
+ inline void set_length(int len) {
+ ASSERT_HOST(reserved_ >= len);
+ length_ = len;
+ }
void set_blob_choices(BLOB_CHOICE_LIST_CLIST *blob_choices);
/// Make more space in unichar_id_ and fragment_lengths_ arrays.
@@ -219,7 +260,7 @@ class WERD_CHOICE {
/// Set the fields in this choice to be default (bad) values.
inline void make_bad() {
length_ = 0;
- rating_ = MAX_FLOAT32;
+ rating_ = kBadRating;
certainty_ = -MAX_FLOAT32;
fragment_mark_ = false;
unichar_string_ = "";
@@ -323,9 +364,6 @@ CLISTIZEH (WERD_CHOICE)
typedef GenericVector BLOB_CHOICE_LIST_VECTOR;
typedef GenericVector WERD_CHOICE_LIST_VECTOR;
-typedef void (*POLY_TESTER) (const STRING&, PBLOB *, DENORM *, BOOL8,
- char *, inT32, BLOB_CHOICE_LIST *);
-
void print_ratings_list(const char *msg, BLOB_CHOICE_LIST *ratings);
void print_ratings_list(
const char *msg, // intro message
diff --git a/ccstruct/rect.h b/ccstruct/rect.h
index a2fc40a11d..d2da28e7df 100644
--- a/ccstruct/rect.h
+++ b/ccstruct/rect.h
@@ -46,6 +46,10 @@ class DLLSYM TBOX { // bounding box
return ((left () >= right ()) || (top () <= bottom ()));
}
+ bool operator==(const TBOX& other) {
+ return bot_left == other.bot_left && top_right == other.top_right;
+ }
+
inT16 top() const { // coord of top
return top_right.y ();
}
@@ -192,6 +196,22 @@ class DLLSYM TBOX { // bounding box
// Do boxes overlap on x axis.
bool x_overlap(const TBOX &box) const;
+ // Return the horizontal gap between the boxes. If the boxes
+ // overlap horizontally then the return value is negative, indicating
+ // the amount of the overlap.
+ int x_gap(const TBOX& box) const {
+ return MAX(bot_left.x(), box.bot_left.x()) -
+ MIN(top_right.x(), box.top_right.x());
+ }
+
+ // Return the vertical gap between the boxes. If the boxes
+ // overlap vertically then the return value is negative, indicating
+ // the amount of the overlap.
+ int y_gap(const TBOX& box) const {
+ return MAX(bot_left.y(), box.bot_left.y()) -
+ MIN(top_right.y(), box.top_right.y());
+ }
+
// Do boxes overlap on x axis by more than
// half of the width of the narrower box.
bool major_x_overlap(const TBOX &box) const;
@@ -206,12 +226,26 @@ class DLLSYM TBOX { // bounding box
// fraction of current box's area covered by other
double overlap_fraction(const TBOX &box) const;
+ // fraction of the current box's projected area covered by the other's
+ double x_overlap_fraction(const TBOX& box) const;
+
+ // fraction of the current box's projected area covered by the other's
+ double y_overlap_fraction(const TBOX& box) const;
+
TBOX intersection( // shared area box
const TBOX &box) const;
TBOX bounding_union( // box enclosing both
const TBOX &box) const;
+ // Sets the box boundaries to the given coordinates.
+ void set_to_given_coords(int x_min, int y_min, int x_max, int y_max) {
+ bot_left.set_x(x_min);
+ bot_left.set_y(y_min);
+ top_right.set_x(x_max);
+ top_right.set_y(y_max);
+ }
+
void print() const { // print
tprintf("Bounding box=(%d,%d)->(%d,%d)\n",
left(), bottom(), right(), top());
@@ -233,7 +267,7 @@ class DLLSYM TBOX { // bounding box
friend DLLSYM TBOX & operator+= (TBOX &, const TBOX &);
// in place union
friend DLLSYM TBOX & operator-= (TBOX &, const TBOX &);
- // in place intrsection
+ // in place intersection
void serialise_asc( // convert to ascii
FILE *f);
@@ -379,4 +413,49 @@ inline bool TBOX::major_y_overlap(const TBOX &box) const {
}
return (overlap >= box.height() / 2 || overlap >= this->height() / 2);
}
+
+/**********************************************************************
+ * TBOX::x_overlap_fraction() Calculates the horizontal overlap of the
+ * given boxes as a fraction of this boxes
+ * width.
+ *
+ **********************************************************************/
+
+inline double TBOX::x_overlap_fraction(const TBOX& other) const {
+ int low = MAX(left(), other.left());
+ int high = MIN(right(), other.right());
+ int width = right() - left();
+ if (width == 0) {
+ int x = left();
+ if (other.left() <= x && x <= other.right())
+ return 1.0;
+ else
+ return 0.0;
+ } else {
+ return MAX(0, static_cast(high - low) / width);
+ }
+}
+
+/**********************************************************************
+ * TBOX::y_overlap_fraction() Calculates the vertical overlap of the
+ * given boxes as a fraction of this boxes
+ * height.
+ *
+ **********************************************************************/
+
+inline double TBOX::y_overlap_fraction(const TBOX& other) const {
+ int low = MAX(bottom(), other.bottom());
+ int high = MIN(top(), other.top());
+ int height = top() - bottom();
+ if (height == 0) {
+ int y = bottom();
+ if (other.bottom() <= y && y <= other.top())
+ return 1.0;
+ else
+ return 0.0;
+ } else {
+ return MAX(0, static_cast(high - low) / height);
+ }
+}
+
#endif
diff --git a/ccstruct/rejctmap.cpp b/ccstruct/rejctmap.cpp
index 8402ffc35d..236d9d7a85 100644
--- a/ccstruct/rejctmap.cpp
+++ b/ccstruct/rejctmap.cpp
@@ -22,13 +22,7 @@
//#include "basefile.h"
#include "rejctmap.h"
#include "secname.h"
-
-#define EXTERN
-
-EXTERN BOOL_VAR (rejword_only_set_if_accepted, TRUE, "Mimic old reject_word");
-EXTERN BOOL_VAR (rejmap_allow_more_good_qual, FALSE,
-"Use initial good qual setting");
-EXTERN BOOL_VAR (rej_use_1Il_rej, TRUE, "1Il rejection enabled");
+#include "params.h"
BOOL8 REJ::perm_rejected() { //Is char perm reject?
return (flag (R_TESS_FAILURE) ||
@@ -96,10 +90,9 @@ BOOL8 REJ::accept_if_good_quality() { //potential rej?
!flag (R_POOR_MATCH) &&
!flag (R_NOT_TESS_ACCEPTED) &&
!flag (R_CONTAINS_BLANKS) &&
- (rejmap_allow_more_good_qual ||
(!rej_between_nn_and_mm () &&
- !rej_between_mm_and_quality_accept () &&
- !rej_between_quality_and_minimal_rej_accept ())));
+ !rej_between_mm_and_quality_accept () &&
+ !rej_between_quality_and_minimal_rej_accept ()));
}
@@ -119,8 +112,7 @@ void REJ::setrej_edge_char() { //Close to image edge
void REJ::setrej_1Il_conflict() { //Initial reject map
- if (rej_use_1Il_rej)
- set_flag(R_1IL_CONFLICT);
+ set_flag(R_1IL_CONFLICT);
}
@@ -449,8 +441,7 @@ void REJMAP::rej_word_not_tess_accepted() { //Reject whole word
int i;
for (i = 0; i < len; i++) {
- if (!rejword_only_set_if_accepted || ptr[i].accepted ())
- ptr[i].setrej_not_tess_accepted ();
+ if (ptr[i].accepted()) ptr[i].setrej_not_tess_accepted();
}
}
@@ -459,8 +450,7 @@ void REJMAP::rej_word_contains_blanks() { //Reject whole word
int i;
for (i = 0; i < len; i++) {
- if (!rejword_only_set_if_accepted || ptr[i].accepted ())
- ptr[i].setrej_contains_blanks ();
+ if (ptr[i].accepted()) ptr[i].setrej_contains_blanks();
}
}
@@ -469,8 +459,7 @@ void REJMAP::rej_word_bad_permuter() { //Reject whole word
int i;
for (i = 0; i < len; i++) {
- if (!rejword_only_set_if_accepted || ptr[i].accepted ())
- ptr[i].setrej_bad_permuter ();
+ if (ptr[i].accepted()) ptr[i].setrej_bad_permuter ();
}
}
@@ -479,8 +468,7 @@ void REJMAP::rej_word_xht_fixup() { //Reject whole word
int i;
for (i = 0; i < len; i++) {
- if (!rejword_only_set_if_accepted || ptr[i].accepted ())
- ptr[i].setrej_xht_fixup ();
+ if (ptr[i].accepted()) ptr[i].setrej_xht_fixup();
}
}
@@ -489,8 +477,7 @@ void REJMAP::rej_word_no_alphanums() { //Reject whole word
int i;
for (i = 0; i < len; i++) {
- if (!rejword_only_set_if_accepted || ptr[i].accepted ())
- ptr[i].setrej_no_alphanums ();
+ if (ptr[i].accepted()) ptr[i].setrej_no_alphanums();
}
}
@@ -499,8 +486,7 @@ void REJMAP::rej_word_mostly_rej() { //Reject whole word
int i;
for (i = 0; i < len; i++) {
- if (!rejword_only_set_if_accepted || ptr[i].accepted ())
- ptr[i].setrej_mostly_rej ();
+ if (ptr[i].accepted()) ptr[i].setrej_mostly_rej();
}
}
@@ -509,8 +495,7 @@ void REJMAP::rej_word_bad_quality() { //Reject whole word
int i;
for (i = 0; i < len; i++) {
- if (!rejword_only_set_if_accepted || ptr[i].accepted ())
- ptr[i].setrej_bad_quality ();
+ if (ptr[i].accepted()) ptr[i].setrej_bad_quality();
}
}
@@ -519,8 +504,7 @@ void REJMAP::rej_word_doc_rej() { //Reject whole word
int i;
for (i = 0; i < len; i++) {
- if (!rejword_only_set_if_accepted || ptr[i].accepted ())
- ptr[i].setrej_doc_rej ();
+ if (ptr[i].accepted()) ptr[i].setrej_doc_rej();
}
}
@@ -529,8 +513,7 @@ void REJMAP::rej_word_block_rej() { //Reject whole word
int i;
for (i = 0; i < len; i++) {
- if (!rejword_only_set_if_accepted || ptr[i].accepted ())
- ptr[i].setrej_block_rej ();
+ if (ptr[i].accepted()) ptr[i].setrej_block_rej();
}
}
@@ -539,7 +522,6 @@ void REJMAP::rej_word_row_rej() { //Reject whole word
int i;
for (i = 0; i < len; i++) {
- if (!rejword_only_set_if_accepted || ptr[i].accepted ())
- ptr[i].setrej_row_rej ();
+ if (ptr[i].accepted()) ptr[i].setrej_row_rej();
}
}
diff --git a/ccstruct/rejctmap.h b/ccstruct/rejctmap.h
index eff8eacfd8..3954cf4c2b 100644
--- a/ccstruct/rejctmap.h
+++ b/ccstruct/rejctmap.h
@@ -46,15 +46,9 @@ OF THIS IMPLIED TEMPORAL ORDERING OF THE FLAGS!!!!
#endif
#include "memry.h"
#include "bits16.h"
-#include "varable.h"
+#include "params.h"
#include "notdll.h"
-extern BOOL_VAR_H (rejword_only_set_if_accepted, TRUE,
-"Mimic old reject_word");
-extern BOOL_VAR_H (rejmap_allow_more_good_qual, FALSE,
-"Use initial good qual setting");
-extern BOOL_VAR_H (rej_use_1Il_rej, TRUE, "1Il rejection enabled");
-
enum REJ_FLAGS
{
/* Reject modes which are NEVER overridden */
diff --git a/wordrec/seam.cpp b/ccstruct/seam.cpp
similarity index 50%
rename from wordrec/seam.cpp
rename to ccstruct/seam.cpp
index 049c7c6bf5..ddfea81336 100644
--- a/wordrec/seam.cpp
+++ b/ccstruct/seam.cpp
@@ -26,9 +26,9 @@
I n c l u d e s
----------------------------------------------------------------------*/
#include "seam.h"
+#include "blobs.h"
#include "callcpp.h"
#include "structures.h"
-#include "makechop.h"
#ifdef __UNIX__
#include
@@ -38,10 +38,7 @@
V a r i a b l e s
----------------------------------------------------------------------*/
#define NUM_STARTING_SEAMS 20
-
-#define SEAMBLOCK 100 /* Cells per block */
-makestructure (newseam, free_seam, printseam, SEAM,
-freeseam, SEAMBLOCK, "SEAM", seamcount);
+makestructure(newseam, free_seam, SEAM);
/*----------------------------------------------------------------------
Public Function Code
@@ -50,15 +47,15 @@ freeseam, SEAMBLOCK, "SEAM", seamcount);
* @name point_in_split
*
* Check to see if either of these points are present in the current
- * split.
+ * split.
* @returns TRUE if one of them is split.
*/
bool point_in_split(SPLIT *split, EDGEPT *point1, EDGEPT *point2) {
- return ((split) ?
- ((exact_point (split->point1, point1) ||
- exact_point (split->point1, point2) ||
- exact_point (split->point2, point1) ||
- exact_point (split->point2, point2)) ? TRUE : FALSE) : FALSE);
+ return ((split) ? ((exact_point (split->point1, point1) ||
+ exact_point (split->point1, point2) ||
+ exact_point (split->point2, point1) ||
+ exact_point (split->point2, point2)) ? TRUE : FALSE)
+ : FALSE);
}
@@ -66,13 +63,13 @@ bool point_in_split(SPLIT *split, EDGEPT *point1, EDGEPT *point2) {
* @name point_in_seam
*
* Check to see if either of these points are present in the current
- * seam.
+ * seam.
* @returns TRUE if one of them is.
*/
bool point_in_seam(SEAM *seam, SPLIT *split) {
- return (point_in_split (seam->split1, split->point1, split->point2) ||
- point_in_split (seam->split2, split->point1, split->point2) ||
- point_in_split (seam->split3, split->point1, split->point2));
+ return (point_in_split(seam->split1, split->point1, split->point2) ||
+ point_in_split(seam->split2, split->point1, split->point2) ||
+ point_in_split(seam->split3, split->point1, split->point2));
}
@@ -106,7 +103,7 @@ void combine_seams(SEAM *dest_seam, SEAM *source_seam) {
else if (!dest_seam->split3)
dest_seam->split3 = source_seam->split1;
else
- cprintf ("combine_seam: Seam is too crowded, can't be combined !\n");
+ cprintf("combine_seam: Seam is too crowded, can't be combined !\n");
}
if (source_seam->split2) {
if (!dest_seam->split2)
@@ -114,13 +111,13 @@ void combine_seams(SEAM *dest_seam, SEAM *source_seam) {
else if (!dest_seam->split3)
dest_seam->split3 = source_seam->split2;
else
- cprintf ("combine_seam: Seam is too crowded, can't be combined !\n");
+ cprintf("combine_seam: Seam is too crowded, can't be combined !\n");
}
if (source_seam->split3) {
if (!dest_seam->split3)
dest_seam->split3 = source_seam->split3;
else
- cprintf ("combine_seam: Seam is too crowded, can't be combined !\n");
+ cprintf("combine_seam: Seam is too crowded, can't be combined !\n");
}
free_seam(source_seam);
}
@@ -136,15 +133,48 @@ void delete_seam(void *arg) { //SEAM *seam)
if (seam) {
if (seam->split1)
- delete_split (seam->split1);
+ delete_split(seam->split1);
if (seam->split2)
- delete_split (seam->split2);
+ delete_split(seam->split2);
if (seam->split3)
- delete_split (seam->split3);
+ delete_split(seam->split3);
free_seam(seam);
}
}
+/**
+ * @name start_seam_list
+ *
+ * Initialize a list of seams that match the original number of blobs
+ * present in the starting segmentation. Each of the seams created
+ * by this routine have location information only.
+ */
+SEAMS start_seam_list(TBLOB *blobs) {
+ TBLOB *blob;
+ SEAMS seam_list;
+ TPOINT topleft;
+ TPOINT botright;
+ TPOINT location;
+ /* Seam slot per char */
+ seam_list = new_seam_list ();
+
+ for (blob = blobs; blob->next != NULL; blob = blob->next) {
+
+ blob_bounding_box(blob, &topleft, &botright);
+ location.x = botright.x;
+ location.y = botright.y + topleft.y;
+ blob_bounding_box (blob->next, &topleft, &botright);
+ location.x += topleft.x;
+ location.y += botright.y + topleft.y;
+ location.x /= 2;
+ location.y /= 4;
+
+ seam_list = add_seam (seam_list,
+ new_seam (0.0, location, NULL, NULL, NULL));
+ }
+
+ return (seam_list);
+}
/**
* @name free_seam_list
@@ -155,7 +185,7 @@ void delete_seam(void *arg) { //SEAM *seam)
void free_seam_list(SEAMS seam_list) {
int x;
- array_loop (seam_list, x) delete_seam (array_value (seam_list, x));
+ array_loop(seam_list, x) delete_seam(array_value (seam_list, x));
array_free(seam_list);
}
@@ -175,17 +205,19 @@ bool test_insert_seam(SEAMS seam_list,
int list_length;
list_length = array_count (seam_list);
- for (test_index = 0, blob = first_blob->next;
- test_index < index; test_index++, blob = blob->next) {
- test_seam = (SEAM *) array_value (seam_list, test_index);
+ for (test_index=0, blob=first_blob->next;
+ test_index < index;
+ test_index++, blob=blob->next) {
+ test_seam = (SEAM *) array_value(seam_list, test_index);
if (test_index + test_seam->widthp < index &&
test_seam->widthp + test_index == index - 1 &&
account_splits_right(test_seam, blob) < 0)
return false;
}
- for (test_index = index, blob = left_blob->next;
- test_index < list_length; test_index++, blob = blob->next) {
- test_seam = (SEAM *) array_value (seam_list, test_index);
+ for (test_index=index, blob=left_blob->next;
+ test_index < list_length;
+ test_index++, blob=blob->next) {
+ test_seam = (SEAM *) array_value(seam_list, test_index);
if (test_index - test_seam->widthn >= index &&
test_index - test_seam->widthn == index &&
account_splits_left(test_seam, first_blob, blob) < 0)
@@ -210,32 +242,32 @@ SEAMS insert_seam(SEAMS seam_list,
int test_index;
int list_length;
- list_length = array_count (seam_list);
- for (test_index = 0, blob = first_blob->next;
- test_index < index; test_index++, blob = blob->next) {
- test_seam = (SEAM *) array_value (seam_list, test_index);
+ list_length = array_count(seam_list);
+ for (test_index=0, blob=first_blob->next;
+ test_index < index;
+ test_index++, blob=blob->next) {
+ test_seam = (SEAM *) array_value(seam_list, test_index);
if (test_index + test_seam->widthp >= index) {
test_seam->widthp++; /*got in the way */
- }
- else if (test_seam->widthp + test_index == index - 1) {
+ } else if (test_seam->widthp + test_index == index - 1) {
test_seam->widthp = account_splits_right(test_seam, blob);
if (test_seam->widthp < 0) {
- cprintf ("Failed to find any right blob for a split!\n");
+ cprintf("Failed to find any right blob for a split!\n");
print_seam("New dud seam", seam);
print_seam("Failed seam", test_seam);
}
}
}
- for (test_index = index, blob = left_blob->next;
- test_index < list_length; test_index++, blob = blob->next) {
- test_seam = (SEAM *) array_value (seam_list, test_index);
+ for (test_index=index, blob=left_blob->next;
+ test_index < list_length;
+ test_index++, blob=blob->next) {
+ test_seam = (SEAM *) array_value(seam_list, test_index);
if (test_index - test_seam->widthn < index) {
test_seam->widthn++; /*got in the way */
- }
- else if (test_index - test_seam->widthn == index) {
+ } else if (test_index - test_seam->widthn == index) {
test_seam->widthn = account_splits_left(test_seam, first_blob, blob);
if (test_seam->widthn < 0) {
- cprintf ("Failed to find any left blob for a split!\n");
+ cprintf("Failed to find any left blob for a split!\n");
print_seam("New dud seam", seam);
print_seam("Failed seam", test_seam);
}
@@ -263,18 +295,17 @@ int account_splits_right(SEAM *seam, TBLOB *blob) {
width = 0;
do {
if (!found_em[0])
- found_em[0] = find_split_in_blob (seam->split1, blob);
+ found_em[0] = find_split_in_blob(seam->split1, blob);
if (!found_em[1])
- found_em[1] = find_split_in_blob (seam->split2, blob);
+ found_em[1] = find_split_in_blob(seam->split2, blob);
if (!found_em[2])
- found_em[2] = find_split_in_blob (seam->split3, blob);
+ found_em[2] = find_split_in_blob(seam->split3, blob);
if (found_em[0] && found_em[1] && found_em[2]) {
return width;
}
width++;
blob = blob->next;
- }
- while (blob != NULL);
+ } while (blob != NULL);
return -1;
}
@@ -286,34 +317,38 @@ int account_splits_right(SEAM *seam, TBLOB *blob) {
* in the blob list.
*/
int account_splits_left(SEAM *seam, TBLOB *blob, TBLOB *end_blob) {
- static inT32 depth = 0;
- static inT8 width;
- static inT8 found_em[3];
+ inT32 depth = 0;
+ inT8 width = 0;
+ inT8 found_em[3];
+ account_splits_left_helper(seam, blob, end_blob, &depth, &width, found_em);
+ return width;
+}
+void account_splits_left_helper(SEAM *seam, TBLOB *blob, TBLOB *end_blob,
+ inT32 *depth, inT8 *width, inT8* found_em) {
if (blob != end_blob) {
- depth++;
- account_splits_left (seam, blob->next, end_blob);
- depth--;
- }
- else {
+ (*depth)++;
+ account_splits_left_helper(seam, blob->next, end_blob,
+ depth, width, found_em);
+ (*depth)--;
+ } else {
found_em[0] = seam->split1 == NULL;
found_em[1] = seam->split2 == NULL;
found_em[2] = seam->split3 == NULL;
- width = 0;
+ *width = 0;
}
if (!found_em[0])
- found_em[0] = find_split_in_blob (seam->split1, blob);
+ found_em[0] = find_split_in_blob(seam->split1, blob);
if (!found_em[1])
- found_em[1] = find_split_in_blob (seam->split2, blob);
+ found_em[1] = find_split_in_blob(seam->split2, blob);
if (!found_em[2])
- found_em[2] = find_split_in_blob (seam->split3, blob);
+ found_em[2] = find_split_in_blob(seam->split3, blob);
if (!found_em[0] || !found_em[1] || !found_em[2]) {
- width++;
- if (depth == 0) {
- width = -1;
+ (*width)++;
+ if (*depth == 0) {
+ *width = -1;
}
}
- return width;
}
@@ -325,19 +360,13 @@ int account_splits_left(SEAM *seam, TBLOB *blob, TBLOB *end_blob) {
bool find_split_in_blob(SPLIT *split, TBLOB *blob) {
TESSLINE *outline;
-#if 0
for (outline = blob->outlines; outline != NULL; outline = outline->next)
- if (is_split_outline (outline, split))
- return TRUE;
- return FALSE;
-#endif
- for (outline = blob->outlines; outline != NULL; outline = outline->next)
- if (point_in_outline(split->point1, outline))
+ if (outline->Contains(split->point1->pos))
break;
if (outline == NULL)
return FALSE;
for (outline = blob->outlines; outline != NULL; outline = outline->next)
- if (point_in_outline(split->point2, outline))
+ if (outline->Contains(split->point2->pos))
return TRUE;
return FALSE;
}
@@ -356,9 +385,9 @@ SEAM *join_two_seams(SEAM *seam1, SEAM *seam2) {
assert(seam1 &&seam2);
if (((seam1->split3 == NULL && seam2->split2 == NULL) ||
- (seam1->split2 == NULL && seam2->split3 == NULL) ||
- seam1->split1 == NULL ||
- seam2->split1 == NULL) && (!shared_split_points (seam1, seam2))) {
+ (seam1->split2 == NULL && seam2->split3 == NULL) ||
+ seam1->split1 == NULL || seam2->split1 == NULL) &&
+ (!shared_split_points(seam1, seam2))) {
clone_seam(result, seam1);
clone_seam(temp, seam2);
combine_seams(result, temp);
@@ -375,7 +404,7 @@ SEAM *join_two_seams(SEAM *seam1, SEAM *seam2) {
* Initailization of this record is done by this routine.
*/
SEAM *new_seam(PRIORITY priority,
- int x_location,
+ const TPOINT& location,
SPLIT *split1,
SPLIT *split2,
SPLIT *split3) {
@@ -384,7 +413,7 @@ SEAM *new_seam(PRIORITY priority,
seam = newseam ();
seam->priority = priority;
- seam->location = x_location;
+ seam->location = location;
seam->widthp = 0;
seam->widthn = 0;
seam->split1 = split1;
@@ -414,17 +443,16 @@ SEAMS new_seam_list() {
void print_seam(const char *label, SEAM *seam) {
if (seam) {
cprintf(label);
- cprintf (" %6.2f @ %5d, p=%d, n=%d ",
- seam->priority, seam->location, seam->widthp, seam->widthn);
-
- print_split (seam->split1);
+ cprintf(" %6.2f @ (%d,%d), p=%d, n=%d ",
+ seam->priority, seam->location.x, seam->location.y,
+ seam->widthp, seam->widthn);
+ print_split(seam->split1);
if (seam->split2) {
- cprintf (", ");
+ cprintf(", ");
print_split (seam->split2);
-
if (seam->split3) {
- cprintf (", ");
+ cprintf(", ");
print_split (seam->split3);
}
}
@@ -444,12 +472,12 @@ void print_seams(const char *label, SEAMS seams) {
char number[CHARS_PER_LINE];
if (seams) {
- cprintf ("%s\n", label);
+ cprintf("%s\n", label);
array_loop(seams, x) {
- sprintf (number, "%2d: ", x);
- print_seam (number, (SEAM *) array_value (seams, x));
+ sprintf(number, "%2d: ", x);
+ print_seam(number, (SEAM *) array_value(seams, x));
}
- cprintf ("\n");
+ cprintf("\n");
}
}
@@ -467,18 +495,186 @@ int shared_split_points(SEAM *seam1, SEAM *seam2) {
if (seam2->split1 == NULL)
return (FALSE);
- if (point_in_seam (seam1, seam2->split1))
+ if (point_in_seam(seam1, seam2->split1))
return (TRUE);
if (seam2->split2 == NULL)
return (FALSE);
- if (point_in_seam (seam1, seam2->split2))
+ if (point_in_seam(seam1, seam2->split2))
return (TRUE);
if (seam2->split3 == NULL)
return (FALSE);
- if (point_in_seam (seam1, seam2->split3))
+ if (point_in_seam(seam1, seam2->split3))
return (TRUE);
return (FALSE);
}
+
+/**********************************************************************
+ * break_pieces
+ *
+ * Break up the blobs in this chain so that they are all independent.
+ * This operation should undo the affect of join_pieces.
+ **********************************************************************/
+void break_pieces(TBLOB *blobs, SEAMS seams, inT16 start, inT16 end) {
+ TESSLINE *outline = blobs->outlines;
+ TBLOB *next_blob;
+ inT16 x;
+
+ for (x = start; x < end; x++)
+ reveal_seam ((SEAM *) array_value (seams, x));
+
+ next_blob = blobs->next;
+
+ while (outline && next_blob) {
+ if (outline->next == next_blob->outlines) {
+ outline->next = NULL;
+ outline = next_blob->outlines;
+ next_blob = next_blob->next;
+ }
+ else {
+ outline = outline->next;
+ }
+ }
+}
+
+
+/**********************************************************************
+ * join_pieces
+ *
+ * Join a group of base level pieces into a single blob that can then
+ * be classified.
+ **********************************************************************/
+void join_pieces(TBLOB *piece_blobs, SEAMS seams, inT16 start, inT16 end) {
+ TBLOB *next_blob;
+ TBLOB *blob;
+ inT16 x;
+ TESSLINE *outline;
+ SEAM *seam;
+
+ for (x = 0, blob = piece_blobs; x < start; x++)
+ blob = blob->next;
+ next_blob = blob->next;
+ outline = blob->outlines;
+ if (!outline)
+ return;
+
+ while (x < end) {
+ seam = (SEAM *) array_value (seams, x);
+ if (x - seam->widthn >= start && x + seam->widthp < end)
+ hide_seam(seam);
+ while (outline->next)
+ outline = outline->next;
+ outline->next = next_blob->outlines;
+ next_blob = next_blob->next;
+
+ x++;
+ }
+}
+
+
+/**********************************************************************
+ * hide_seam
+ *
+ * Change the edge points that are referenced by this seam to make
+ * them hidden edges.
+ **********************************************************************/
+void hide_seam(SEAM *seam) {
+ if (seam == NULL || seam->split1 == NULL)
+ return;
+ hide_edge_pair (seam->split1->point1, seam->split1->point2);
+
+ if (seam->split2 == NULL)
+ return;
+ hide_edge_pair (seam->split2->point1, seam->split2->point2);
+
+ if (seam->split3 == NULL)
+ return;
+ hide_edge_pair (seam->split3->point1, seam->split3->point2);
+}
+
+
+/**********************************************************************
+ * hide_edge_pair
+ *
+ * Change the edge points that are referenced by this seam to make
+ * them hidden edges.
+ **********************************************************************/
+void hide_edge_pair(EDGEPT *pt1, EDGEPT *pt2) {
+ EDGEPT *edgept;
+
+ edgept = pt1;
+ do {
+ edgept->Hide();
+ edgept = edgept->next;
+ }
+ while (!exact_point (edgept, pt2) && edgept != pt1);
+ if (edgept == pt1) {
+ /* cprintf("Hid entire outline at (%d,%d)!!\n",
+ edgept->pos.x,edgept->pos.y); */
+ }
+ edgept = pt2;
+ do {
+ edgept->Hide();
+ edgept = edgept->next;
+ }
+ while (!exact_point (edgept, pt1) && edgept != pt2);
+ if (edgept == pt2) {
+ /* cprintf("Hid entire outline at (%d,%d)!!\n",
+ edgept->pos.x,edgept->pos.y); */
+ }
+}
+
+
+/**********************************************************************
+ * reveal_seam
+ *
+ * Change the edge points that are referenced by this seam to make
+ * them hidden edges.
+ **********************************************************************/
+void reveal_seam(SEAM *seam) {
+ if (seam == NULL || seam->split1 == NULL)
+ return;
+ reveal_edge_pair (seam->split1->point1, seam->split1->point2);
+
+ if (seam->split2 == NULL)
+ return;
+ reveal_edge_pair (seam->split2->point1, seam->split2->point2);
+
+ if (seam->split3 == NULL)
+ return;
+ reveal_edge_pair (seam->split3->point1, seam->split3->point2);
+}
+
+
+/**********************************************************************
+ * reveal_edge_pair
+ *
+ * Change the edge points that are referenced by this seam to make
+ * them hidden edges.
+ **********************************************************************/
+void reveal_edge_pair(EDGEPT *pt1, EDGEPT *pt2) {
+ EDGEPT *edgept;
+
+ edgept = pt1;
+ do {
+ edgept->Reveal();
+ edgept = edgept->next;
+ }
+ while (!exact_point (edgept, pt2) && edgept != pt1);
+ if (edgept == pt1) {
+ /* cprintf("Hid entire outline at (%d,%d)!!\n",
+ edgept->pos.x,edgept->pos.y); */
+ }
+ edgept = pt2;
+ do {
+ edgept->Reveal();
+ edgept = edgept->next;
+ }
+ while (!exact_point (edgept, pt1) && edgept != pt2);
+ if (edgept == pt2) {
+ /* cprintf("Hid entire outline at (%d,%d)!!\n",
+ edgept->pos.x,edgept->pos.y); */
+ }
+}
diff --git a/wordrec/seam.h b/ccstruct/seam.h
similarity index 72%
rename from wordrec/seam.h
rename to ccstruct/seam.h
index e8793757e0..14ee8c9f55 100644
--- a/wordrec/seam.h
+++ b/ccstruct/seam.h
@@ -28,6 +28,7 @@
/*----------------------------------------------------------------------
I n c l u d e s
----------------------------------------------------------------------*/
+#include "blobs.h"
#include "split.h"
#include "tessarray.h"
@@ -41,7 +42,7 @@ typedef struct seam_record
PRIORITY priority;
inT8 widthp;
inT8 widthn;
- inT16 location;
+ TPOINT location;
SPLIT *split1;
SPLIT *split2;
SPLIT *split3;
@@ -60,20 +61,20 @@ extern SEAM *newseam();
* Create a new seam record and copy the contents of this seam into it.
*/
-#define clone_seam(dest,source) \
-if (source) { \
- (dest) = newseam (); \
- (dest)->location = (source)->location; \
- (dest)->widthp = (source)->widthp; \
- (dest)->widthn = (source)->widthn; \
- (dest)->priority = (source)->priority; \
- clone_split ((dest)->split1, (source)->split1); \
- clone_split ((dest)->split2, (source)->split2); \
- clone_split ((dest)->split3, (source)->split3); \
-} \
-else { \
- (dest) = (SEAM*) NULL; \
-} \
+#define clone_seam(dest,source) \
+if (source) { \
+ (dest) = newseam (); \
+ (dest)->location = (source)->location; \
+ (dest)->widthp = (source)->widthp; \
+ (dest)->widthn = (source)->widthn; \
+ (dest)->priority = (source)->priority; \
+ clone_split ((dest)->split1, (source)->split1); \
+ clone_split ((dest)->split2, (source)->split2); \
+ clone_split ((dest)->split3, (source)->split3); \
+} \
+else { \
+ (dest) = (SEAM*) NULL; \
+} \
/**
@@ -84,7 +85,7 @@ else { \
*/
#define exact_point(p1,p2) \
- (! ((p1->pos.x - p2->pos.x) || (p1->pos.y - p2->pos.y)))
+ (! ((p1->pos.x - p2->pos.x) || (p1->pos.y - p2->pos.y)))
/*----------------------------------------------------------------------
F u n c t i o n s
@@ -99,6 +100,8 @@ void combine_seams(SEAM *dest_seam, SEAM *source_seam);
void delete_seam(void *arg); //SEAM *seam);
+SEAMS start_seam_list(TBLOB *blobs);
+
void free_seam_list(SEAMS seam_list);
bool test_insert_seam(SEAMS seam_list,
@@ -116,12 +119,15 @@ int account_splits_right(SEAM *seam, TBLOB *blob);
int account_splits_left(SEAM *seam, TBLOB *blob, TBLOB *end_blob);
+void account_splits_left_helper(SEAM *seam, TBLOB *blob, TBLOB *end_blob,
+ inT32 *depth, inT8 *width, inT8 *found_em);
+
bool find_split_in_blob(SPLIT *split, TBLOB *blob);
SEAM *join_two_seams(SEAM *seam1, SEAM *seam2);
SEAM *new_seam(PRIORITY priority,
- int x_location,
+ const TPOINT& location,
SPLIT *split1,
SPLIT *split2,
SPLIT *split3);
@@ -133,4 +139,17 @@ void print_seam(const char *label, SEAM *seam);
void print_seams(const char *label, SEAMS seams);
int shared_split_points(SEAM *seam1, SEAM *seam2);
+
+void break_pieces(TBLOB *blobs, SEAMS seams, inT16 start, inT16 end);
+
+void join_pieces(TBLOB *piece_blobs, SEAMS seams, inT16 start, inT16 end);
+
+void hide_seam(SEAM *seam);
+
+void hide_edge_pair(EDGEPT *pt1, EDGEPT *pt2);
+
+void reveal_seam(SEAM *seam);
+
+void reveal_edge_pair(EDGEPT *pt1, EDGEPT *pt2);
+
#endif
diff --git a/wordrec/split.cpp b/ccstruct/split.cpp
similarity index 94%
rename from wordrec/split.cpp
rename to ccstruct/split.cpp
index 4fc4a5aaf5..3bdc9f211e 100644
--- a/wordrec/split.cpp
+++ b/ccstruct/split.cpp
@@ -27,7 +27,6 @@
----------------------------------------------------------------------*/
#include "split.h"
#include "structures.h"
-#include "hideedge.h"
#include "callcpp.h"
#ifdef __UNIX__
@@ -39,9 +38,7 @@
----------------------------------------------------------------------*/
BOOL_VAR(wordrec_display_splits, 0, "Display splits");
-#define SPLITBLOCK 100 /* Cells per block */
-makestructure (newsplit, free_split, printsplit, SPLIT,
-freesplit, SPLITBLOCK, "SPLIT", splitcount);
+makestructure(newsplit, free_split, SPLIT);
/*----------------------------------------------------------------------
F u n c t i o n s
@@ -68,7 +65,7 @@ void delete_split(SPLIT *split) {
EDGEPT *make_edgept(int x, int y, EDGEPT *next, EDGEPT *prev) {
EDGEPT *this_edgept;
/* Create point */
- this_edgept = newedgept ();
+ this_edgept = new EDGEPT;
this_edgept->pos.x = x;
this_edgept->pos.y = y;
/* Hook it up */
@@ -82,9 +79,6 @@ EDGEPT *make_edgept(int x, int y, EDGEPT *next, EDGEPT *prev) {
this_edgept->prev->vec.x = x - this_edgept->prev->pos.x;
this_edgept->prev->vec.y = y - this_edgept->prev->pos.y;
- reveal_edge(this_edgept);
- this_edgept->flags[1] = 0;
-
return (this_edgept);
}
@@ -159,8 +153,8 @@ void unsplit_outlines(EDGEPT *p1, EDGEPT *p2) {
p1->next = tmp2->next;
p2->next = tmp1->next;
- oldedgept(tmp1);
- oldedgept(tmp2);
+ delete tmp1;
+ delete tmp2;
p1->vec.x = p1->next->pos.x - p1->pos.x;
p1->vec.y = p1->next->pos.y - p1->pos.y;
diff --git a/wordrec/split.h b/ccstruct/split.h
similarity index 99%
rename from wordrec/split.h
rename to ccstruct/split.h
index ae3a25d5d5..ad76725fd4 100644
--- a/wordrec/split.h
+++ b/ccstruct/split.h
@@ -28,7 +28,7 @@
/*----------------------------------------------------------------------
I n c l u d e s
----------------------------------------------------------------------*/
-#include "tessclas.h"
+#include "blobs.h"
#include "oldlist.h"
/*----------------------------------------------------------------------
diff --git a/ccstruct/statistc.cpp b/ccstruct/statistc.cpp
index b8ceac0d2c..1d765174aa 100644
--- a/ccstruct/statistc.cpp
+++ b/ccstruct/statistc.cpp
@@ -740,8 +740,6 @@ DLLSYM inT32 choose_nth_item( //fast median
float *array, //array of items
inT32 count //no of items
) {
- static uinT16 seeds[3] = { SEED1, SEED2, SEED3 };
- //for nrand
inT32 next_sample; //next one to do
inT32 next_lesser; //space for new
inT32 prev_greater; //last one saved
@@ -764,11 +762,7 @@ DLLSYM inT32 choose_nth_item( //fast median
index = 0; //ensure lergal
else if (index >= count)
index = count - 1;
- #ifdef __UNIX__
- equal_count = (inT32) (nrand48 (seeds) % count);
- #else
- equal_count = (inT32) (rand () % count);
- #endif
+ equal_count = (inT32) (rand() % count);
pivot = array[equal_count];
//fill gap
array[equal_count] = array[0];
@@ -823,8 +817,6 @@ size_t size, //element size
//comparator
int (*compar) (const void *, const void *)
) {
- static uinT16 seeds[3] = { SEED1, SEED2, SEED3 };
- //for nrand
int result; //of compar
inT32 next_sample; //next one to do
inT32 next_lesser; //space for new
@@ -846,11 +838,7 @@ int (*compar) (const void *, const void *)
index = 0; //ensure lergal
else if (index >= count)
index = count - 1;
- #ifdef __UNIX__
- pivot = (inT32) (nrand48 (seeds) % count);
- #else
pivot = (inT32) (rand () % count);
- #endif
swap_entries (array, size, pivot, 0);
next_lesser = 0;
prev_greater = count;
diff --git a/ccstruct/stepblob.cpp b/ccstruct/stepblob.cpp
index fe6f996fcd..13450f8233 100644
--- a/ccstruct/stepblob.cpp
+++ b/ccstruct/stepblob.cpp
@@ -18,7 +18,8 @@
**********************************************************************/
#include "mfcpch.h"
-#include "stepblob.h"
+#include "stepblob.h"
+#include "allheaders.h"
// Include automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
@@ -166,6 +167,14 @@ C_BLOB::C_BLOB( //constructor
}
}
+// Simpler constructor to build a blob from a single outline that has
+// already been fully initialized.
+C_BLOB::C_BLOB(C_OUTLINE* outline) {
+ C_OUTLINE_IT it(&outlines);
+ it.add_to_end(outline);
+}
+
+
// Build and return a fake blob containing a single fake outline with no
// steps.
C_BLOB* C_BLOB::FakeBlob(const TBOX& box) {
@@ -320,6 +329,24 @@ void C_BLOB::rotate(const FCOORD& rotation) {
RotateOutlineList(rotation, &outlines);
}
+static void render_outline_list(C_OUTLINE_LIST *list,
+ int left, int top, Pix* pix) {
+ C_OUTLINE_IT it(list);
+ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
+ C_OUTLINE* outline = it.data();
+ outline->render(left, top, pix);
+ if (!outline->child()->empty())
+ render_outline_list(outline->child(), left, top, pix);
+ }
+}
+
+// Returns a Pix rendering of the blob. pixDestroy after use.
+Pix* C_BLOB::render() {
+ TBOX box = bounding_box();
+ Pix* pix = pixCreate(box.width(), box.height(), 1);
+ render_outline_list(&outlines, box.left(), box.top(), pix);
+ return pix;
+}
/**********************************************************************
* C_BLOB::plot
diff --git a/ccstruct/stepblob.h b/ccstruct/stepblob.h
index a4dcaece0f..17b89df74b 100644
--- a/ccstruct/stepblob.h
+++ b/ccstruct/stepblob.h
@@ -23,12 +23,17 @@
#include "coutln.h"
#include "rect.h"
+struct Pix;
+
class C_BLOB:public ELIST_LINK
{
public:
C_BLOB() {
}
explicit C_BLOB(C_OUTLINE_LIST *outline_list);
+ // Simpler constructor to build a blob from a single outline that has
+ // already been fully initialized.
+ explicit C_BLOB(C_OUTLINE* outline);
// Build and return a fake blob containing a single fake outline with no
// steps.
@@ -48,6 +53,9 @@ class C_BLOB:public ELIST_LINK
void move(const ICOORD vec); // repostion blob by vector
void rotate(const FCOORD& rotation); // Rotate by given vector.
+ // Returns a Pix rendering of the blob. pixDestroy after use.
+ Pix* render();
+
void plot( //draw one
ScrollView* window, //window to draw in
ScrollView::Color blob_colour, //for outer bits
diff --git a/ccstruct/vecfuncs.h b/ccstruct/vecfuncs.h
index 844d036f21..4a21d7c5e2 100644
--- a/ccstruct/vecfuncs.h
+++ b/ccstruct/vecfuncs.h
@@ -25,8 +25,10 @@
#ifndef VECFUNCS_H
#define VECFUNCS_H
-#include "tessclas.h"
#include
+#include "blobs.h"
+
+class EDGEPT;
/*----------------------------------------------------------------------
M a c r o s
@@ -75,17 +77,4 @@
----------------------------------------------------------------------*/
int direction(EDGEPT *point);
-/*
-#if defined(__STDC__) || defined(__cplusplus) || MAC_OR_DOS
-# define _ARGS(s) s
-#else
-# define _ARGS(s) ()
-#endif*/
-
-/* vecfuncs.c
-int direction
- _ARGS((EDGEPT *point));
-
-#undef _ARGS
-*/
#endif
diff --git a/ccstruct/werd.cpp b/ccstruct/werd.cpp
index c6cbe9c391..cd7fbf0bf2 100644
--- a/ccstruct/werd.cpp
+++ b/ccstruct/werd.cpp
@@ -1,8 +1,8 @@
/**********************************************************************
* File: werd.cpp (Formerly word.c)
* Description: Code for the WERD class.
- * Author: Ray Smith
- * Created: Tue Oct 08 14:32:12 BST 1991
+ * Author: Ray Smith
+ * Created: Tue Oct 08 14:32:12 BST 1991
*
* (C) Copyright 1991, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,63 +18,51 @@
**********************************************************************/
#include "mfcpch.h"
-#include "blckerr.h"
-#include "linlsq.h"
-#include "werd.h"
+#include "blckerr.h"
+#include "helpers.h"
+#include "linlsq.h"
+#include "werd.h"
// Include automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif
-#define FIRST_COLOUR ScrollView::RED //< first rainbow colour
-
-/// last rainbow colour
-#define LAST_COLOUR ScrollView::AQUAMARINE
-#define CHILD_COLOUR ScrollView::BROWN //< colour of children
+#define FIRST_COLOUR ScrollView::RED //< first rainbow colour
+#define LAST_COLOUR ScrollView::AQUAMARINE //< last rainbow colour
+#define CHILD_COLOUR ScrollView::BROWN //< colour of children
const ERRCODE CANT_SCALE_EDGESTEPS =
-"Attempted to scale an edgestep format word";
-
-#define EXTERN
+ "Attempted to scale an edgestep format word";
-EXTERN BOOL_VAR (bln_numericmode, 0, "Optimize for numbers");
-EXTERN INT_VAR (bln_x_height, 128, "Baseline Normalisation X-height");
-EXTERN INT_VAR (bln_baseline_offset, 64, "Baseline Norm. offset of baseline");
-EXTERN double_VAR (bln_blshift_maxshift, -1.0,
-"Fraction of xh before shifting");
-EXTERN double_VAR (bln_blshift_xfraction, 0.75,
-"Size fraction of xh before shifting");
+ELIST2IZE_S(WERD)
-ELISTIZE_S (WERD)
/**
* WERD::WERD
*
* Constructor to build a WERD from a list of C_BLOBs.
- * The C_BLOBs are not copied so the source list is emptied.
+ * blob_list The C_BLOBs (in word order) are not copied;
+ * we take its elements and put them in our lists.
+ * blank_count blanks in front of the word
+ * text correct text, outlives this WERD
*/
-WERD::WERD ( //constructor
-C_BLOB_LIST * blob_list, //< in word order
-uinT8 blank_count, //< blanks in front
-const char *text //< correct text
-):
-flags (0),
-correct(text) {
- C_BLOB_IT start_it = blob_list;//iterator
- C_BLOB_IT end_it = blob_list; //another
- //rejected blobs in wd
+WERD::WERD(C_BLOB_LIST *blob_list, uinT8 blank_count, const char *text)
+ : blanks(blank_count),
+ flags(0),
+ script_id_(0),
+ correct(text) {
+ C_BLOB_IT start_it = blob_list;
+ C_BLOB_IT end_it = blob_list;
C_BLOB_IT rej_cblob_it = &rej_cblobs;
- C_OUTLINE_IT c_outline_it; //coutline iterator
- BOOL8 blob_inverted;
- BOOL8 reject_blob;
+ C_OUTLINE_IT c_outline_it;
inT16 inverted_vote = 0;
inT16 non_inverted_vote = 0;
- while (!end_it.at_last ())
- end_it.forward (); //move to last
- //move to our list
- cblobs.assign_to_sublist (&start_it, &end_it);
- blanks = blank_count;
+ // Move blob_list's elements into cblobs.
+ while (!end_it.at_last())
+ end_it.forward();
+ cblobs.assign_to_sublist(&start_it, &end_it);
+
/*
Set white on black flag for the WERD, moving any duff blobs onto the
rej_cblobs list.
@@ -88,23 +76,23 @@ correct(text) {
Walk the blobs again, moving any blob whose inversion flag does not agree
with the concencus onto the reject list.
*/
- start_it.set_to_list (&cblobs);
- if (start_it.empty ())
+ start_it.set_to_list(&cblobs);
+ if (start_it.empty())
return;
- for (start_it.mark_cycle_pt ();
- !start_it.cycled_list (); start_it.forward ()) {
- c_outline_it.set_to_list (start_it.data ()->out_list ());
- blob_inverted = c_outline_it.data ()->flag (COUT_INVERSE);
- reject_blob = FALSE;
- for (c_outline_it.mark_cycle_pt ();
- !c_outline_it.cycled_list () && !reject_blob;
- c_outline_it.forward ()) {
- reject_blob =
- c_outline_it.data ()->flag (COUT_INVERSE) != blob_inverted;
+ for (start_it.mark_cycle_pt(); !start_it.cycled_list(); start_it.forward()) {
+ BOOL8 reject_blob = FALSE;
+ BOOL8 blob_inverted;
+
+ c_outline_it.set_to_list(start_it.data()->out_list());
+ blob_inverted = c_outline_it.data()->flag(COUT_INVERSE);
+ for (c_outline_it.mark_cycle_pt();
+ !c_outline_it.cycled_list() && !reject_blob;
+ c_outline_it.forward()) {
+ reject_blob = c_outline_it.data()->flag(COUT_INVERSE) != blob_inverted;
}
- if (reject_blob)
- rej_cblob_it.add_after_then_move (start_it.extract ());
- else {
+ if (reject_blob) {
+ rej_cblob_it.add_after_then_move(start_it.extract());
+ } else {
if (blob_inverted)
inverted_vote++;
else
@@ -112,16 +100,15 @@ correct(text) {
}
}
- flags.set_bit (W_INVERSE, (inverted_vote > non_inverted_vote));
+ flags.set_bit(W_INVERSE, (inverted_vote > non_inverted_vote));
- start_it.set_to_list (&cblobs);
- if (start_it.empty ())
+ start_it.set_to_list(&cblobs);
+ if (start_it.empty())
return;
- for (start_it.mark_cycle_pt ();
- !start_it.cycled_list (); start_it.forward ()) {
- c_outline_it.set_to_list (start_it.data ()->out_list ());
- if (c_outline_it.data ()->flag (COUT_INVERSE) != flags.bit (W_INVERSE))
- rej_cblob_it.add_after_then_move (start_it.extract ());
+ for (start_it.mark_cycle_pt(); !start_it.cycled_list(); start_it.forward()) {
+ c_outline_it.set_to_list(start_it.data()->out_list());
+ if (c_outline_it.data()->flag(COUT_INVERSE) != flags.bit(W_INVERSE))
+ rej_cblob_it.add_after_then_move(start_it.extract());
}
}
@@ -133,13 +120,12 @@ correct(text) {
* The BLOBs are not copied so the source list is emptied.
*/
-WERD::WERD ( //constructor
-PBLOB_LIST * blob_list, //< in word order
-uinT8 blank_count, //< blanks in front
-const char *text //< correct text
-):
-flags (0),
-correct(text) {
+WERD::WERD(PBLOB_LIST *blob_list, //< In word order
+ uinT8 blank_count, //< Blanks in front
+ const char *text) //< Ccorrect text
+ : flags(0),
+ script_id_(0),
+ correct(text) {
PBLOB_IT start_it = blob_list; //iterator
PBLOB_IT end_it = blob_list; //another
@@ -161,10 +147,11 @@ correct(text) {
* The BLOBs are not copied so the source list is emptied.
*/
-WERD::WERD ( //constructor
-PBLOB_LIST * blob_list, //< in word order
-WERD * clone //< sorce of flags
-):flags (clone->flags), correct (clone->correct) {
+WERD::WERD(PBLOB_LIST * blob_list, //< In word order
+ WERD * clone) //< Source of flags
+ : flags(clone->flags),
+ script_id_(clone->script_id_),
+ correct(clone->correct) {
PBLOB_IT start_it = blob_list; //iterator
PBLOB_IT end_it = blob_list; //another
@@ -184,12 +171,13 @@ WERD * clone //< sorce of flags
* The C_BLOBs are not copied so the source list is emptied.
*/
-WERD::WERD ( //constructor
-C_BLOB_LIST * blob_list, //< in word order
-WERD * clone //< source of flags
-):flags (clone->flags), correct (clone->correct) {
- C_BLOB_IT start_it = blob_list;//iterator
- C_BLOB_IT end_it = blob_list; //another
+WERD::WERD(C_BLOB_LIST * blob_list, //< In word order
+ WERD * clone) //< Source of flags
+ : flags(clone->flags),
+ script_id_(clone->script_id_),
+ correct(clone->correct) {
+ C_BLOB_IT start_it = blob_list; // iterator
+ C_BLOB_IT end_it = blob_list; // another
while (!end_it.at_last ())
end_it.forward (); //move to last
@@ -199,6 +187,17 @@ WERD * clone //< source of flags
// fprintf(stderr,"Wrong constructor!!!!\n");
}
+// Construct a WERD from a single_blob and clone the flags from this.
+// W_BOL and W_EOL flags are set according to the given values.
+WERD* WERD::ConstructFromSingleBlob(bool bol, bool eol, C_BLOB* blob) {
+ C_BLOB_LIST temp_blobs;
+ C_BLOB_IT temp_it(&temp_blobs);
+ temp_it.add_after_then_move(blob);
+ WERD* blob_word = new WERD(&temp_blobs, this);
+ blob_word->set_flag(W_BOL, bol);
+ blob_word->set_flag(W_EOL, eol);
+ return blob_word;
+}
/**
* WERD::poly_copy
@@ -207,56 +206,36 @@ WERD * clone //< source of flags
* The source WERD is untouched.
*/
-WERD *WERD::poly_copy( //make a poly copy
- float xheight //< row height
- ) {
+WERD *WERD::poly_copy() {
PBLOB *blob; //new blob
WERD *result = new WERD; //output word
C_BLOB_IT src_it = &cblobs; //iterator
- // LARC_BLOB_IT larc_it=(LARC_BLOB_LIST*)(&cblobs);
PBLOB_IT dest_it = (PBLOB_LIST *) (&result->cblobs);
- //another
if (flags.bit (W_POLYGON)) {
*result = *this; //just copy it
}
else {
result->flags = flags;
+ result->script_id_ = script_id_;
result->correct = correct; //copy info
result->dummy = dummy;
if (!src_it.empty ()) {
- // if (flags.bit(W_LINEARC))
- // {
- // do
- // {
- // blob=new PBLOB;
- // poly_linearc_outlines(larc_it.data()->out_list(),
- // blob->out_list()); //convert outlines
- // dest_it.add_after_then_move(blob); //add to dest list
- // larc_it.forward();
- // }
- // while (!larc_it.at_first());
- // }
- // else
- // {
do {
- blob = new PBLOB (src_it.data (), xheight);
- //convert blob
- //add to dest list
+ // convert blob and add to dest list
+ blob = new PBLOB (src_it.data ());
dest_it.add_after_then_move (blob);
src_it.forward ();
}
while (!src_it.at_first ());
- // }
}
if (!rej_cblobs.empty ()) {
- /* Polygonal approx of reject blobs */
+ // Polygonal approx of reject blobs
src_it.set_to_list (&rej_cblobs);
dest_it = (PBLOB_LIST *) (&result->rej_cblobs);
do {
- //convert blob
- blob = new PBLOB (src_it.data (), xheight);
- //add to dest list
+ // convert blob and add to dest list
+ blob = new PBLOB (src_it.data ());
dest_it.add_after_then_move (blob);
src_it.forward ();
}
@@ -283,29 +262,24 @@ WERD *WERD::poly_copy( //make a poly copy
* row being marked as FUZZY space.
*/
-TBOX WERD::bounding_box() { //bounding box
- TBOX box; //box being built
- //rejected blobs in wd
- C_BLOB_IT rej_cblob_it = &rej_cblobs;
+TBOX WERD::bounding_box() {
+ TBOX box; // box being built
+ C_BLOB_IT rej_cblob_it = &rej_cblobs; // rejected blobs
- for (rej_cblob_it.mark_cycle_pt ();
- !rej_cblob_it.cycled_list (); rej_cblob_it.forward ()) {
- box += rej_cblob_it.data ()->bounding_box ();
+ for (rej_cblob_it.mark_cycle_pt(); !rej_cblob_it.cycled_list();
+ rej_cblob_it.forward()) {
+ box += rej_cblob_it.data()->bounding_box();
}
- if (flags.bit (W_POLYGON)) {
- //polygons
- PBLOB_IT it = (PBLOB_LIST *) (&cblobs);
-
- for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
- box += it.data ()->bounding_box ();
+ if (flags.bit(W_POLYGON)) { // polygons
+ PBLOB_IT it = (PBLOB_LIST *)(&cblobs);
+ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
+ box += it.data()->bounding_box();
}
- }
- else {
- C_BLOB_IT it = &cblobs; //blobs of WERD
-
- for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
- box += it.data ()->bounding_box ();
+ } else {
+ C_BLOB_IT it = &cblobs; // blobs of WERD
+ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
+ box += it.data()->bounding_box();
}
}
return box;
@@ -319,27 +293,17 @@ TBOX WERD::bounding_box() { //bounding box
* NOTE!! REJECT CBLOBS ARE NOT MOVED
*/
-void WERD::move( // reposition WERD
- const ICOORD vec //< by vector
- ) {
- PBLOB_IT blob_it ((PBLOB_LIST *) & cblobs);
- // blob iterator
- // LARC_BLOB_IT lblob_it((LARC_BLOB_LIST*)&cblobs);
+void WERD::move(const ICOORD vec) {
+ PBLOB_IT blob_it((PBLOB_LIST *)&cblobs);
C_BLOB_IT cblob_it(&cblobs); // cblob iterator
- if (flags.bit (W_POLYGON))
- for (blob_it.mark_cycle_pt ();
- !blob_it.cycled_list (); blob_it.forward ())
- blob_it.data ()->move (vec);
- // else if (flags.bit(W_LINEARC))
- // for( lblob_it.mark_cycle_pt();
- // !lblob_it.cycled_list();
- // lblob_it.forward() )
- // lblob_it.data()->move( vec );
- else
- for (cblob_it.mark_cycle_pt ();
- !cblob_it.cycled_list (); cblob_it.forward ())
- cblob_it.data ()->move (vec);
+ if (flags.bit(W_POLYGON)) {
+ for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward())
+ blob_it.data()->move(vec);
+ } else {
+ for (cblob_it.mark_cycle_pt(); !cblob_it.cycled_list(); cblob_it.forward())
+ cblob_it.data()->move(vec);
+ }
}
@@ -349,24 +313,15 @@ void WERD::move( // reposition WERD
* Scale WERD by multiplier
*/
-void WERD::scale( // scale WERD
- const float f //< by multiplier
- ) {
- PBLOB_IT blob_it ((PBLOB_LIST *) & cblobs);
- // blob iterator
- // LARC_BLOB_IT lblob_it((LARC_BLOB_LIST*)&cblobs);
-
- if (flags.bit (W_POLYGON))
- for (blob_it.mark_cycle_pt ();
- !blob_it.cycled_list (); blob_it.forward ())
- blob_it.data ()->scale (f);
- // else if (flags.bit(W_LINEARC))
- // for (lblob_it.mark_cycle_pt();
- // !lblob_it.cycled_list();
- // lblob_it.forward() )
- // lblob_it.data()->scale( f );
- else
- CANT_SCALE_EDGESTEPS.error ("WERD::scale", ABORT, NULL);
+void WERD::scale(const float f) {
+ PBLOB_IT blob_it((PBLOB_LIST *)&cblobs);
+
+ if (flags.bit(W_POLYGON)) {
+ for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward())
+ blob_it.data()->scale(f);
+ } else {
+ CANT_SCALE_EDGESTEPS.error("WERD::scale", ABORT, NULL);
+ }
}
@@ -376,22 +331,19 @@ void WERD::scale( // scale WERD
* Join other word onto this one. Delete the old word.
*/
-void WERD::join_on( // join WERD
- WERD *&other //< other word
- ) {
- PBLOB_IT blob_it ((PBLOB_LIST *) & cblobs);
- // blob iterator
- PBLOB_IT src_it ((PBLOB_LIST *) & other->cblobs);
+void WERD::join_on(WERD* other) {
+ PBLOB_IT blob_it((PBLOB_LIST *)&cblobs);
+ PBLOB_IT src_it((PBLOB_LIST *)&other->cblobs);
C_BLOB_IT rej_cblob_it(&rej_cblobs);
- C_BLOB_IT src_rej_it (&other->rej_cblobs);
+ C_BLOB_IT src_rej_it(&other->rej_cblobs);
- while (!src_it.empty ()) {
- blob_it.add_to_end (src_it.extract ());
- src_it.forward ();
+ while (!src_it.empty()) {
+ blob_it.add_to_end(src_it.extract());
+ src_it.forward();
}
- while (!src_rej_it.empty ()) {
- rej_cblob_it.add_to_end (src_rej_it.extract ());
- src_rej_it.forward ();
+ while (!src_rej_it.empty()) {
+ rej_cblob_it.add_to_end(src_rej_it.extract());
+ src_rej_it.forward();
}
}
@@ -402,345 +354,205 @@ void WERD::join_on( // join WERD
* Copy blobs from other word onto this one.
*/
-void WERD::copy_on( //copy blobs
- WERD *&other //< from other
- ) {
- if (flags.bit (W_POLYGON)) {
- PBLOB_IT blob_it ((PBLOB_LIST *) & cblobs);
+void WERD::copy_on(WERD* other) {
+ bool reversed = other->bounding_box().left() < bounding_box().left();
+ if (flags.bit(W_POLYGON)) {
+ PBLOB_IT blob_it((PBLOB_LIST *) & cblobs);
// blob iterator
PBLOB_LIST blobs;
blobs.deep_copy(reinterpret_cast(&other->cblobs),
&PBLOB::deep_copy);
- blob_it.move_to_last();
- blob_it.add_list_after(&blobs);
+ if (reversed) {
+ blob_it.add_list_before(&blobs);
+ } else {
+ blob_it.move_to_last();
+ blob_it.add_list_after(&blobs);
+ }
} else {
C_BLOB_IT c_blob_it(&cblobs);
C_BLOB_LIST c_blobs;
c_blobs.deep_copy(&other->cblobs, &C_BLOB::deep_copy);
- c_blob_it.move_to_last ();
- c_blob_it.add_list_after (&c_blobs);
+ if (reversed) {
+ c_blob_it.add_list_before(&c_blobs);
+ } else {
+ c_blob_it.move_to_last();
+ c_blob_it.add_list_after(&c_blobs);
+ }
}
- if (!other->rej_cblobs.empty ()) {
+ if (!other->rej_cblobs.empty()) {
C_BLOB_IT rej_c_blob_it(&rej_cblobs);
C_BLOB_LIST new_rej_c_blobs;
new_rej_c_blobs.deep_copy(&other->rej_cblobs, &C_BLOB::deep_copy);
- rej_c_blob_it.move_to_last ();
- rej_c_blob_it.add_list_after (&new_rej_c_blobs);
+ if (reversed) {
+ rej_c_blob_it.add_list_before(&new_rej_c_blobs);
+ } else {
+ rej_c_blob_it.move_to_last();
+ rej_c_blob_it.add_list_after(&new_rej_c_blobs);
+ }
}
}
/**
- * WERD::baseline_normalise
+ * WERD::baseline_normalize
*
- * Baseline Normalise the word in Tesseract style. (I.e origin at centre of
+ * Baseline Normalize the word in Tesseract style. (I.e origin at centre of
* word at bottom. x-height region scaled to region y =
- * (bln_baseline_offset)..(bln_baseline_offset + bln_x_height)
+ * (kBlnBaselineOffset)..(kBlnBaselineOffset + kBlnXHeight)
* - usually 64..192)
*/
-void WERD::baseline_normalise( // Tess style BL Norm
- ROW *row,
- DENORM *denorm //< antidote
- ) {
- baseline_normalise_x (row, row->x_height (), denorm);
- //Use standard x ht
+void WERD::baseline_normalize(ROW *row, DENORM *denorm, bool numeric_mode) {
+ baseline_normalize_x(row, row->x_height(), denorm, numeric_mode);
}
+/**********************************************************************
+ * Helper functions for WERD::baseline_normalize_x()
+ **********************************************************************/
+
/**
- * WERD::baseline_normalise_x
+ * Center a sequence of blobs of numbers [0-9] about x=0, with
+ * y = kBlnBaselineOffset .. kBlnBaselineOffset + (4/3) * kBlnXHeight
+ * (usually 64..170)
+ *
+ * Arguments:
+ * word_box - bounding box for the word in original coordinates.
+ * blobs - blobs to transform to normalized coordinates.
+ * antidote - this is the preliminary antidote (with the correct
+ * origin, default scale, and row).
+ * update_antidote - we should update antidote's segment information.
+ * Globals:
+ * kBlnXHeight
*
- * Baseline Normalise the word in Tesseract style. (I.e origin at centre of
+ **/
+static void baseline_normalize_numerals(
+ const TBOX &word_box, PBLOB_LIST *blobs, DENORM *antidote,
+ BOOL8 update_antidote) {
+ int segments = 0;
+ PBLOB_IT blob_it(blobs);
+ DENORM_SEG *segs = new DENORM_SEG[blob_it.length()];
+
+ for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
+ PBLOB *blob = blob_it.data();
+ TBOX blob_box = blob->bounding_box();
+ blob->move(FCOORD(-antidote->origin(), -blob_box.bottom()));
+ // Constrain the scale factor as target numbers should be either
+ // cap height already or xheight.
+ float factor = ClipToRange(
+ kBlnXHeight * 4.0f / (3 * blob_box.height()),
+ antidote->scale(), antidote->scale() * 1.5f);
+ blob->scale(factor);
+ blob->move(FCOORD(0.0, kBlnBaselineOffset));
+ segs[segments].xstart = blob->bounding_box().left();
+ segs[segments].ycoord = blob_box.bottom();
+ segs[segments++].scale_factor = factor;
+ }
+ if (update_antidote) {
+ antidote->set_segments(segs, segments);
+ }
+ delete [] segs;
+}
+
+/**
+ * Center a sequence of textual blobs about x=0,
+ * using antidote for y positioning, scaling and row baseline information.
+ **/
+static void baseline_normalize_text(const DENORM &antidote, PBLOB_LIST *blobs) {
+ PBLOB_IT blob_it(blobs);
+
+ for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
+ PBLOB *blob = blob_it.data();
+ TBOX blob_box = blob->bounding_box();
+ float blob_x_center = (blob_box.left() + blob_box.right()) / 2.0;
+ blob->move(FCOORD(-antidote.origin(),
+ -antidote.yshift_at_orig_x(blob_x_center)));
+ blob->scale(antidote.scale());
+ blob->move(FCOORD(0.0, kBlnBaselineOffset));
+ }
+}
+
+/**
+ * WERD::baseline_normalize_x
+ *
+ * Baseline Normalize the word in Tesseract style. (I.e origin at centre of
* word at bottom. x-height region scaled to region y =
- * (bln_baseline_offset)..(bln_baseline_offset + bln_x_height)
+ * (kBlnBaselineOffset)..(kBlnBaselineOffset + kBlnXHeight)
* - usually 64..192)
- * USE A SPECIFIED X-HEIGHT - NOT NECESSARILY THE ONE IN row
+ *
+ * Arguments:
+ * row - row information (mainly baseline info)
+ * x_height - the x height to assume in the source coordinates
+ * (not necessarily the one in row)
+ * denorm - if non NULL, where to return the "undo" information
*/
-void WERD::baseline_normalise_x( // Tess style BL Norm
- ROW *row,
- float x_height, //< non standard value
- DENORM *denorm //< antidote
- ) {
- BOOL8 using_row; //as baseline
- float blob_x_centre; //middle of blob
- float blob_offset; //bottom miss
- float top_offset; //top miss
- float blob_x_height; //xh for this blob
- inT16 segments; //no of segments
- inT16 segment; //current segment
- DENORM_SEG *segs; //array of segments
- float mean_x; //mean xheight
- inT32 x_count; //no of xs
- TBOX word_box = bounding_box ();//word bounding box
- TBOX blob_box; //blob bounding box
- PBLOB_IT blob_it ((PBLOB_LIST *) & cblobs);
- // blob iterator
- PBLOB *blob;
- LLSQ line; //fitted line
- double line_m, line_c; //fitted line
- //inverse norm
- DENORM antidote (word_box.left () +
-
- (word_box.right () - word_box.left ()) / 2.0,
- bln_x_height / x_height, row);
-
- if (!flags.bit (W_POLYGON)) {
- WRONG_WORD.error ("WERD::baseline_normalise", ABORT,
- "Need to poly approx");
+void WERD::baseline_normalize_x(ROW *row, float x_height,
+ DENORM *denorm, bool numeric_mode) {
+ 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);
}
- if (flags.bit (W_NORMALIZED)) {
- WRONG_WORD.error ("WERD::baseline_normalise", ABORT,
- "Baseline unnormalised");
- }
-
- if (bln_numericmode) {
- segs = new DENORM_SEG[blob_it.length ()];
- segments = 0;
- float factor; // For scaling to baseline normalised size.
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
- blob_it.forward ()) {
- blob = blob_it.data ();
- blob_box = blob->bounding_box ();
- blob->move (FCOORD (-antidote.origin (),
- -blob_box.bottom ()));
- factor = bln_x_height * 4.0f / (3 * blob_box.height ());
- // Constrain the scale factor as target numbers should be either
- // cap height already or xheight.
- if (factor < antidote.scale())
- factor = antidote.scale();
- else if (factor > antidote.scale() * 1.5f)
- factor = antidote.scale() * 1.5f;
- blob->scale (factor);
- blob->move (FCOORD (0.0, bln_baseline_offset));
- segs[segments].xstart = blob->bounding_box().left();
- segs[segments].ycoord = blob_box.bottom();
- segs[segments++].scale_factor = factor;
- }
- antidote = DENORM (antidote.origin (), antidote.scale (),
- 0.0f, 0.0f, segments, segs, true, row);
- delete [] segs;
-
- //Repeat for rej blobs
- blob_it.set_to_list ((PBLOB_LIST *) & rej_cblobs);
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
- blob_it.forward ()) {
- blob = blob_it.data ();
- blob_box = blob->bounding_box ();
- blob->move (FCOORD (-antidote.origin (),
- -blob_box.bottom ()));
- blob->scale (bln_x_height * 4.0f / (3 * blob_box.height ()));
- blob->move (FCOORD (0.0, bln_baseline_offset));
- }
+ if (!flags.bit(W_POLYGON)) {
+ WRONG_WORD.error("WERD::baseline_normalize", ABORT,
+ "Need to poly approx");
}
- else if (bln_blshift_maxshift < 0) {
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
- blob_it.forward ()) {
- blob = blob_it.data ();
- blob_box = blob->bounding_box ();
- blob_x_centre = blob_box.left () +
- (blob_box.right () - blob_box.left ()) / 2.0;
- blob->move (FCOORD (-antidote.origin (),
- -(row->base_line (blob_x_centre))));
- blob->scale (antidote.scale ());
- blob->move (FCOORD (0.0, bln_baseline_offset));
- }
-
- //Repeat for rej blobs
- blob_it.set_to_list ((PBLOB_LIST *) & rej_cblobs);
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
- blob_it.forward ()) {
- blob = blob_it.data ();
- blob_box = blob->bounding_box ();
- blob_x_centre = blob_box.left () +
- (blob_box.right () - blob_box.left ()) / 2.0;
- blob->move (FCOORD (-antidote.origin (),
- -(row->base_line (blob_x_centre))));
- blob->scale (antidote.scale ());
- blob->move (FCOORD (0.0, bln_baseline_offset));
- }
-
+ if (flags.bit(W_NORMALIZED)) {
+ WRONG_WORD.error("WERD::baseline_normalize", ABORT,
+ "Baseline unnormalized");
}
- else {
- mean_x = x_height;
- x_count = 1;
- segs = new DENORM_SEG[blob_it.length ()];
- segments = 0;
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
- blob_it.forward ()) {
- blob = blob_it.data ();
- blob_box = blob->bounding_box ();
- if (blob_box.height () > bln_blshift_xfraction * x_height) {
- blob_x_centre = blob_box.left () +
- (blob_box.right () - blob_box.left ()) / 2.0;
- blob_offset =
- blob_box.bottom () - row->base_line (blob_x_centre);
- top_offset = blob_offset + blob_box.height () - x_height - 1;
- blob_x_height = top_offset + x_height;
- if (top_offset < 0)
- top_offset = -top_offset;
- if (blob_offset < 0)
- blob_offset = -blob_offset;
- if (blob_offset < bln_blshift_maxshift * x_height) {
- segs[segments].ycoord = blob_box.bottom ();
- line.add (blob_x_centre, blob_box.bottom ());
- if (top_offset < bln_blshift_maxshift * x_height) {
- segs[segments].scale_factor = blob_box.height () - 1.0f;
- x_count++;
- }
- else
- segs[segments].scale_factor = 0.0f;
- //fix it later
- }
- else {
- //not a goer
- segs[segments].ycoord = -MAX_INT32;
- if (top_offset < bln_blshift_maxshift * x_height) {
- segs[segments].scale_factor = blob_x_height;
- x_count++;
- }
- else
- segs[segments].scale_factor = 0.0f;
- //fix it later
- }
- }
- else {
- segs[segments].scale_factor = 0.0f;
- segs[segments].ycoord = -MAX_INT32;
- }
- segs[segments].xstart = blob_box.left ();
- segments++;
- }
- using_row = line.count () <= 1;
- if (!using_row) {
- line_m = line.m ();
- line_c = line.c (line_m);
- }
- else
- line_m = line_c = 0;
- segments = 0;
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
- blob_it.forward ()) {
- blob = blob_it.data ();
- blob_box = blob->bounding_box ();
- blob_x_centre = blob_box.left () +
- (blob_box.right () - blob_box.left ()) / 2.0;
- if (segs[segments].ycoord == -MAX_INT32
- && segs[segments].scale_factor != 0 && !using_row) {
- blob_offset = line_m * blob_x_centre + line_c;
- segs[segments].scale_factor = blob_box.top () - blob_offset;
- }
- if (segs[segments].scale_factor != 0)
- mean_x += segs[segments].scale_factor;
- segments++;
- }
- mean_x /= x_count;
- // printf("mean x=%g, count=%d, line_m=%g, line_c=%g\n",
- // mean_x,x_count,line_m,line_c);
- segments = 0;
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
- blob_it.forward ()) {
- blob = blob_it.data ();
- blob_box = blob->bounding_box ();
- blob_x_centre = blob_box.left () +
- (blob_box.right () - blob_box.left ()) / 2.0;
- if (segs[segments].ycoord != -MAX_INT32)
- blob_offset = (float) segs[segments].ycoord;
- else if (using_row)
- blob_offset = row->base_line (blob_x_centre);
- else
- blob_offset = line_m * blob_x_centre + line_c;
- if (segs[segments].scale_factor == 0)
- segs[segments].scale_factor = mean_x;
- segs[segments].scale_factor =
- bln_x_height / segs[segments].scale_factor;
- // printf("Blob sf=%g, top=%d, bot=%d, base=%g\n",
- // segs[segments].scale_factor,blob_box.top(),
- // blob_box.bottom(),blob_offset);
- blob->move (FCOORD (-antidote.origin (), -blob_offset));
- blob->
- scale (FCOORD (antidote.scale (), segs[segments].scale_factor));
- blob->move (FCOORD (0.0, bln_baseline_offset));
- segments++;
- }
- //Repeat for rej blobs
- blob_it.set_to_list ((PBLOB_LIST *) & rej_cblobs);
- segment = 0;
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
- blob_it.forward ()) {
- blob = blob_it.data ();
- blob_box = blob->bounding_box ();
- blob_x_centre = blob_box.left () +
- (blob_box.right () - blob_box.left ()) / 2.0;
- while (segment < segments - 1
- && segs[segment + 1].xstart <= blob_x_centre)
- segment++;
- if (segs[segment].ycoord != -MAX_INT32)
- blob_offset = (float) segs[segment].ycoord;
- else if (using_row)
- blob_offset = row->base_line (blob_x_centre);
- else
- blob_offset = line_m * blob_x_centre + line_c;
- blob->move (FCOORD (-antidote.origin (), -blob_offset));
- blob->
- scale (FCOORD (antidote.scale (), segs[segment].scale_factor));
- blob->move (FCOORD (0.0, bln_baseline_offset));
- }
- if (line.count () > 0 || x_count > 1)
- antidote = DENORM (antidote.origin (), antidote.scale (),
- line_m, line_c, segments, segs, using_row, row);
- delete[]segs;
+ if (numeric_mode) {
+ baseline_normalize_numerals(word_box, (PBLOB_LIST *)&cblobs, &antidote,
+ TRUE);
+ baseline_normalize_numerals(word_box, (PBLOB_LIST *)&rej_cblobs, &antidote,
+ FALSE);
+ } else {
+ baseline_normalize_text(antidote, (PBLOB_LIST *)&cblobs);
+ baseline_normalize_text(antidote, (PBLOB_LIST *)&rej_cblobs);
}
if (denorm != NULL)
*denorm = antidote;
- //it's normalised
- flags.set_bit (W_NORMALIZED, TRUE);
+
+ flags.set_bit(W_NORMALIZED, TRUE);
}
/**
- * WERD::baseline_denormalise
+ * WERD::baseline_denormalize
*
- * Baseline DeNormalise the word in Tesseract style. (I.e origin at centre of
- * word at bottom. x-height region scaled to region y =
- * (bln_baseline_offset)..(bln_baseline_offset + bln_x_height)
- * - usually 64..192)
+ * Return a normalized word to its original coordinates.
*/
-void WERD::baseline_denormalise( // Tess style BL Norm
- const DENORM *denorm //< antidote
- ) {
- PBLOB_IT blob_it ((PBLOB_LIST *) & cblobs);
- // blob iterator
+void WERD::baseline_denormalize(const DENORM *antidote) {
+ PBLOB_IT blob_it((PBLOB_LIST *)&cblobs);
PBLOB *blob;
- if (!flags.bit (W_NORMALIZED)) {
- WRONG_WORD.error ("WERD::baseline_denormalise", ABORT,
- "Baseline normalised");
+ if (!flags.bit(W_NORMALIZED)) {
+ WRONG_WORD.error("WERD::baseline_denormalize", ABORT,
+ "Baseline normalized");
}
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
- blob = blob_it.data ();
- //denormalise it
- blob->baseline_denormalise (denorm);
+ for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
+ blob = blob_it.data();
+ blob->baseline_denormalise(antidote);
}
- //Repeat for rej blobs
- blob_it.set_to_list ((PBLOB_LIST *) & rej_cblobs);
- for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
- blob = blob_it.data ();
- //denormalise it
- blob->baseline_denormalise (denorm);
+ // Repeat for rejected blobs
+ blob_it.set_to_list((PBLOB_LIST *)&rej_cblobs);
+ for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
+ blob = blob_it.data();
+ blob->baseline_denormalise(antidote);
}
- //it's not normalised
- flags.set_bit (W_NORMALIZED, FALSE);
+ flags.set_bit(W_NORMALIZED, FALSE);
}
@@ -750,31 +562,28 @@ void WERD::baseline_denormalise( // Tess style BL Norm
* Display members
*/
-void WERD::print( //print
- FILE * //< file to print on
- ) {
- tprintf ("Blanks= %d\n", blanks);
- bounding_box ().print ();
- tprintf ("Flags = %d = 0%o\n", flags.val, flags.val);
- tprintf (" W_SEGMENTED = %s\n",
- flags.bit (W_SEGMENTED) ? "TRUE" : "FALSE ");
- tprintf (" W_ITALIC = %s\n", flags.bit (W_ITALIC) ? "TRUE" : "FALSE ");
- tprintf (" W_BOL = %s\n", flags.bit (W_BOL) ? "TRUE" : "FALSE ");
- tprintf (" W_EOL = %s\n", flags.bit (W_EOL) ? "TRUE" : "FALSE ");
- tprintf (" W_NORMALIZED = %s\n",
- flags.bit (W_NORMALIZED) ? "TRUE" : "FALSE ");
- tprintf (" W_POLYGON = %s\n", flags.bit (W_POLYGON) ? "TRUE" : "FALSE ");
- tprintf (" W_LINEARC = %s\n", flags.bit (W_LINEARC) ? "TRUE" : "FALSE ");
- tprintf (" W_DONT_CHOP = %s\n",
- flags.bit (W_DONT_CHOP) ? "TRUE" : "FALSE ");
- tprintf (" W_REP_CHAR = %s\n",
- flags.bit (W_REP_CHAR) ? "TRUE" : "FALSE ");
- tprintf (" W_FUZZY_SP = %s\n",
- flags.bit (W_FUZZY_SP) ? "TRUE" : "FALSE ");
- tprintf (" W_FUZZY_NON = %s\n",
- flags.bit (W_FUZZY_NON) ? "TRUE" : "FALSE ");
- tprintf ("Correct= %s\n", correct.string ());
- tprintf ("Rejected cblob count = %d\n", rej_cblobs.length ());
+void WERD::print() {
+ tprintf("Blanks= %d\n", blanks);
+ bounding_box().print();
+ tprintf("Flags = %d = 0%o\n", flags.val, flags.val);
+ tprintf(" W_SEGMENTED = %s\n", flags.bit(W_SEGMENTED) ? "TRUE" : "FALSE ");
+ tprintf(" W_ITALIC = %s\n", flags.bit(W_ITALIC) ? "TRUE" : "FALSE ");
+ tprintf(" W_BOL = %s\n", flags.bit(W_BOL) ? "TRUE" : "FALSE ");
+ tprintf(" W_EOL = %s\n", flags.bit(W_EOL) ? "TRUE" : "FALSE ");
+ tprintf(" W_NORMALIZED = %s\n",
+ flags.bit(W_NORMALIZED) ? "TRUE" : "FALSE ");
+ tprintf(" W_POLYGON = %s\n", flags.bit(W_POLYGON) ? "TRUE" : "FALSE ");
+ tprintf(" W_SCRIPT_HAS_XHEIGHT = %s\n",
+ flags.bit(W_SCRIPT_HAS_XHEIGHT) ? "TRUE" : "FALSE ");
+ tprintf(" W_SCRIPT_IS_LATIN = %s\n",
+ flags.bit(W_SCRIPT_IS_LATIN) ? "TRUE" : "FALSE ");
+ tprintf(" W_DONT_CHOP = %s\n", flags.bit(W_DONT_CHOP) ? "TRUE" : "FALSE ");
+ tprintf(" W_REP_CHAR = %s\n", flags.bit(W_REP_CHAR) ? "TRUE" : "FALSE ");
+ tprintf(" W_FUZZY_SP = %s\n", flags.bit(W_FUZZY_SP) ? "TRUE" : "FALSE ");
+ tprintf(" W_FUZZY_NON = %s\n", flags.bit(W_FUZZY_NON) ? "TRUE" : "FALSE ");
+ tprintf("Correct= %s\n", correct.string());
+ tprintf("Rejected cblob count = %d\n", rej_cblobs.length());
+ tprintf("Script = %d\n", script_id_);
}
@@ -785,86 +594,55 @@ void WERD::print( //print
*/
#ifndef GRAPHICS_DISABLED
-void WERD::plot( //draw it
- ScrollView* window, //window to draw in
- ScrollView::Color colour, //colour to draw in
- BOOL8 solid //draw larcs solid
- ) {
- if (flags.bit (W_POLYGON)) {
- //polygons
- PBLOB_IT it = (PBLOB_LIST *) (&cblobs);
-
- for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
- it.data ()->plot (window, colour, colour);
+void WERD::plot(ScrollView *window, ScrollView::Color colour) {
+ if (flags.bit(W_POLYGON)) { // polygon blobs
+ PBLOB_IT it = (PBLOB_LIST *)(&cblobs);
+ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
+ it.data()->plot(window, colour, colour);
}
- }
- // else if (flags.bit(W_LINEARC))
- // {
- // LARC_BLOB_IT it=(LARC_BLOB_LIST*)(&cblobs);
-
- // for ( it.mark_cycle_pt(); !it.cycled_list(); it.forward() )
- // {
- // it.data()->plot(window,solid,colour,solid ? BLACK : colour);
- // }
- // }
- else {
- C_BLOB_IT it = &cblobs; //blobs of WERD
-
- for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
- it.data ()->plot (window, colour, colour);
+ } else { // chain code blobs
+ C_BLOB_IT it = &cblobs;
+ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
+ it.data()->plot(window, colour, colour);
}
}
- plot_rej_blobs(window, solid);
+ plot_rej_blobs(window);
}
#endif
+#ifndef GRAPHICS_DISABLED
+
+// Get the next color in the (looping) rainbow.
+ScrollView::Color WERD::NextColor(ScrollView::Color colour) {
+ ScrollView::Color next = static_cast(colour + 1);
+ if (next >= LAST_COLOUR || next < FIRST_COLOUR)
+ next = FIRST_COLOUR;
+ return next;
+}
+
/**
* WERD::plot
*
- * Draw the WERD in rainbow colours.
+ * Draw the WERD in rainbow colours in window.
*/
-#ifndef GRAPHICS_DISABLED
-void WERD::plot( //draw it
- ScrollView* window, //< window to draw in
- BOOL8 solid //< draw larcs solid
- ) {
- ScrollView::Color colour = FIRST_COLOUR; //current colour
- if (flags.bit (W_POLYGON)) {
- //polygons
- PBLOB_IT it = (PBLOB_LIST *) (&cblobs);
-
- for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
- it.data ()->plot (window, colour, CHILD_COLOUR);
- colour = (ScrollView::Color) (colour + 1);
- if (colour == LAST_COLOUR)
- colour = FIRST_COLOUR; //cycle round
+void WERD::plot(ScrollView* window) {
+ ScrollView::Color colour = FIRST_COLOUR;
+ if (flags.bit(W_POLYGON)) {
+ PBLOB_IT it = (PBLOB_LIST *)(&cblobs);
+ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
+ it.data()->plot(window, colour, CHILD_COLOUR);
+ colour = NextColor(colour);
}
- }
- // else if (flags.bit(W_LINEARC))
- // {
- // LARC_BLOB_IT it=(LARC_BLOB_LIST*)(&cblobs);
-
- // for ( it.mark_cycle_pt(); !it.cycled_list(); it.forward() )
- // {
- // it.data()->plot(window,solid,colour,solid ? BLACK : CHILD_COLOUR);
- // colour=(COLOUR)(colour+1);
- // if (colour==LAST_COLOUR)
- // colour=FIRST_COLOUR;
- // }
- // }
- else {
- C_BLOB_IT it = &cblobs; //blobs of WERD
-
- for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
- it.data ()->plot (window, colour, CHILD_COLOUR);
- colour = (ScrollView::Color) (colour + 1);
- if (colour == LAST_COLOUR)
- colour = FIRST_COLOUR; //cycle round
+ } else {
+ C_BLOB_IT it = &cblobs;
+ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
+ it.data()->plot(window, colour, CHILD_COLOUR);
+ colour = NextColor(colour);
}
}
- plot_rej_blobs(window, solid);
+ plot_rej_blobs(window);
}
#endif
@@ -872,26 +650,20 @@ void WERD::plot( //draw it
/**
* WERD::plot_rej_blobs
*
- * Draw the WERD rejected blobs - ALWAYS GREY
+ * Draw the WERD rejected blobs in window - ALWAYS GREY
*/
#ifndef GRAPHICS_DISABLED
-void WERD::plot_rej_blobs( //draw it
- ScrollView* window, //< window to draw in
- BOOL8 solid //< draw larcs solid
- ) {
- if (flags.bit (W_POLYGON)) {
- PBLOB_IT it = (PBLOB_LIST *) (&rej_cblobs);
- //polygons
-
- for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
- it.data ()->plot (window, ScrollView::GREY, ScrollView::GREY);
+void WERD::plot_rej_blobs(ScrollView *window) {
+ if (flags.bit(W_POLYGON)) {
+ PBLOB_IT it = (PBLOB_LIST *)(&rej_cblobs);
+ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
+ it.data()->plot(window, ScrollView::GREY, ScrollView::GREY);
}
} else {
- C_BLOB_IT it = &rej_cblobs; //blobs of WERD
-
- for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
- it.data ()->plot (window, ScrollView::GREY, ScrollView::GREY);
+ C_BLOB_IT it = &rej_cblobs;
+ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
+ it.data()->plot(window, ScrollView::GREY, ScrollView::GREY);
}
}
}
@@ -904,7 +676,7 @@ void WERD::plot_rej_blobs( //draw it
* Make a shallow copy of a word
*/
-WERD *WERD::shallow_copy() { //shallow copy
+WERD *WERD::shallow_copy() {
WERD *new_word = new WERD;
new_word->blanks = blanks;
@@ -921,15 +693,14 @@ WERD *WERD::shallow_copy() { //shallow copy
* Assign a word, DEEP copying the blob list
*/
-WERD & WERD::operator= ( //assign words
-const WERD & source //from this
-) {
- this->ELIST_LINK::operator= (source);
+WERD & WERD::operator= (const WERD & source) {
+ this->ELIST2_LINK::operator= (source);
blanks = source.blanks;
flags = source.flags;
+ script_id_ = source.script_id_;
dummy = source.dummy;
correct = source.correct;
- if (flags.bit (W_POLYGON)) {
+ if (flags.bit(W_POLYGON)) {
if (!cblobs.empty())
reinterpret_cast(&cblobs)->clear();
reinterpret_cast(&cblobs)->deep_copy(
@@ -941,12 +712,12 @@ const WERD & source //from this
reinterpret_cast(&source.rej_cblobs),
&PBLOB::deep_copy);
} else {
- if (!cblobs.empty ())
- cblobs.clear ();
+ if (!cblobs.empty())
+ cblobs.clear();
cblobs.deep_copy(&source.cblobs, &C_BLOB::deep_copy);
- if (!rej_cblobs.empty ())
- rej_cblobs.clear ();
+ if (!rej_cblobs.empty())
+ rej_cblobs.clear();
rej_cblobs.deep_copy(&source.rej_cblobs, &C_BLOB::deep_copy);
}
return *this;
@@ -960,14 +731,107 @@ const WERD & source //from this
* order of left edge.
*/
-int word_comparator( //sort blobs
- const void *word1p, //< ptr to ptr to word1
- const void *word2p //< ptr to ptr to word2
- ) {
- WERD *
- word1 = *(WERD **) word1p;
- WERD *
- word2 = *(WERD **) word2p;
+int word_comparator(const void *word1p, const void *word2p) {
+ WERD *word1 = *(WERD **)word1p;
+ WERD *word2 = *(WERD **)word2p;
+ return word1->bounding_box().left() - word2->bounding_box().left();
+}
- return word1->bounding_box ().left () - word2->bounding_box ().left ();
+/**
+ * WERD::ConstructWerdWithNewBlobs()
+ *
+ * This method returns a new werd constructed using the blobs in the input
+ * all_blobs list, which correspond to the blobs in this werd object. The
+ * blobs used to construct the new word are consumed and removed from the
+ * input all_blobs list.
+ * Returns NULL if the word couldn't be constructed.
+ * Returns original blobs for which no matches were found in the output list
+ * orphan_blobs (appends).
+ */
+
+WERD* WERD::ConstructWerdWithNewBlobs(C_BLOB_LIST* all_blobs,
+ C_BLOB_LIST* orphan_blobs) {
+ C_BLOB_LIST current_blob_list;
+ C_BLOB_IT werd_blobs_it(¤t_blob_list);
+ // Add the word's c_blobs.
+ werd_blobs_it.add_list_after(cblob_list());
+
+ // New blob list. These contain the blobs which will form the new word.
+ C_BLOB_LIST new_werd_blobs;
+ C_BLOB_IT new_blobs_it(&new_werd_blobs);
+
+ // not_found_blobs contains the list of current word's blobs for which a
+ // corresponding blob wasn't found in the input all_blobs list.
+ C_BLOB_LIST not_found_blobs;
+ C_BLOB_IT not_found_it(¬_found_blobs);
+ not_found_it.move_to_last();
+
+ werd_blobs_it.move_to_first();
+ for (werd_blobs_it.mark_cycle_pt(); !werd_blobs_it.cycled_list();
+ werd_blobs_it.forward()) {
+ C_BLOB* werd_blob = werd_blobs_it.extract();
+ TBOX werd_blob_box = werd_blob->bounding_box();
+ bool found = false;
+ // Now find the corresponding blob for this blob in the all_blobs
+ // list. For now, follow the inefficient method of pairwise
+ // comparisons. Ideally, one can pre-bucket the blobs by row.
+ C_BLOB_IT all_blobs_it(all_blobs);
+ for (all_blobs_it.mark_cycle_pt(); !all_blobs_it.cycled_list();
+ all_blobs_it.forward()) {
+ C_BLOB* a_blob = all_blobs_it.data();
+ // Compute the overlap of the two blobs. If major, a_blob should
+ // be added to the new blobs list.
+ TBOX a_blob_box = a_blob->bounding_box();
+ if (a_blob_box.null_box()) {
+ tprintf("Bounding box couldn't be ascertained\n");
+ }
+ if (werd_blob_box.contains(a_blob_box) ||
+ werd_blob_box.major_overlap(a_blob_box)) {
+ // Old blobs are from minimal splits, therefore are expected to be
+ // bigger. The new small blobs should cover a significant portion.
+ // This is it.
+ all_blobs_it.extract();
+ new_blobs_it.add_after_then_move(a_blob);
+ found = true;
+ }
+ }
+ if (!found) {
+ not_found_it.add_after_then_move(werd_blob);
+ }
+ }
+ // Iterate over all not found blobs. Some of them may be due to
+ // under-segmentation (which is OK, since the corresponding blob is already
+ // in the list in that case.
+ not_found_it.move_to_first();
+ for (not_found_it.mark_cycle_pt(); !not_found_it.cycled_list();
+ not_found_it.forward()) {
+ C_BLOB* not_found = not_found_it.data();
+ TBOX not_found_box = not_found->bounding_box();
+ bool found = false;
+ C_BLOB_IT existing_blobs_it(new_blobs_it);
+ for (existing_blobs_it.mark_cycle_pt(); !existing_blobs_it.cycled_list();
+ existing_blobs_it.forward()) {
+ C_BLOB* a_blob = existing_blobs_it.data();
+ TBOX a_blob_box = a_blob->bounding_box();
+ if ((not_found_box.major_overlap(a_blob_box) ||
+ a_blob_box.major_overlap(not_found_box)) &&
+ not_found_box.y_overlap(a_blob_box) > 0.8) {
+ // Already taken care of.
+ found = true;
+ not_found_it.extract();
+ }
+ }
+ }
+ if (orphan_blobs) {
+ C_BLOB_IT orphan_blobs_it(orphan_blobs);
+ orphan_blobs_it.move_to_last();
+ orphan_blobs_it.add_list_after(¬_found_blobs);
+ }
+
+ // New blobs are ready. Create a new werd object with these.
+ WERD* new_werd = NULL;
+ if (!new_werd_blobs.empty()) {
+ new_werd = new WERD(&new_werd_blobs, this);
+ }
+ return new_werd;
}
diff --git a/ccstruct/werd.h b/ccstruct/werd.h
index eff12267a8..47b3248f56 100644
--- a/ccstruct/werd.h
+++ b/ccstruct/werd.h
@@ -20,13 +20,12 @@
#ifndef WERD_H
#define WERD_H
-#include "varable.h"
+#include "params.h"
#include "bits16.h"
#include "strngs.h"
#include "blckerr.h"
#include "stepblob.h"
#include "polyblob.h"
-//#include "larcblob.h"
enum WERD_FLAGS
{
@@ -37,7 +36,8 @@ enum WERD_FLAGS
W_EOL, //< end of line
W_NORMALIZED, //< flags
W_POLYGON, //< approximation
- W_LINEARC, //< linearc approx
+ W_SCRIPT_HAS_XHEIGHT, //< x-height concept makes sense.
+ W_SCRIPT_IS_LATIN, //< Special case latin for y. splitting.
W_DONT_CHOP, //< fixed pitch chopped
W_REP_CHAR, //< repeated character
W_FUZZY_SP, //< fuzzy space
@@ -57,221 +57,185 @@ enum DISPLAY_FLAGS
class ROW; //forward decl
-class WERD:public ELIST_LINK
-{
+class WERD : public ELIST2_LINK {
public:
- WERD() {
- } //empty constructor
- WERD( //constructor
- C_BLOB_LIST *blob_list, //blobs in word
- uinT8 blanks, //blanks in front
- const char *text); //correct text
- WERD( //constructor
- PBLOB_LIST *blob_list, //blobs in word
- uinT8 blanks, //blanks in front
- const char *text); //correct text
- WERD( //constructor
- PBLOB_LIST *blob_list, //blobs in word
- WERD *clone); //use these flags etc.
- WERD( //constructor
- C_BLOB_LIST *blob_list, //blobs in word
- WERD *clone); //use these flags etc.
- ~WERD () { //destructor
- if (flags.bit (W_POLYGON)) {
- //use right destructor
- ((PBLOB_LIST *) & cblobs)->clear ();
- //use right destructor
- ((PBLOB_LIST *) & rej_cblobs)->clear ();
+ WERD() {}
+ // WERD constructed with:
+ // blob_list - blobs of the word (we take this list's contents)
+ // blanks - number of blanks before the word
+ // text - correct text (outlives WERD)
+ WERD(C_BLOB_LIST *blob_list, uinT8 blanks, const char *text);
+ WERD(PBLOB_LIST *blob_list, uinT8 blanks, const char *text);
+
+ // WERD constructed from:
+ // blob_list - blobs in the word
+ // clone - werd to clone flags, etc from.
+ WERD(PBLOB_LIST *blob_list, WERD *clone);
+ WERD(C_BLOB_LIST *blob_list, WERD *clone);
+
+ // Construct a WERD from a single_blob and clone the flags from this.
+ // W_BOL and W_EOL flags are set according to the given values.
+ WERD* ConstructFromSingleBlob(bool bol, bool eol, C_BLOB* blob);
+
+ ~WERD() {
+ if (flags.bit(W_POLYGON)) {
+ // use right destructor for PBLOBs
+ ((PBLOB_LIST *) &cblobs)->clear();
+ ((PBLOB_LIST *) &rej_cblobs)->clear();
}
- // else if (flags.bit(W_LINEARC))
- // ((LARC_BLOB_LIST*)&cblobs)->clear(); //use right destructor
}
- WERD *poly_copy( //make copy as poly
- float xheight); //row xheight
- WERD *larc_copy( //make copy as larc
- float xheight); //row xheight
+ // assignment
+ WERD & operator= (const WERD &source);
+
+ // This method returns a new werd constructed using the blobs in the input
+ // all_blobs list, which correspond to the blobs in this werd object. The
+ // blobs used to construct the new word are consumed and removed from the
+ // input all_blobs list.
+ // Returns NULL if the word couldn't be constructed.
+ // Returns original blobs for which no matches were found in the output list
+ // orphan_blobs (appends).
+ WERD *ConstructWerdWithNewBlobs(C_BLOB_LIST *all_blobs,
+ C_BLOB_LIST *orphan_blobs);
+
+ WERD *poly_copy(); // make a copy
- //get DUFF compact blobs
- C_BLOB_LIST *rej_cblob_list() {
- if (flags.bit (W_POLYGON))
- WRONG_WORD.error ("WERD::rej_cblob_list", ABORT, NULL);
+ // Accessors for reject / DUFF blobs in various formats
+ C_BLOB_LIST *rej_cblob_list() { // compact format
+ if (flags.bit(W_POLYGON))
+ WRONG_WORD.error("WERD::rej_cblob_list", ABORT, NULL);
return &rej_cblobs;
}
-
- //get DUFF poly blobs
- PBLOB_LIST *rej_blob_list() {
- if (!flags.bit (W_POLYGON))
- WRONG_WORD.error ("WERD::rej_blob_list", ABORT, NULL);
- return (PBLOB_LIST *) (&rej_cblobs);
+ PBLOB_LIST *rej_blob_list() { // poly format
+ if (!flags.bit(W_POLYGON))
+ WRONG_WORD.error("WERD::rej_blob_list", ABORT, NULL);
+ return (PBLOB_LIST *)(&rej_cblobs);
}
- C_BLOB_LIST *cblob_list() { //get compact blobs
- if (flags.bit (W_POLYGON) || flags.bit (W_LINEARC))
- WRONG_WORD.error ("WERD::cblob_list", ABORT, NULL);
+ // Accessors for good blobs in various formats.
+ C_BLOB_LIST *cblob_list() { // get compact blobs
+ if (flags.bit(W_POLYGON))
+ WRONG_WORD.error("WERD::cblob_list", ABORT, NULL);
return &cblobs;
}
- PBLOB_LIST *blob_list() { //get poly blobs
- if (!flags.bit (W_POLYGON))
- WRONG_WORD.error ("WERD::blob_list", ABORT, NULL);
- //make it right type
- return (PBLOB_LIST *) (&cblobs);
+ PBLOB_LIST *blob_list() { // get poly blobs
+ if (!flags.bit(W_POLYGON))
+ WRONG_WORD.error("WERD::blob_list", ABORT, NULL);
+ return (PBLOB_LIST *)(&cblobs);
}
- // LARC_BLOB_LIST *larc_blob_list() //get poly blobs
- // {
- // if (!flags.bit(W_LINEARC))
- // WRONG_WORD.error("WERD::larc_blob_list",ABORT,NULL);
- // return (LARC_BLOB_LIST*)(&cblobs); //make it right type
- // }
- PBLOB_LIST *gblob_list() { //get generic blobs
- //make it right type
- return (PBLOB_LIST *) (&cblobs);
+ PBLOB_LIST *gblob_list() { // get generic blobs
+ return (PBLOB_LIST *)(&cblobs);
}
- const char *text() const { //correct text
- return correct.string ();
- }
- uinT8 space() { //access function
+ uinT8 space() { // access function
return blanks;
}
- void set_blanks( //set blanks
- uinT8 new_blanks) {
+ void set_blanks(uinT8 new_blanks) {
blanks = new_blanks;
}
-
- void set_text( //replace correct text
- const char *new_text) { //with this
- correct = new_text;
+ int script_id() const {
+ return script_id_;
+ }
+ void set_script_id(int id) {
+ script_id_ = id;
}
- TBOX bounding_box(); //compute bounding box
+ TBOX bounding_box(); // compute bounding box
- BOOL8 flag( //test flag
- WERD_FLAGS mask) const { //flag to test
- return flags.bit (mask);
- }
- void set_flag( //set flag value
- WERD_FLAGS mask, //flag to test
- BOOL8 value) { //value to set
- flags.set_bit (mask, value);
- }
+ const char *text() const { return correct.string(); }
+ void set_text(const char *new_text) { correct = new_text; }
- BOOL8 display_flag( //test display flag
- uinT8 flag) const { //flag to test
- return disp_flags.bit (flag);
- }
+ BOOL8 flag(WERD_FLAGS mask) const { return flags.bit(mask); }
+ void set_flag(WERD_FLAGS mask, BOOL8 value) { flags.set_bit(mask, value); }
- void set_display_flag( //set display flag
- uinT8 flag, //flag to set
- BOOL8 value) { //value to set
- disp_flags.set_bit (flag, value);
+ BOOL8 display_flag(uinT8 flag) const { return disp_flags.bit(flag); }
+ void set_display_flag(uinT8 flag, BOOL8 value) {
+ disp_flags.set_bit(flag, value);
}
- WERD *shallow_copy(); //shallow copy word
-
- void move( // reposition word
- const ICOORD vec); // by vector
+ WERD *shallow_copy(); // shallow copy word
- void scale( // scale word
- const float vec); // by multiplier
+ // reposition word by vector
+ void move(const ICOORD vec);
- void join_on( //append word
- WERD *&other); //Deleting other
+ // scale word by multiplier
+ void scale(const float f);
- void copy_on( //copy blobs
- WERD *&other); //from other
+ // join other's blobs onto this werd, emptying out other.
+ void join_on(WERD* other);
- void baseline_normalise ( // Tess style BL Norm
- //optional antidote
- ROW * row, DENORM * denorm = NULL);
+ // copy other's blobs onto this word, leaving other intact.
+ void copy_on(WERD* other);
- void baseline_normalise_x ( //Use non standard xht
- ROW * row, float x_height, //Weird value to use
- DENORM * denorm = NULL); //optional antidote
+ // Normalize a word to tesseract coordinates, (x centered at 0, y between
+ // (bln_baseline_offset)..(bln_baseline_offset + bln_x_height)
+ // - usually 64..192)
+ // Optionally return an antidote (denorm) to undo this normalization.
+ // If xheight is given, we use that instead of row's xheight.
+ void baseline_normalize(ROW *row, DENORM *denorm, bool numeric_mode);
+ void baseline_normalize_x(ROW *row, float x_height,
+ DENORM *denorm, bool numeric_mode);
- void baseline_denormalise( //un-normalise
- const DENORM *denorm);
+ // return word to original coordinates
+ void baseline_denormalize(const DENORM *antidote);
- void print( //print
- FILE *fp); //file to print on
+ // tprintf word metadata (but not blob innards)
+ void print();
- void plot ( //draw one
- ScrollView* window, //window to draw in
- //uniform colour
- ScrollView::Color colour, BOOL8 solid = FALSE);
+ // plot word on window in a uniform colour
+ void plot(ScrollView *window, ScrollView::Color colour);
- void plot ( //draw one
- //in rainbow colours
- ScrollView* window, BOOL8 solid = FALSE);
+ // Get the next color in the (looping) rainbow.
+ static ScrollView::Color NextColor(ScrollView::Color colour);
- void plot_rej_blobs ( //draw one
- //in rainbow colours
- ScrollView* window, BOOL8 solid = FALSE);
+ // plot word on window in a rainbow of colours
+ void plot(ScrollView *window);
- WERD & operator= ( //assign words
- const WERD & source); //from this
+ // plot rejected blobs in a rainbow of colours
+ void plot_rej_blobs(ScrollView *window);
- void prep_serialise() { //set ptrs to counts
- correct.prep_serialise ();
- if (flags.bit (W_POLYGON))
- ((PBLOB_LIST *) (&cblobs))->prep_serialise ();
- // else if (flags.bit(W_LINEARC))
- // ((LARC_BLOB_LIST*)(&cblobs))->prep_serialise();
+ void prep_serialise() { // set ptrs to counts
+ correct.prep_serialise();
+ if (flags.bit(W_POLYGON))
+ ((PBLOB_LIST *)(&cblobs))->prep_serialise();
else
- cblobs.prep_serialise ();
- rej_cblobs.prep_serialise ();
+ cblobs.prep_serialise();
+ rej_cblobs.prep_serialise();
}
- void dump( //write external bits
- FILE *f) {
- correct.dump (f);
- if (flags.bit (W_POLYGON))
- ((PBLOB_LIST *) (&cblobs))->dump (f);
- // else if (flags.bit(W_LINEARC))
- // ((LARC_BLOB_LIST*)(&cblobs))->dump( f );
+ // write external bits
+ void dump(FILE *f) {
+ correct.dump(f);
+ if (flags.bit(W_POLYGON))
+ ((PBLOB_LIST *)(&cblobs))->dump(f);
else
- cblobs.dump (f);
- rej_cblobs.dump (f);
+ cblobs.dump(f);
+ rej_cblobs.dump(f);
}
- void de_dump( //read external bits
- FILE *f) {
- correct.de_dump (f);
- if (flags.bit (W_POLYGON))
- ((PBLOB_LIST *) (&cblobs))->de_dump (f);
- // else if (flags.bit(W_LINEARC))
- // ((LARC_BLOB_LIST*)(&cblobs))->de_dump( f );
+ // read external bits
+ void de_dump(FILE *f) {
+ correct.de_dump(f);
+ if (flags.bit(W_POLYGON))
+ ((PBLOB_LIST *)(&cblobs))->de_dump(f);
else
- cblobs.de_dump (f);
- rej_cblobs.de_dump (f);
+ cblobs.de_dump(f);
+ rej_cblobs.de_dump(f);
}
make_serialise (WERD) private:
- uinT8 blanks; //no of blanks
- uinT8 dummy; //padding
- BITS16 flags; //flags about word
- BITS16 disp_flags; //display flags
- inT16 dummy2; //padding
- STRING correct; //correct text
- C_BLOB_LIST cblobs; //compacted blobs
- C_BLOB_LIST rej_cblobs; //DUFF blobs
+ uinT8 blanks; // no of blanks
+ uinT8 dummy; // padding
+ BITS16 flags; // flags about word
+ BITS16 disp_flags; // display flags
+ inT16 script_id_; // From unicharset.
+ STRING correct; // correct text
+ C_BLOB_LIST cblobs; // compacted blobs
+ C_BLOB_LIST rej_cblobs; // DUFF blobs
};
-ELISTIZEH_S (WERD)
-#include "ocrrow.h" //placed here due to
-extern BOOL_VAR_H (bln_numericmode, 0, "Optimize for numbers");
-extern INT_VAR_H (bln_x_height, 128, "Baseline Normalisation X-height");
-extern INT_VAR_H (bln_baseline_offset, 64,
-"Baseline Norm. offset of baseline");
-//void poly_linearc_outlines( //do list of outlines
-//LARC_OUTLINE_LIST *srclist, //list to convert
-//OUTLINE_LIST *destlist //desstination list
-//);
-//OUTLINE *poly_larcline( //draw it
-//LARC_OUTLINE *srcline //one to approximate
-//);
-int word_comparator( //sort blobs
- const void *word1p, //ptr to ptr to word1
- const void *word2p //ptr to ptr to word2
- );
+ELIST2IZEH_S (WERD)
+#include "ocrrow.h" // placed here due to
+// compare words by increasing order of left edge, suitable for qsort(3)
+int word_comparator(const void *word1p, const void *word2p);
#endif
diff --git a/ccutil/Makefile.am b/ccutil/Makefile.am
index 0d4c289062..31cde300b3 100644
--- a/ccutil/Makefile.am
+++ b/ccutil/Makefile.am
@@ -1,21 +1,21 @@
SUBDIRS =
AM_CXXFLAGS = -DTESSDATA_PREFIX=@datadir@/
-EXTRA_DIST = ccutil.vcproj mfcpch.cpp scanutils.cpp scanutils.h
+EXTRA_DIST = mfcpch.cpp scanutils.cpp scanutils.h
include_HEADERS = \
ambigs.h basedir.h bits16.h boxread.h \
- callback.h ccutil.h clst.h \
+ tesscallback.h ccutil.h clst.h \
debugwin.h elst2.h elst.h errcode.h \
fileerr.h genericvector.h globaloc.h \
hashfn.h helpers.h host.h hosthplb.h lsterr.h \
- mainblk.h memblk.h memry.h memryerr.h mfcpch.h \
+ memblk.h memry.h memryerr.h mfcpch.h \
ndminx.h notdll.h nwmain.h \
- ocrclass.h ocrshell.h platform.h qrsequence.h \
- secname.h serialis.h stderr.h strngs.h scanutils.h \
- tessclas.h tessdatamanager.h tessopt.h tordvars.h tprintf.h \
+ ocrclass.h platform.h qrsequence.h \
+ secname.h serialis.h sorthelper.h stderr.h strngs.h \
+ tessdatamanager.h tprintf.h \
unichar.h unicharmap.h unicharset.h unicity_table.h \
- varable.h
+ params.h
lib_LTLIBRARIES = libtesseract_ccutil.la
libtesseract_ccutil_la_SOURCES = \
@@ -23,10 +23,10 @@ libtesseract_ccutil_la_SOURCES = \
ccutil.cpp clst.cpp debugwin.cpp \
elst2.cpp elst.cpp errcode.cpp \
globaloc.cpp hashfn.cpp \
- mainblk.cpp memblk.cpp memry.cpp ocrshell.cpp \
- serialis.cpp strngs.cpp scanutils.cpp\
- tessdatamanager.cpp tessopt.cpp tordvars.cpp tprintf.cpp \
+ mainblk.cpp memblk.cpp memry.cpp \
+ serialis.cpp strngs.cpp \
+ tessdatamanager.cpp tprintf.cpp \
unichar.cpp unicharmap.cpp unicharset.cpp \
- varable.cpp
+ params.cpp
libtesseract_ccutil_la_LDFLAGS = -version-info $(GENERIC_LIBRARY_VERSION)
diff --git a/ccutil/Makefile.in b/ccutil/Makefile.in
index 16a4858278..24da53b9d9 100644
--- a/ccutil/Makefile.in
+++ b/ccutil/Makefile.in
@@ -74,9 +74,8 @@ libtesseract_ccutil_la_LIBADD =
am_libtesseract_ccutil_la_OBJECTS = ambigs.lo basedir.lo bits16.lo \
boxread.lo ccutil.lo clst.lo debugwin.lo elst2.lo elst.lo \
errcode.lo globaloc.lo hashfn.lo mainblk.lo memblk.lo memry.lo \
- ocrshell.lo serialis.lo strngs.lo scanutils.lo \
- tessdatamanager.lo tessopt.lo tordvars.lo tprintf.lo \
- unichar.lo unicharmap.lo unicharset.lo varable.lo
+ serialis.lo strngs.lo tessdatamanager.lo tprintf.lo unichar.lo \
+ unicharmap.lo unicharset.lo params.lo
libtesseract_ccutil_la_OBJECTS = $(am_libtesseract_ccutil_la_OBJECTS)
libtesseract_ccutil_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
@@ -249,7 +248,6 @@ libdir = @libdir@
libexecdir = @libexecdir@
localedir = @localedir@
localstatedir = @localstatedir@
-lt_ECHO = @lt_ECHO@
mandir = @mandir@
mkdir_p = @mkdir_p@
oldincludedir = @oldincludedir@
@@ -267,20 +265,20 @@ top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
SUBDIRS =
AM_CXXFLAGS = -DTESSDATA_PREFIX=@datadir@/
-EXTRA_DIST = ccutil.vcproj mfcpch.cpp scanutils.cpp scanutils.h
+EXTRA_DIST = mfcpch.cpp scanutils.cpp scanutils.h
include_HEADERS = \
ambigs.h basedir.h bits16.h boxread.h \
- callback.h ccutil.h clst.h \
+ tesscallback.h ccutil.h clst.h \
debugwin.h elst2.h elst.h errcode.h \
fileerr.h genericvector.h globaloc.h \
hashfn.h helpers.h host.h hosthplb.h lsterr.h \
- mainblk.h memblk.h memry.h memryerr.h mfcpch.h \
+ memblk.h memry.h memryerr.h mfcpch.h \
ndminx.h notdll.h nwmain.h \
- ocrclass.h ocrshell.h platform.h qrsequence.h \
- secname.h serialis.h stderr.h strngs.h scanutils.h \
- tessclas.h tessdatamanager.h tessopt.h tordvars.h tprintf.h \
+ ocrclass.h platform.h qrsequence.h \
+ secname.h serialis.h sorthelper.h stderr.h strngs.h \
+ tessdatamanager.h tprintf.h \
unichar.h unicharmap.h unicharset.h unicity_table.h \
- varable.h
+ params.h
lib_LTLIBRARIES = libtesseract_ccutil.la
libtesseract_ccutil_la_SOURCES = \
@@ -288,11 +286,11 @@ libtesseract_ccutil_la_SOURCES = \
ccutil.cpp clst.cpp debugwin.cpp \
elst2.cpp elst.cpp errcode.cpp \
globaloc.cpp hashfn.cpp \
- mainblk.cpp memblk.cpp memry.cpp ocrshell.cpp \
- serialis.cpp strngs.cpp scanutils.cpp\
- tessdatamanager.cpp tessopt.cpp tordvars.cpp tprintf.cpp \
+ mainblk.cpp memblk.cpp memry.cpp \
+ serialis.cpp strngs.cpp \
+ tessdatamanager.cpp tprintf.cpp \
unichar.cpp unicharmap.cpp unicharset.cpp \
- varable.cpp
+ params.cpp
libtesseract_ccutil_la_LDFLAGS = -version-info $(GENERIC_LIBRARY_VERSION)
all: all-recursive
@@ -384,18 +382,14 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mainblk.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/memblk.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/memry.Plo@am__quote@
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ocrshell.Plo@am__quote@
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scanutils.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/params.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/serialis.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strngs.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tessdatamanager.Plo@am__quote@
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tessopt.Plo@am__quote@
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tordvars.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tprintf.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unichar.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unicharmap.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unicharset.Plo@am__quote@
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/varable.Plo@am__quote@
.cpp.o:
@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
diff --git a/ccutil/ambigs.cpp b/ccutil/ambigs.cpp
index 637974b70c..c5f93308a0 100644
--- a/ccutil/ambigs.cpp
+++ b/ccutil/ambigs.cpp
@@ -21,9 +21,9 @@
#include "ambigs.h"
#include "helpers.h"
-INT_VAR(global_ambigs_debug_level, 0, "Debug level for unichar ambiguities");
-BOOL_VAR(use_definite_ambigs_for_classifier, 0,
- "Use definite ambiguities when running character classifier");
+#ifdef WIN32
+#define strtok_r strtok_s
+#endif
namespace tesseract {
@@ -37,15 +37,23 @@ AmbigSpec::AmbigSpec() {
ELISTIZE(AmbigSpec);
-void UnicharAmbigs::LoadUnicharAmbigs(FILE *AmbigFile, inT64 end_offset,
+void UnicharAmbigs::LoadUnicharAmbigs(FILE *AmbigFile,
+ inT64 end_offset,
+ int debug_level,
+ bool use_ambigs_for_adaption,
UNICHARSET *unicharset) {
- int i;
+ int i, j;
+ UnicharIdVector *adaption_ambigs_entry;
for (i = 0; i < unicharset->size(); ++i) {
replace_ambigs_.push_back(NULL);
dang_ambigs_.push_back(NULL);
one_to_one_definite_ambigs_.push_back(NULL);
+ if (use_ambigs_for_adaption) {
+ ambigs_for_adaption_.push_back(NULL);
+ reverse_ambigs_for_adaption_.push_back(NULL);
+ }
}
- if (global_ambigs_debug_level) tprintf("Reading ambiguities\n");
+ if (debug_level) tprintf("Reading ambiguities\n");
int TestAmbigPartSize;
int ReplacementAmbigPartSize;
@@ -75,10 +83,10 @@ void UnicharAmbigs::LoadUnicharAmbigs(FILE *AmbigFile, inT64 end_offset,
while ((end_offset < 0 || ftell(AmbigFile) < end_offset) &&
fgets(buffer, kBufferSize, AmbigFile) != NULL) {
chomp_string(buffer);
- if (global_ambigs_debug_level > 2) tprintf("read line %s\n", buffer);
+ if (debug_level > 2) tprintf("read line %s\n", buffer);
++line_num;
- if (!ParseAmbiguityLine(line_num, version, *unicharset, buffer,
- &TestAmbigPartSize, TestUnicharIds,
+ if (!ParseAmbiguityLine(line_num, version, debug_level, *unicharset,
+ buffer, &TestAmbigPartSize, TestUnicharIds,
&ReplacementAmbigPartSize,
ReplacementString, &type)) continue;
// Construct AmbigSpec and add it to the appropriate AmbigSpec_LIST.
@@ -89,7 +97,7 @@ void UnicharAmbigs::LoadUnicharAmbigs(FILE *AmbigFile, inT64 end_offset,
ambig_spec, unicharset);
// Update one_to_one_definite_ambigs_.
- if (use_definite_ambigs_for_classifier && TestAmbigPartSize == 1 &&
+ if (TestAmbigPartSize == 1 &&
ReplacementAmbigPartSize == 1 && type == DEFINITE_AMBIG) {
if (one_to_one_definite_ambigs_[TestUnicharIds[0]] == NULL) {
one_to_one_definite_ambigs_[TestUnicharIds[0]] = new UnicharIdVector();
@@ -97,10 +105,56 @@ void UnicharAmbigs::LoadUnicharAmbigs(FILE *AmbigFile, inT64 end_offset,
one_to_one_definite_ambigs_[TestUnicharIds[0]]->push_back(
ambig_spec->correct_ngram_id);
}
+ // Update ambigs_for_adaption_.
+ if (use_ambigs_for_adaption) {
+ for (i = 0; i < TestAmbigPartSize; ++i) {
+ if (ambigs_for_adaption_[TestUnicharIds[i]] == NULL) {
+ ambigs_for_adaption_[TestUnicharIds[i]] = new UnicharIdVector();
+ }
+ adaption_ambigs_entry = ambigs_for_adaption_[TestUnicharIds[i]];
+ const char *tmp_ptr = ReplacementString;
+ const char *tmp_ptr_end = ReplacementString + strlen(ReplacementString);
+ int step = unicharset->step(tmp_ptr);
+ while (step > 0) {
+ UNICHAR_ID id_to_insert = unicharset->unichar_to_id(tmp_ptr, step);
+ ASSERT_HOST(id_to_insert != INVALID_UNICHAR_ID);
+ // Add the new unichar id to adaption_ambigs_entry (only if the
+ // vector does not already contain it) keeping it in sorted order.
+ for (j = 0; j < adaption_ambigs_entry->size() &&
+ (*adaption_ambigs_entry)[j] > id_to_insert; ++j);
+ if (j < adaption_ambigs_entry->size()) {
+ if ((*adaption_ambigs_entry)[j] != id_to_insert) {
+ adaption_ambigs_entry->insert(id_to_insert, j);
+ }
+ } else {
+ adaption_ambigs_entry->push_back(id_to_insert);
+ }
+ // Update tmp_ptr and step.
+ tmp_ptr += step;
+ step = tmp_ptr < tmp_ptr_end ? unicharset->step(tmp_ptr) : 0;
+ }
+ }
+ }
}
delete[] buffer;
+
+ // Fill in reverse_ambigs_for_adaption from ambigs_for_adaption vector.
+ if (use_ambigs_for_adaption) {
+ for (i = 0; i < ambigs_for_adaption_.size(); ++i) {
+ adaption_ambigs_entry = ambigs_for_adaption_[i];
+ if (adaption_ambigs_entry == NULL) continue;
+ for (j = 0; j < adaption_ambigs_entry->size(); ++j) {
+ UNICHAR_ID ambig_id = (*adaption_ambigs_entry)[j];
+ if (reverse_ambigs_for_adaption_[ambig_id] == NULL) {
+ reverse_ambigs_for_adaption_[ambig_id] = new UnicharIdVector();
+ }
+ reverse_ambigs_for_adaption_[ambig_id]->push_back(i);
+ }
+ }
+ }
+
// Print what was read from the input file.
- if (global_ambigs_debug_level > 2) {
+ if (debug_level > 1) {
for (int tbl = 0; tbl < 2; ++tbl) {
const UnicharAmbigsVector &print_table =
(tbl == 0) ? replace_ambigs_ : dang_ambigs_;
@@ -122,11 +176,30 @@ void UnicharAmbigs::LoadUnicharAmbigs(FILE *AmbigFile, inT64 end_offset,
}
}
}
+ if (use_ambigs_for_adaption) {
+ for (int vec_id = 0; vec_id < 2; ++vec_id) {
+ const GenericVector &vec = (vec_id == 0) ?
+ ambigs_for_adaption_ : reverse_ambigs_for_adaption_;
+ for (i = 0; i < vec.size(); ++i) {
+ adaption_ambigs_entry = vec[i];
+ if (adaption_ambigs_entry != NULL) {
+ tprintf("%sAmbigs for adaption for %s:\n",
+ (vec_id == 0) ? "" : "Reverse ",
+ unicharset->debug_str(i).string());
+ for (j = 0; j < adaption_ambigs_entry->size(); ++j) {
+ tprintf("%s ", unicharset->debug_str(
+ (*adaption_ambigs_entry)[j]).string());
+ }
+ tprintf("\n");
+ }
+ }
+ }
+ }
}
}
bool UnicharAmbigs::ParseAmbiguityLine(
- int line_num, int version, const UNICHARSET &unicharset,
+ int line_num, int version, int debug_level, const UNICHARSET &unicharset,
char *buffer, int *TestAmbigPartSize, UNICHAR_ID *TestUnicharIds,
int *ReplacementAmbigPartSize, char *ReplacementString, int *type) {
int i;
@@ -134,7 +207,7 @@ bool UnicharAmbigs::ParseAmbiguityLine(
char *next_token;
if (!(token = strtok_r(buffer, kAmbigDelimiters, &next_token)) ||
!sscanf(token, "%d", TestAmbigPartSize) || TestAmbigPartSize <= 0) {
- if (global_ambigs_debug_level) tprintf(kIllegalMsg, line_num);
+ if (debug_level) tprintf(kIllegalMsg, line_num);
return false;
}
if (*TestAmbigPartSize > MAX_AMBIG_SIZE) {
@@ -144,7 +217,7 @@ bool UnicharAmbigs::ParseAmbiguityLine(
for (i = 0; i < *TestAmbigPartSize; ++i) {
if (!(token = strtok_r(NULL, kAmbigDelimiters, &next_token))) break;
if (!unicharset.contains_unichar(token)) {
- if (global_ambigs_debug_level) tprintf(kIllegalUnicharMsg, token);
+ if (debug_level) tprintf(kIllegalUnicharMsg, token);
break;
}
TestUnicharIds[i] = unicharset.unichar_to_id(token);
@@ -155,7 +228,7 @@ bool UnicharAmbigs::ParseAmbiguityLine(
!(token = strtok_r(NULL, kAmbigDelimiters, &next_token)) ||
!sscanf(token, "%d", ReplacementAmbigPartSize) ||
*ReplacementAmbigPartSize <= 0) {
- if (global_ambigs_debug_level) tprintf(kIllegalMsg, line_num);
+ if (debug_level) tprintf(kIllegalMsg, line_num);
return false;
}
if (*ReplacementAmbigPartSize > MAX_AMBIG_SIZE) {
@@ -167,12 +240,12 @@ bool UnicharAmbigs::ParseAmbiguityLine(
if (!(token = strtok_r(NULL, kAmbigDelimiters, &next_token))) break;
strcat(ReplacementString, token);
if (!unicharset.contains_unichar(token)) {
- if (global_ambigs_debug_level) tprintf(kIllegalUnicharMsg, token);
+ if (debug_level) tprintf(kIllegalUnicharMsg, token);
break;
}
}
if (i != *ReplacementAmbigPartSize) {
- if (global_ambigs_debug_level) tprintf(kIllegalMsg, line_num);
+ if (debug_level) tprintf(kIllegalMsg, line_num);
return false;
}
if (version > 0) {
@@ -187,7 +260,7 @@ bool UnicharAmbigs::ParseAmbiguityLine(
// has limited support for ngram unichar (e.g. dawg permuter).
if (!(token = strtok_r(NULL, kAmbigDelimiters, &next_token)) ||
!sscanf(token, "%d", type)) {
- if (global_ambigs_debug_level) tprintf(kIllegalMsg, line_num);
+ if (debug_level) tprintf(kIllegalMsg, line_num);
return false;
}
}
@@ -226,7 +299,7 @@ void UnicharAmbigs::InsertIntoTable(
if (ReplacementAmbigPartSize > 1) {
unicharset->set_isngram(ambig_spec->correct_ngram_id, true);
}
- // Add the corresponding fragments of the correct ngram to unicharset.
+ // Add the corresponding fragments of the wrong ngram to unicharset.
int i;
for (i = 0; i < TestAmbigPartSize; ++i) {
UNICHAR_ID unichar_id;
@@ -248,7 +321,7 @@ void UnicharAmbigs::InsertIntoTable(
table[TestUnicharIds[0]] = new AmbigSpec_LIST();
}
table[TestUnicharIds[0]]->add_sorted(
- AmbigSpec::compare_ambig_specs, ambig_spec);
+ AmbigSpec::compare_ambig_specs, false, ambig_spec);
}
} // namespace tesseract
diff --git a/ccutil/ambigs.h b/ccutil/ambigs.h
index e3bde2fd99..a72e751633 100644
--- a/ccutil/ambigs.h
+++ b/ccutil/ambigs.h
@@ -29,13 +29,10 @@
#define MAX_AMBIG_SIZE 10
-extern INT_VAR_H(global_ambigs_debug_level, 0,
- "Debug level for unichar ambiguities");
-extern BOOL_VAR_H(use_definite_ambigs_for_classifier, 0,
- "Use definite ambiguities when running character classifier");
-
namespace tesseract {
+typedef GenericVector UnicharIdVector;
+
static const int kUnigramAmbigsBufferSize = 1000;
static const char kAmbigNgramSeparator[] = { ' ', '\0' };
static const char kAmbigDelimiters[] = "\t ";
@@ -75,6 +72,15 @@ class UnicharIdArrayUtils {
return *ptr1 == INVALID_UNICHAR_ID ? -1 : 1;
}
+ // Look uid in the vector of uids. If found, the index of the matched
+ // element is returned. Otherwise, it returns -1.
+ static inline int find_in(const UnicharIdVector& uid_vec,
+ const UNICHAR_ID uid) {
+ for (int i = 0; i < uid_vec.size(); ++i)
+ if (uid_vec[i] == uid) return i;
+ return -1;
+ }
+
// Copies UNICHAR_IDs from dst to src. Returns the number of ids copied.
// The function assumes that the arrays are terminated by INVALID_UNICHAR_ID
// and that dst has enough space for all the elements from src.
@@ -131,7 +137,6 @@ ELISTIZEH(AmbigSpec);
// AMBIG_TABLE[i] stores a set of ambiguities whose
// wrong ngram starts with unichar id i.
typedef GenericVector UnicharAmbigsVector;
-typedef GenericVector UnicharIdVector;
class UnicharAmbigs {
public:
@@ -155,18 +160,39 @@ class UnicharAmbigs {
// one_to_one_definite_ambigs_. This vector is also indexed by the class id
// of the wrong part of the ambiguity and each entry contains a vector of
// unichar ids that are ambiguous to it.
- void LoadUnicharAmbigs(FILE *ambigs_file, inT64 end_offset,
- UNICHARSET *unicharset);
+ void LoadUnicharAmbigs(FILE *ambigs_file, inT64 end_offset, int debug_level,
+ bool use_ambigs_for_adaption, UNICHARSET *unicharset);
- // Return definite 1-1 ambigs.
- const UnicharIdVector *OneToOneDefiniteAmbigs(UNICHAR_ID unichar_id) const {
+ // Returns definite 1-1 ambigs for the given unichar id.
+ inline const UnicharIdVector *OneToOneDefiniteAmbigs(
+ UNICHAR_ID unichar_id) const {
if (one_to_one_definite_ambigs_.empty()) return NULL;
return one_to_one_definite_ambigs_[unichar_id];
}
+ // Returns a pointer to the vector with all unichar ids that appear in the
+ // 'correct' part of the ambiguity pair when the given unichar id appears
+ // in the 'wrong' part of the ambiguity. E.g. if DangAmbigs file consist of
+ // m->rn,rn->m,m->iii, UnicharAmbigsForAdaption() called with unichar id of
+ // m will return a pointer to a vector with unichar ids of r,n,i.
+ inline const UnicharIdVector *AmbigsForAdaption(
+ UNICHAR_ID unichar_id) const {
+ if (ambigs_for_adaption_.empty()) return NULL;
+ return ambigs_for_adaption_[unichar_id];
+ }
+
+ // Similar to the above, but return the vector of unichar ids for which
+ // the given unichar_id is an ambiguity (appears in the 'wrong' part of
+ // some ambiguity pair).
+ inline const UnicharIdVector *ReverseAmbigsForAdaption(
+ UNICHAR_ID unichar_id) const {
+ if (reverse_ambigs_for_adaption_.empty()) return NULL;
+ return reverse_ambigs_for_adaption_[unichar_id];
+ }
+
private:
- bool ParseAmbiguityLine(int line_num, int version,
+ bool ParseAmbiguityLine(int line_num, int version, int debug_level,
const UNICHARSET &unicharset, char *buffer,
int *TestAmbigPartSize, UNICHAR_ID *TestUnicharIds,
int *ReplacementAmbigPartSize,
@@ -179,6 +205,8 @@ class UnicharAmbigs {
UnicharAmbigsVector dang_ambigs_;
UnicharAmbigsVector replace_ambigs_;
GenericVector one_to_one_definite_ambigs_;
+ GenericVector ambigs_for_adaption_;
+ GenericVector reverse_ambigs_for_adaption_;
};
} // namespace tesseract
diff --git a/ccutil/basedir.cpp b/ccutil/basedir.cpp
index 9acfcd5eec..9f07803095 100644
--- a/ccutil/basedir.cpp
+++ b/ccutil/basedir.cpp
@@ -22,17 +22,14 @@
#ifdef __UNIX__
#include
#include
+#else
+#include
#endif
#include
#include "basedir.h"
-#include "varable.h"
+#include "params.h"
#include "notdll.h" //must be last include
-#ifdef __MSW32__
-STRING_VAR(tessedit_module_name, "tessdll.dll",
- "Module colocated with tessdata dir");
-#endif
-
/**********************************************************************
* getpath
*
@@ -42,6 +39,7 @@ STRING_VAR(tessedit_module_name, "tessdll.dll",
DLLSYM inT8 getpath( //get dir name of code
const char *code, //executable to locate
+ const STRING &dll_module_name,
STRING &path //output path name
) {
char directory[MAX_PATH]; //main directory
@@ -96,7 +94,7 @@ DLLSYM inT8 getpath( //get dir name of code
// Attempt to get the path of the most relevant module. If the dll
// is being used, this will be the dll. Otherwise GetModuleHandle will
// return NULL and default to the path of the executable.
- if (GetModuleFileName(GetModuleHandle(tessedit_module_name.string()),
+ if (GetModuleFileName(GetModuleHandle(dll_module_name.string()),
directory, MAX_PATH - 1) == 0) {
return -1;
}
diff --git a/ccutil/basedir.h b/ccutil/basedir.h
index 1c8d61c7b9..856e9e6e77 100644
--- a/ccutil/basedir.h
+++ b/ccutil/basedir.h
@@ -27,6 +27,7 @@
DLLSYM inT8 getpath( //get dir name of code
const char *code, //executable to locate
+ const STRING &dll_module_name,
STRING &path //output path name
);
#endif
diff --git a/ccutil/boxread.cpp b/ccutil/boxread.cpp
index c1f9ae845d..fd4b479c94 100644
--- a/ccutil/boxread.cpp
+++ b/ccutil/boxread.cpp
@@ -23,6 +23,26 @@
#include "unichar.h"
#include "tprintf.h"
+// Special char code used to identify multi-blob labels.
+static const char* kMultiBlobLabelCode = "WordStr";
+
+// Open the boxfile based on the given image filename.
+FILE* OpenBoxFile(const STRING& fname) {
+ STRING filename = fname;
+ const char *lastdot = strrchr(filename.string(), '.');
+ if (lastdot != NULL)
+ filename[lastdot - filename.string()] = '\0';
+
+ filename += ".box";
+ FILE* box_file = NULL;
+ if (!(box_file = fopen(filename.string(), "r"))) {
+ CANTOPENFILE.error("read_next_box", TESSEXIT,
+ "Cant open box file %s",
+ filename.string());
+ }
+ return box_file;
+}
+
// Box files are used ONLY DURING TRAINING, but by both processes of
// creating tr files with tesseract, and unicharset_extractor.
// read_next_box factors out the code to interpret a line of a box
@@ -33,26 +53,26 @@
// space or tab between fields.
// utf8_str must be at least kBoxReadBufSize in length.
// If there are page numbers in the file, it reads them all.
-bool read_next_box(FILE* box_file, char* utf8_str,
+bool read_next_box(int *line_number, FILE* box_file, char* utf8_str,
int* x_min, int* y_min, int* x_max, int* y_max) {
- return read_next_box(-1, box_file, utf8_str,
+ return read_next_box(-1, line_number, box_file, utf8_str,
x_min, y_min, x_max, y_max);
}
// As read_next_box above, but get a specific page number. (0-based)
// Use -1 to read any page number. Files without page number all
// read as if they are page 0.
-bool read_next_box(int target_page, FILE* box_file, char* utf8_str,
+bool read_next_box(int target_page, int *line_number,
+ FILE* box_file, char* utf8_str,
int* x_min, int* y_min, int* x_max, int* y_max) {
- static int line = 0;
int count = 0;
int page = 0;
- char buff[kBoxReadBufSize]; //boxfile read buffer
+ char buff[kBoxReadBufSize]; // boxfile read buffer
char uch[kBoxReadBufSize];
char *buffptr = buff;
while (fgets(buff, sizeof(buff) - 1, box_file)) {
- line++;
+ (*line_number)++;
buffptr = buff;
const unsigned char *ubuf = reinterpret_cast(buffptr);
@@ -63,6 +83,9 @@ bool read_next_box(int target_page, FILE* box_file, char* utf8_str,
buffptr++;
if (*buffptr != '\0') {
// Read the unichar without messing up on Tibetan.
+ // According to issue 253 the utf-8 surrogates 85 and A0 are treated
+ // as whitespace by sscanf, so it is more reliable to just find
+ // ascii space and tab.
int uch_len = 0;
while (*buffptr != '\0' && *buffptr != ' ' && *buffptr != '\t')
uch[uch_len++] = *buffptr++;
@@ -76,40 +99,40 @@ bool read_next_box(int target_page, FILE* box_file, char* utf8_str,
page = 0;
count = sscanf(buffptr, "%d %d %d %d", x_min, y_min, x_max, y_max);
} else {
- tprintf("Box file format error on line %i; ignored\n", line);
+ tprintf("Box file format error on line %i; ignored\n", *line_number);
continue;
}
}
if (target_page >= 0 && target_page != page)
continue; // Not on the appropriate page.
- if (count >= 4) {
- // Validate UTF8 by making unichars with it.
- int used = 0;
- while (used < uch_len) {
- UNICHAR ch(uch + used, uch_len - used);
- int new_used = ch.utf8_len();
- if (new_used == 0) {
- tprintf("Bad UTF-8 str %s starts with 0x%02x at line %d, col %d\n",
- uch + used, uch[used], line, used + 1);
- count = 0;
- break;
- }
- used += new_used;
- }
- if (uch_len > UNICHAR_LEN) {
- tprintf("utf-8 string too long at line %d\n", line);
+ // Test for long space-delimited string label.
+ if (strcmp(uch, kMultiBlobLabelCode) == 0 &&
+ (buffptr = strchr(buffptr, '#')) != NULL) {
+ strcpy(uch, buffptr + 1);
+ chomp_string(uch);
+ uch_len = strlen(uch);
+ }
+ // Validate UTF8 by making unichars with it.
+ int used = 0;
+ while (used < uch_len) {
+ UNICHAR ch(uch + used, uch_len - used);
+ int new_used = ch.utf8_len();
+ if (new_used == 0) {
+ tprintf("Bad UTF-8 str %s starts with 0x%02x at line %d, col %d\n",
+ uch + used, uch[used], *line_number, used + 1);
count = 0;
+ break;
}
+ used += new_used;
}
- if ((count < 5 && target_page > 0) || (count < 4 && target_page <= 0)) {
- tprintf("Box file format error on line %i ignored\n", line);
+ if (count < 4 || used == 0) {
+ tprintf("Box file format error on line %i; ignored\n", *line_number);
} else {
- strcpy(utf8_str, uch);
+ strncpy(utf8_str, uch, kBoxReadBufSize);
return true; // Successfully read a box.
}
}
}
fclose(box_file);
- line = 0;
- return false; //EOF
+ return false; // EOF
}
diff --git a/ccutil/boxread.h b/ccutil/boxread.h
index f00cbd13a5..a326df5abc 100644
--- a/ccutil/boxread.h
+++ b/ccutil/boxread.h
@@ -21,9 +21,13 @@
#define TESSERACT_CCUTIL_BOXREAD_H__
#include
+#include "strngs.h"
// Size of buffer used to read a line from a box file.
-const int kBoxReadBufSize = 256;
+const int kBoxReadBufSize = 1024;
+
+// Open the boxfile based on the given image filename.
+FILE* OpenBoxFile(const STRING& fname);
// read_next_box factors out the code to interpret a line of a box
// file so that applybox and unicharset_extractor interpret the same way.
@@ -33,12 +37,12 @@ const int kBoxReadBufSize = 256;
// space or tab between fields.
// utf8_str must be at least kBoxReadBufSize in length.
// If there are page numbers in the file, it reads them all.
-bool read_next_box(FILE* box_file, char* utf8_str,
+bool read_next_box(int *line_number, FILE* box_file, char* utf8_str,
int* x_min, int* y_min, int* x_max, int* y_max);
// As read_next_box above, but get a specific page number. (0-based)
// Use -1 to read any page number. Files without page number all
// read as if they are page 0.
-bool read_next_box(int page, FILE* box_file, char* utf8_str,
+bool read_next_box(int page, int *line_number, FILE* box_file, char* utf8_str,
int* x_min, int* y_min, int* x_max, int* y_max);
#endif // TESSERACT_CCUTIL_BOXREAD_H__
diff --git a/ccutil/ccutil.cpp b/ccutil/ccutil.cpp
index 7b064ffc1d..4d5e37c08a 100644
--- a/ccutil/ccutil.cpp
+++ b/ccutil/ccutil.cpp
@@ -4,15 +4,20 @@
#include "ccutil.h"
namespace tesseract {
-CCUtil::CCUtil()
- : //// mainblk.* /////////////////////////////////////////////////////
- BOOL_MEMBER(m_print_variables, FALSE,
- "Print initial values of all variables"),
- STRING_MEMBER(m_data_sub_dir,
- "tessdata/", "Directory for data files")
- ////////////////////////////////////////////////////////////////////
- {
-
+CCUtil::CCUtil() :
+ params_(),
+ STRING_INIT_MEMBER(m_data_sub_dir,
+ "tessdata/", "Directory for data files", ¶ms_),
+#ifdef __MSW32__
+ STRING_INIT_MEMBER(tessedit_module_name, "tessdll.dll",
+ "Module colocated with tessdata dir", ¶ms_),
+#endif
+ INT_INIT_MEMBER(ambigs_debug_level, 0, "Debug level for unichar ambiguities",
+ ¶ms_),
+ BOOL_INIT_MEMBER(use_definite_ambigs_for_classifier, 0, "Use definite"
+ " ambiguities when running character classifier", ¶ms_),
+ BOOL_INIT_MEMBER(use_ambigs_for_adaption, 0, "Use ambigs for deciding"
+ " whether to adapt to a character", ¶ms_) {
}
CCUtil::~CCUtil() {
@@ -43,6 +48,5 @@ void CCUtilMutex::Unlock() {
#endif
}
-
-CCUtilMutex tprintfMutex;
+CCUtilMutex tprintfMutex; // should remain global
} // namespace tesseract
diff --git a/ccutil/ccutil.h b/ccutil/ccutil.h
index 731d419e8c..55744ca23f 100644
--- a/ccutil/ccutil.h
+++ b/ccutil/ccutil.h
@@ -23,7 +23,7 @@
#include "errcode.h"
#include "strngs.h"
#include "tessdatamanager.h"
-#include "varable.h"
+#include "params.h"
#include "unicharset.h"
#ifdef WIN32
@@ -54,20 +54,18 @@ class CCUtilMutex {
class CCUtil {
public:
CCUtil();
- ~CCUtil();
+ virtual ~CCUtil();
public:
+ // Read the arguments and set up the data path.
void main_setup(
const char *argv0, // program name
const char *basename // name of image
);
- public:
+ ParamsVectors *params() { return ¶ms_; }
+
STRING datadir; // dir for data files
STRING imagebasename; // name of image
-
- BOOL_VAR_H (m_print_variables, FALSE,
- "Print initial values of all variables");
- STRING_VAR_H (m_data_sub_dir, "tessdata/", "Directory for data files");
STRING lang;
STRING language_data_path_prefix;
TessdataManager tessdata_manager;
@@ -75,9 +73,27 @@ class CCUtil {
UnicharAmbigs unichar_ambigs;
STRING imagefile; // image file name
STRING directory; // main directory
+
+ private:
+ ParamsVectors params_;
+
+ public:
+ // Member parameters.
+ // These have to be declared and initialized after params_ member, since
+ // params_ should be initialized before parameters are added to it.
+ STRING_VAR_H(m_data_sub_dir, "tessdata/", "Directory for data files");
+ #ifdef __MSW32__
+ STRING_VAR_H(tessedit_module_name, "tessdll.dll",
+ "Module colocated with tessdata dir");
+ #endif
+ INT_VAR_H(ambigs_debug_level, 0, "Debug level for unichar ambiguities");
+ BOOL_VAR_H(use_definite_ambigs_for_classifier, 0,
+ "Use definite ambiguities when running character classifier");
+ BOOL_VAR_H(use_ambigs_for_adaption, 0,
+ "Use ambigs for deciding whether to adapt to a character");
};
-extern CCUtilMutex tprintfMutex;
+extern CCUtilMutex tprintfMutex; // should remain global
} // namespace tesseract
#endif // TESSERACT_CCUTIL_CCUTIL_H__
diff --git a/ccutil/clst.cpp b/ccutil/clst.cpp
index 3b7590f3bb..48a801bcf7 100644
--- a/ccutil/clst.cpp
+++ b/ccutil/clst.cpp
@@ -160,8 +160,8 @@ void CLIST::assign_to_sublist( //to this list
* Return count of elements on list
**********************************************************************/
-inT32 CLIST::length() { //count elements
- CLIST_ITERATOR it(this);
+inT32 CLIST::length() const { //count elements
+ CLIST_ITERATOR it(const_cast(this));
inT32 count = 0;
#ifndef NDEBUG
@@ -169,7 +169,7 @@ inT32 CLIST::length() { //count elements
NULL_OBJECT.error ("CLIST::length", ABORT, NULL);
#endif
- for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ())
+ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward())
count++;
return count;
}
@@ -225,7 +225,8 @@ const void *, const void *)) {
// indirection. Time is O(1) to add to beginning or end.
// Time is linear to add pre-sorted items to an empty list.
// If unique, then don't add duplicate entries.
-void CLIST::add_sorted(int comparator(const void*, const void*),
+// Returns true if the element was added to the list.
+bool CLIST::add_sorted(int comparator(const void*, const void*),
bool unique, void* new_data) {
// Check for adding at the end.
if (last == NULL || comparator(&last->data, &new_data) < 0) {
@@ -238,13 +239,14 @@ void CLIST::add_sorted(int comparator(const void*, const void*),
last->next = new_element;
}
last = new_element;
+ return true;
} else if (!unique || last->data != new_data) {
// Need to use an iterator.
CLIST_ITERATOR it(this);
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
void* data = it.data();
if (data == new_data && unique)
- return;
+ return false;
if (comparator(&data, &new_data) > 0)
break;
}
@@ -252,6 +254,37 @@ void CLIST::add_sorted(int comparator(const void*, const void*),
it.add_to_end(new_data);
else
it.add_before_then_move(new_data);
+ return true;
+ }
+ return false;
+}
+
+// Assuming that the minuend and subtrahend are already sorted with
+// the same comparison function, shallow clears this and then copies
+// the set difference minuend - subtrahend to this, being the elements
+// of minuend that do not compare equal to anything in subtrahend.
+// If unique is true, any duplicates in minuend are also eliminated.
+void CLIST::set_subtract(int comparator(const void*, const void*),
+ bool unique,
+ CLIST* minuend, CLIST* subtrahend) {
+ shallow_clear();
+ CLIST_ITERATOR m_it(minuend);
+ CLIST_ITERATOR s_it(subtrahend);
+ // Since both lists are sorted, finding the subtras that are not
+ // minus is a case of a parallel iteration.
+ for (m_it.mark_cycle_pt(); !m_it.cycled_list(); m_it.forward()) {
+ void* minu = m_it.data();
+ void* subtra = NULL;
+ if (!s_it.empty()) {
+ subtra = s_it.data();
+ while (!s_it.at_last() &&
+ comparator(&subtra, &minu) < 0) {
+ s_it.forward();
+ subtra = s_it.data();
+ }
+ }
+ if (subtra == NULL || comparator(&subtra, &minu) != 0)
+ add_sorted(comparator, unique, minu);
}
}
diff --git a/ccutil/clst.h b/ccutil/clst.h
index 33697e7550..8b65126f79 100644
--- a/ccutil/clst.h
+++ b/ccutil/clst.h
@@ -96,12 +96,12 @@ class DLLSYM CLIST
void shallow_clear(); //clear list but dont
//delete data elements
- bool empty() { //is list empty?
+ bool empty() const { //is list empty?
return !last;
}
- bool singleton() {
- return last != NULL ? (last == last->next) : FALSE;
+ bool singleton() const {
+ return last != NULL ? (last == last->next) : false;
}
void shallow_copy( //dangerous!!
@@ -117,7 +117,7 @@ class DLLSYM CLIST
CLIST_ITERATOR *start_it, //from list start
CLIST_ITERATOR *end_it); //from list end
- inT32 length(); //# elements in list
+ inT32 length() const; //# elements in list
void sort ( //sort elements
int comparator ( //comparison routine
@@ -129,9 +129,18 @@ class DLLSYM CLIST
// indirection. Time is O(1) to add to beginning or end.
// Time is linear to add pre-sorted items to an empty list.
// If unique, then don't add duplicate entries.
- void add_sorted(int comparator(const void*, const void*),
+ // Returns true if the element was added to the list.
+ bool add_sorted(int comparator(const void*, const void*),
bool unique, void* new_data);
+ // Assuming that the minuend and subtrahend are already sorted with
+ // the same comparison function, shallow clears this and then copies
+ // the set difference minuend - subtrahend to this, being the elements
+ // of minuend that do not compare equal to anything in subtrahend.
+ // If unique is true, any duplicates in minuend are also eliminated.
+ void set_subtract(int comparator(const void*, const void*), bool unique,
+ CLIST* minuend, CLIST* subtrahend);
+
void internal_dump ( //serialise each elem
FILE * f, //to this file
void element_serialiser ( //using this function
@@ -165,13 +174,13 @@ class DLLSYM CLIST_ITERATOR
CLIST_LINK *prev; //prev element
CLIST_LINK *current; //current element
CLIST_LINK *next; //next element
- bool ex_current_was_last; //current extracted
+ BOOL8 ex_current_was_last; //current extracted
//was end of list
- bool ex_current_was_cycle_pt; //current extracted
+ BOOL8 ex_current_was_cycle_pt; //current extracted
//was cycle point
CLIST_LINK *cycle_pt; //point we are cycling
//the list to.
- bool started_cycling; //Have we moved off
+ BOOL8 started_cycling; //Have we moved off
//the start?
CLIST_LINK *extract_sublist( //from this current...
@@ -229,7 +238,7 @@ class DLLSYM CLIST_ITERATOR
void mark_cycle_pt(); //remember current
- bool empty() { //is list empty?
+ BOOL8 empty() { //is list empty?
#ifndef NDEBUG
if (!list)
NO_LIST.error ("CLIST_ITERATOR::empty", ABORT, NULL);
@@ -237,15 +246,15 @@ class DLLSYM CLIST_ITERATOR
return list->empty ();
}
- bool current_extracted() { //current extracted?
+ BOOL8 current_extracted() { //current extracted?
return !current;
}
- bool at_first(); //Current is first?
+ BOOL8 at_first(); //Current is first?
- bool at_last(); //Current is last?
+ BOOL8 at_last(); //Current is last?
- bool cycled_list(); //Completed a cycle?
+ BOOL8 cycled_list(); //Completed a cycle?
void add_to_end( //add at end &
void *new_data); //dont move
@@ -695,7 +704,7 @@ inline void CLIST_ITERATOR::mark_cycle_pt() {
*
**********************************************************************/
-inline bool CLIST_ITERATOR::at_first() {
+inline BOOL8 CLIST_ITERATOR::at_first() {
#ifndef NDEBUG
if (!this)
NULL_OBJECT.error ("CLIST_ITERATOR::at_first", ABORT, NULL);
@@ -717,7 +726,7 @@ inline bool CLIST_ITERATOR::at_first() {
*
**********************************************************************/
-inline bool CLIST_ITERATOR::at_last() {
+inline BOOL8 CLIST_ITERATOR::at_last() {
#ifndef NDEBUG
if (!this)
NULL_OBJECT.error ("CLIST_ITERATOR::at_last", ABORT, NULL);
@@ -739,7 +748,7 @@ inline bool CLIST_ITERATOR::at_last() {
*
**********************************************************************/
-inline bool CLIST_ITERATOR::cycled_list() {
+inline BOOL8 CLIST_ITERATOR::cycled_list() {
#ifndef NDEBUG
if (!this)
NULL_OBJECT.error ("CLIST_ITERATOR::cycled_list", ABORT, NULL);
diff --git a/ccutil/debugwin.cpp b/ccutil/debugwin.cpp
index 8efbff0af2..ae073f5409 100644
--- a/ccutil/debugwin.cpp
+++ b/ccutil/debugwin.cpp
@@ -23,12 +23,14 @@
#include "mfcpch.h" //precompiled headers
#include
#include "debugwin.h"
+#include "params.h"
// Include automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif
+// Should remain a global parameter, since this is only used for debug editor.
DLLSYM INT_VAR (debug_lines, 256, "Number of lines in debug window");
#ifndef GRAPHICS_DISABLED
@@ -41,12 +43,14 @@ DLLSYM INT_VAR (debug_lines, 256, "Number of lines in debug window");
#define scrl_SCROLLER 101
#define text_FLOWED 100
+// Should remain a global variable, since this is only used for debug editor.
static LCommander *pCommander = NULL;
#endif
//NT implementation
#if defined(__MSW32__) && !defined(_CONSOLE)
+#include
#define ID_DEBUG_MSG 32779
/**********************************************************************
@@ -407,80 +411,7 @@ DEBUG_WIN::~DEBUG_WIN (
void
DEBUG_WIN::dprintf ( //debug printf
const char *format, ... //special message
-) {
- #if 0
- LTextEdit *pTextEdit;
- va_list args; //variable args
- static char msg[1024];
-
- inT32 i;
- inT32 OriginalLength;
- inT32 NewLength;
- TEHandle hTextEdit;
- char *pTempBuffer;
- CharsHandle hChar;
- char *pOriginalText;
- inT32 StringLength;
-
- pTextEdit = (LTextEdit *) pWindow->FindPaneByID (text_FLOWED);
- if (pTextEdit == NULL)
- DebugStr ("\pwhoops");
-
- // get a C String from the format and args passed in
-
- va_start(args, format); //variable list
- vsprintf(msg, format, args); //Format into msg
- va_end(args);
-
- StringLength = strlen (msg);
-
- // get the handle for the text
-
- hTextEdit = pTextEdit->GetMacTEH ();
- if (hTextEdit == NULL)
- DebugStr ("\pDEBUG_WIN,WriteCharsToConsole()");
-
- // get a pointer to the characters and the length of the character stream
-
- hChar = TEGetText (hTextEdit);
- if (hChar == NULL)
- DebugStr ("\pDEBUG_WIN,WriteCharsToConsole()");
-
- pOriginalText = *hChar; // get pointer to existing text
-
- // get the length of the original data
- OriginalLength = (*hTextEdit)->teLength;
-
- // setup a temporary buffer for the new text
-
- NewLength = OriginalLength + StringLength;
-
- pTempBuffer = NewPtr (NewLength);
- if (pTempBuffer == NULL)
- DebugStr ("\pDEBUG_WIN,WriteCharsToConsole()");
-
- // copy the original data into the new buffer
-
- for (i = 0; i < OriginalLength; i++)
- pTempBuffer[i] = pOriginalText[i];
-
- // append the new data onto the end of the original buffer
-
- for (i = 0; i < StringLength; i++) {
- if (msg[i] == '\n')
- pTempBuffer[i + OriginalLength] = '\r';
- else
- pTempBuffer[i + OriginalLength] = msg[i];
- }
-
- // put the new text into the text edit item
-
- TESetText(pTempBuffer, NewLength, hTextEdit);
-
- // clean up
-
- DisposePtr(pTempBuffer);
- #endif
+) {
}
#endif //Mac Implmentation
@@ -504,4 +435,3 @@ void await_destruction() {
#endif
-
diff --git a/ccutil/debugwin.h b/ccutil/debugwin.h
index 58e74c660f..6e3bad6929 100644
--- a/ccutil/debugwin.h
+++ b/ccutil/debugwin.h
@@ -21,7 +21,7 @@
#define DEBUGWIN_H
#include "host.h"
-#include "varable.h"
+#include "params.h"
#ifdef __MAC__
#include
@@ -35,7 +35,7 @@
#define DEBUG_WIN_XSIZE 700 //default size
#define DEBUG_WIN_YSIZE 300 //default size
-//number of lines in the scrollable area of the window
+// Should remain a global parameter, since this is only used for debug editor.
extern DLLSYM INT_VAR_H (debug_lines, 256, "Number of lines in debug window");
//the API for the debug window is simple, see below.
diff --git a/ccutil/elst.cpp b/ccutil/elst.cpp
index 275a8fb9d2..24e9d4ffc5 100644
--- a/ccutil/elst.cpp
+++ b/ccutil/elst.cpp
@@ -124,8 +124,8 @@ void ELIST::assign_to_sublist( //to this list
* Return count of elements on list
**********************************************************************/
-inT32 ELIST::length() { //count elements
- ELIST_ITERATOR it(this);
+inT32 ELIST::length() const { // count elements
+ ELIST_ITERATOR it(const_cast(this));
inT32 count = 0;
#ifndef NDEBUG
@@ -190,8 +190,14 @@ const void *, const void *)) {
// Comparision function is the same as used by sort, i.e. uses double
// indirection. Time is O(1) to add to beginning or end.
// Time is linear to add pre-sorted items to an empty list.
-void ELIST::add_sorted(int comparator(const void*, const void*),
- ELIST_LINK* new_link) {
+// If unique is set to true and comparator() returns 0 (an entry with the
+// same information as the one contained in new_link is already in the
+// list) - new_link is not added to the list and the function returns the
+// pointer to the identical entry that already exists in the list
+// (otherwise the function returns new_link).
+ELIST_LINK *ELIST::add_sorted_and_find(
+ int comparator(const void*, const void*),
+ bool unique, ELIST_LINK* new_link) {
// Check for adding at the end.
if (last == NULL || comparator(&last, &new_link) < 0) {
if (last == NULL) {
@@ -206,14 +212,19 @@ void ELIST::add_sorted(int comparator(const void*, const void*),
ELIST_ITERATOR it(this);
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
ELIST_LINK* link = it.data();
- if (comparator(&link, &new_link) > 0)
+ int compare = comparator(&link, &new_link);
+ if (compare > 0) {
break;
+ } else if (unique && compare == 0) {
+ return link;
+ }
}
if (it.cycled_list())
it.add_to_end(new_link);
else
it.add_before_then_move(new_link);
}
+ return new_link;
}
/***********************************************************************
diff --git a/ccutil/elst.h b/ccutil/elst.h
index 41fea3089b..e07e40f643 100644
--- a/ccutil/elst.h
+++ b/ccutil/elst.h
@@ -137,20 +137,16 @@ class DLLSYM ELIST
last = NULL;
}
- virtual ~ELIST() {
- // Empty
- }
-
void internal_clear ( //destroy all links
//ptr to zapper functn
void (*zapper) (ELIST_LINK *));
- bool empty() { //is list empty?
+ bool empty() const { //is list empty?
return !last;
}
- bool singleton() {
- return last ? (last == last->next) : FALSE;
+ bool singleton() const {
+ return last ? (last == last->next) : false;
}
void shallow_copy( //dangerous!!
@@ -166,7 +162,7 @@ class DLLSYM ELIST
ELIST_ITERATOR *start_it, //from list start
ELIST_ITERATOR *end_it); //from list end
- inT32 length(); //# elements in list
+ inT32 length() const; // # elements in list
void sort ( //sort elements
int comparator ( //comparison routine
@@ -177,8 +173,20 @@ class DLLSYM ELIST
// Comparision function is the same as used by sort, i.e. uses double
// indirection. Time is O(1) to add to beginning or end.
// Time is linear to add pre-sorted items to an empty list.
- void add_sorted(int comparator(const void*, const void*),
- ELIST_LINK* new_link);
+ // If unique is set to true and comparator() returns 0 (an entry with the
+ // same information as the one contained in new_link is already in the
+ // list) - new_link is not added to the list and the function returns the
+ // pointer to the identical entry that already exists in the list
+ // (otherwise the function returns new_link).
+ ELIST_LINK *add_sorted_and_find(int comparator(const void*, const void*),
+ bool unique, ELIST_LINK* new_link);
+
+ // Same as above, but returns true if the new entry was inserted, false
+ // if the identical entry already existed in the list.
+ bool add_sorted(int comparator(const void*, const void*),
+ bool unique, ELIST_LINK* new_link) {
+ return (add_sorted_and_find(comparator, unique, new_link) == new_link);
+ }
void internal_dump ( //serialise each elem
FILE * f, //to this file
diff --git a/ccutil/elst2.cpp b/ccutil/elst2.cpp
index 11063fb4da..1820d923f2 100644
--- a/ccutil/elst2.cpp
+++ b/ccutil/elst2.cpp
@@ -100,8 +100,8 @@ void ELIST2::assign_to_sublist( //to this list
* Return count of elements on list
**********************************************************************/
-inT32 ELIST2::length() { //count elements
- ELIST2_ITERATOR it(this);
+inT32 ELIST2::length() const { // count elements
+ ELIST2_ITERATOR it(const_cast(this));
inT32 count = 0;
#ifndef NDEBUG
diff --git a/ccutil/elst2.h b/ccutil/elst2.h
index 42daa8b398..4ee7e6d7f6 100644
--- a/ccutil/elst2.h
+++ b/ccutil/elst2.h
@@ -110,12 +110,12 @@ class DLLSYM ELIST2
void (*zapper) (ELIST2_LINK *));
//ptr to zapper functn
- bool empty() { //is list empty?
+ bool empty() const { //is list empty?
return !last;
}
- bool singleton() {
- return last ? (last == last->next) : FALSE;
+ bool singleton() const {
+ return last ? (last == last->next) : false;
}
void shallow_copy( //dangerous!!
@@ -131,7 +131,7 @@ class DLLSYM ELIST2
ELIST2_ITERATOR *start_it, //from list start
ELIST2_ITERATOR *end_it); //from list end
- inT32 length(); //# elements in list
+ inT32 length() const; // # elements in list
void sort ( //sort elements
int comparator ( //comparison routine
@@ -179,13 +179,13 @@ class DLLSYM ELIST2_ITERATOR
ELIST2_LINK *prev; //prev element
ELIST2_LINK *current; //current element
ELIST2_LINK *next; //next element
- bool ex_current_was_last; //current extracted
+ BOOL8 ex_current_was_last; //current extracted
//was end of list
- bool ex_current_was_cycle_pt; //current extracted
+ BOOL8 ex_current_was_cycle_pt; //current extracted
//was cycle point
ELIST2_LINK *cycle_pt; //point we are cycling
//the list to.
- bool started_cycling; //Have we moved off
+ BOOL8 started_cycling; //Have we moved off
//the start?
ELIST2_LINK *extract_sublist( //from this current...
@@ -246,7 +246,7 @@ class DLLSYM ELIST2_ITERATOR
void mark_cycle_pt(); //remember current
- bool empty() { //is list empty?
+ BOOL8 empty() { //is list empty?
#ifndef NDEBUG
if (!list)
NO_LIST.error ("ELIST2_ITERATOR::empty", ABORT, NULL);
@@ -254,15 +254,15 @@ class DLLSYM ELIST2_ITERATOR
return list->empty ();
}
- bool current_extracted() { //current extracted?
+ BOOL8 current_extracted() { //current extracted?
return !current;
}
- bool at_first(); //Current is first?
+ BOOL8 at_first(); //Current is first?
- bool at_last(); //Current is last?
+ BOOL8 at_last(); //Current is last?
- bool cycled_list(); //Completed a cycle?
+ BOOL8 cycled_list(); //Completed a cycle?
void add_to_end( //add at end &
ELIST2_LINK *new_link); //dont move
@@ -750,7 +750,7 @@ inline void ELIST2_ITERATOR::mark_cycle_pt() {
*
**********************************************************************/
-inline bool ELIST2_ITERATOR::at_first() {
+inline BOOL8 ELIST2_ITERATOR::at_first() {
#ifndef NDEBUG
if (!this)
NULL_OBJECT.error ("ELIST2_ITERATOR::at_first", ABORT, NULL);
@@ -772,7 +772,7 @@ inline bool ELIST2_ITERATOR::at_first() {
*
**********************************************************************/
-inline bool ELIST2_ITERATOR::at_last() {
+inline BOOL8 ELIST2_ITERATOR::at_last() {
#ifndef NDEBUG
if (!this)
NULL_OBJECT.error ("ELIST2_ITERATOR::at_last", ABORT, NULL);
@@ -794,7 +794,7 @@ inline bool ELIST2_ITERATOR::at_last() {
*
**********************************************************************/
-inline bool ELIST2_ITERATOR::cycled_list() {
+inline BOOL8 ELIST2_ITERATOR::cycled_list() {
#ifndef NDEBUG
if (!this)
NULL_OBJECT.error ("ELIST2_ITERATOR::cycled_list", ABORT, NULL);
diff --git a/ccutil/errcode.cpp b/ccutil/errcode.cpp
index b2fd96c6e3..be27543106 100644
--- a/ccutil/errcode.cpp
+++ b/ccutil/errcode.cpp
@@ -92,7 +92,7 @@ const char *format, ... //special message
case DBG:
case TESSLOG:
return; //report only
- case EXIT:
+ case TESSEXIT:
//err_exit();
case ABORT:
// Create a deliberate segv as the stack trace is more useful that way.
diff --git a/ccutil/errcode.h b/ccutil/errcode.h
index 5d5602fd38..1eff6c2d64 100644
--- a/ccutil/errcode.h
+++ b/ccutil/errcode.h
@@ -24,8 +24,8 @@
/*Control parameters for error()*/
#define DBG -1 /*log without alert */
-#define TESSLOG 0 /*alert user */
-#define EXIT 1 /*exit after erro */
+#define TESSLOG 0 /*alert user */
+#define TESSEXIT 1 /*exit after erro */
#define ABORT 2 /*abort after error */
/* Explicit Error Abort codes */
diff --git a/ccutil/genericvector.h b/ccutil/genericvector.h
index e567ee11cc..7ee7275bda 100644
--- a/ccutil/genericvector.h
+++ b/ccutil/genericvector.h
@@ -21,8 +21,9 @@
#define TESSERACT_CCUTIL_GENERICVECTOR_H_
#include
+#include
-#include "callback.h"
+#include "tesscallback.h"
#include "errcode.h"
#include "helpers.h"
@@ -47,6 +48,9 @@ class GenericVector {
// Double the size of the internal array.
void double_the_size();
+ // Resizes to size and sets all values to t.
+ void init_to_size(int size, T t);
+
// Return the size used.
int size() const {
return size_used_;
@@ -80,6 +84,10 @@ class GenericVector {
int push_back(T object);
void operator+=(T t);
+ // Push an element in the front of the array
+ // Note: This function is O(n)
+ int push_front(T object);
+
// Set the value at the given index
void set(T t, int index);
@@ -90,17 +98,24 @@ class GenericVector {
// shifts the remaining elements to the left.
void remove(int index);
+ // Truncates the array to the given size by removing the end.
+ // If the current size is less, the array is not expanded.
+ void truncate(int size) {
+ if (size < size_used_)
+ size_used_ = size;
+ }
+
// Add a callback to be called to delete the elements when the array took
// their ownership.
- void set_clear_callback(Callback1* cb);
+ void set_clear_callback(TessCallback1* cb);
// Add a callback to be called to compare the elements when needed (contains,
// get_id, ...)
- void set_compare_callback(ResultCallback2* cb);
+ void set_compare_callback(TessResultCallback2* cb);
// Clear the array, calling the clear callback function if any.
- // All the owned Callbacks are also deleted.
- // If you don't want the Callbacks to be deleted, before calling clear, set
+ // All the owned callbacks are also deleted.
+ // If you don't want the callbacks to be deleted, before calling clear, set
// the callback to NULL.
virtual void clear();
@@ -113,13 +128,13 @@ class GenericVector {
void move(GenericVector* from);
// Read/Write the array to a file. This does _NOT_ read/write the callbacks.
- // The Callback given must be permanent since they will be called more than
+ // The callback given must be permanent since they will be called more than
// once. The given callback will be deleted at the end.
// If the callbacks are NULL, then the data is simply read/written using
// fread (and swapping)/fwrite.
// Returns false on error or if the callback returns false.
- bool write(FILE* f, ResultCallback2* cb) const;
- bool read(FILE* f, ResultCallback3* cb, bool swap);
+ bool write(FILE* f, TessResultCallback2* cb) const;
+ bool read(FILE* f, TessResultCallback3