mirror of
https://github.com/tesseract-ocr/tesseract.git
synced 2025-06-08 02:12:40 +08:00
Merge branch 'old_doxygen_merge' into more-doxygen
This commit is contained in:
commit
06190bad64
@ -1,6 +1,7 @@
|
|||||||
Note that this is a text-only and possibly out-of-date version of the
|
Note that this is a text-only and possibly out-of-date version of the
|
||||||
wiki ReadMe, which is located at:
|
wiki ReadMe, which is located at:
|
||||||
http://code.google.com/p/tesseract-ocr/wiki/ReadMe
|
|
||||||
|
https://github.com/tesseract-ocr/tesseract/blob/master/README
|
||||||
|
|
||||||
Introduction
|
Introduction
|
||||||
============
|
============
|
||||||
@ -10,15 +11,15 @@ Originally developed at Hewlett Packard Laboratories Bristol and
|
|||||||
at Hewlett Packard Co, Greeley Colorado, all the code
|
at Hewlett Packard Co, Greeley Colorado, all the code
|
||||||
in this distribution is now licensed under the Apache License:
|
in this distribution is now licensed under the Apache License:
|
||||||
|
|
||||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
** you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
** You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
** http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
** Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
** See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
** limitations under the License.
|
* limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
Dependencies and Licenses
|
Dependencies and Licenses
|
||||||
@ -56,7 +57,7 @@ those that want to do their own training. Most users should NOT download
|
|||||||
these files.
|
these files.
|
||||||
|
|
||||||
Instructions for using the training tools are documented separately at
|
Instructions for using the training tools are documented separately at
|
||||||
Tesseract wiki http://code.google.com/p/tesseract-ocr/w/list
|
Tesseract wiki https://github.com/tesseract-ocr/tesseract/wiki
|
||||||
|
|
||||||
|
|
||||||
Windows
|
Windows
|
||||||
@ -64,6 +65,9 @@ Windows
|
|||||||
|
|
||||||
Please use installer (for 3.00 and above). Tesseract is library with
|
Please use installer (for 3.00 and above). Tesseract is library with
|
||||||
command line interface. If you need GUI, please check AddOns wiki page
|
command line interface. If you need GUI, please check AddOns wiki page
|
||||||
|
|
||||||
|
TODO-UPDATE-WIKI-LINKS
|
||||||
|
|
||||||
http://code.google.com/p/tesseract-ocr/wiki/AddOns#GUI
|
http://code.google.com/p/tesseract-ocr/wiki/AddOns#GUI
|
||||||
|
|
||||||
If you are building from the sources, the recommended build platform is
|
If you are building from the sources, the recommended build platform is
|
||||||
@ -82,6 +86,9 @@ tesseract imagename outputbase [-l lang] [-psm pagesegmode] [configfiles...]
|
|||||||
|
|
||||||
If you need interface to other applications, please check wrapper section
|
If you need interface to other applications, please check wrapper section
|
||||||
on AddOns wiki page:
|
on AddOns wiki page:
|
||||||
|
|
||||||
|
TODO-UPDATE-WIKI-LINKS
|
||||||
|
|
||||||
http://code.google.com/p/tesseract-ocr/wiki/AddOns#Tesseract_3.0x
|
http://code.google.com/p/tesseract-ocr/wiki/AddOns#Tesseract_3.0x
|
||||||
|
|
||||||
|
|
||||||
@ -112,6 +119,10 @@ If you are linking to the libraries, as Ocropus does, please link to
|
|||||||
libtesseract_api.
|
libtesseract_api.
|
||||||
|
|
||||||
|
|
||||||
|
If you get `leptonica not found` and you've installed it with e.g. homebrew, you
|
||||||
|
can run `CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" ./configure`
|
||||||
|
instead of `./configure` above.
|
||||||
|
|
||||||
|
|
||||||
History
|
History
|
||||||
=======
|
=======
|
4
android/AndroidManifest.xml
Normal file
4
android/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<!--
|
||||||
|
This file is needed by the android_native_library rule to determine the
|
||||||
|
project directory for ndk-build.
|
||||||
|
-->
|
1
android/Makefile.am
Normal file
1
android/Makefile.am
Normal file
@ -0,0 +1 @@
|
|||||||
|
EXTRA_DIST = AndroidManifest.xml jni/Android.mk jni/Application.mk
|
57
android/jni/Android.mk
Normal file
57
android/jni/Android.mk
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE := tesseract-$(APP_ABI)
|
||||||
|
|
||||||
|
LOCAL_STATIC_LIBRARIES := \
|
||||||
|
mobile_base \
|
||||||
|
leptonica-$(APP_ABI)
|
||||||
|
|
||||||
|
LOCAL_C_INCLUDES := $(APP_C_INCLUDES)
|
||||||
|
|
||||||
|
LOCAL_C_INCLUDES += \
|
||||||
|
$(LOCAL_PATH)/../../api \
|
||||||
|
$(LOCAL_PATH)/../../ccmain\
|
||||||
|
$(LOCAL_PATH)/../../ccstruct\
|
||||||
|
$(LOCAL_PATH)/../../ccutil\
|
||||||
|
$(LOCAL_PATH)/../../classify\
|
||||||
|
$(LOCAL_PATH)/../../cutil\
|
||||||
|
$(LOCAL_PATH)/../../dict\
|
||||||
|
$(LOCAL_PATH)/../../image\
|
||||||
|
$(LOCAL_PATH)/../../textord\
|
||||||
|
$(LOCAL_PATH)/../../third_party\
|
||||||
|
$(LOCAL_PATH)/../../wordrec\
|
||||||
|
$(LOCAL_PATH)/../../opencl\
|
||||||
|
$(LOCAL_PATH)/../../viewer\
|
||||||
|
$(LOCAL_PATH)/../../../leptonica/include
|
||||||
|
|
||||||
|
$(info local c includes=$(LOCAL_C_INCLUDES))
|
||||||
|
$(info local path=$(LOCAL_PATH))
|
||||||
|
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/../../api/*.cpp $(LOCAL_PATH)/../../ccmain/*.cpp $(LOCAL_PATH)/../../ccstruct/*.cpp $(LOCAL_PATH)/../../ccutil/*.cpp $(LOCAL_PATH)/../../classify/*.cpp $(LOCAL_PATH)/../../cutil/*.cpp $(LOCAL_PATH)/../../dict/*.cpp $(LOCAL_PATH)/../../image/*.cpp $(LOCAL_PATH)/../../textord/*.cpp $(LOCAL_PATH)/../../viewer/*.cpp $(LOCAL_PATH)/../../wordrec/*.cpp)
|
||||||
|
|
||||||
|
EXPLICIT_SRC_EXCLUDES := \
|
||||||
|
$(LOCAL_PATH)/../../ccmain/cubeclassifier.cpp \
|
||||||
|
$(LOCAL_PATH)/../../ccmain/cubeclassifier.h \
|
||||||
|
$(LOCAL_PATH)/../../ccmain/cube_control.cpp \
|
||||||
|
$(LOCAL_PATH)/../../ccmain/cube_reco_context.cpp \
|
||||||
|
$(LOCAL_PATH)/../../ccmain/cube_reco_context.h \
|
||||||
|
$(LOCAL_PATH)/../../ccmain/tesseract_cube_combiner.cpp \
|
||||||
|
$(LOCAL_PATH)/../../ccmain/tesseract_cube_combiner.h \
|
||||||
|
$(LOCAL_PATH)/../../api/pdfrenderer.cpp \
|
||||||
|
$(LOCAL_PATH)/../../api/tesseractmain.cpp \
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := $(filter-out $(EXPLICIT_SRC_EXCLUDES), $(LOCAL_SRC_FILES))
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := $(LOCAL_SRC_FILES:$(LOCAL_PATH)/%=%)
|
||||||
|
|
||||||
|
$(info local src files = $(LOCAL_SRC_FILES))
|
||||||
|
|
||||||
|
LOCAL_LDLIBS := -ldl -llog -ljnigraphics
|
||||||
|
LOCAL_CFLAGS := -DANDROID_BUILD -DGRAPHICS_DISABLED
|
||||||
|
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
||||||
|
$(call import-module,mobile/base)
|
||||||
|
$(call import-module,mobile/base)
|
||||||
|
$(call import-module,mobile/util/hash)
|
||||||
|
$(call import-module,third_party/leptonica/android/jni)
|
13
android/jni/Application.mk
Normal file
13
android/jni/Application.mk
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Include common.mk for building google3 native code.
|
||||||
|
DEPOT_PATH := $(firstword $(subst /google3, ,$(abspath $(call my-dir))))
|
||||||
|
ifneq ($(wildcard $(DEPOT_PATH)/google3/mobile/build/common.mk),)
|
||||||
|
include $(DEPOT_PATH)/google3/mobile/build/common.mk
|
||||||
|
else
|
||||||
|
include $(DEPOT_PATH)/READONLY/google3/mobile/build/common.mk
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Specify the hash namespace that we're using, based on the APP_STL we're using.
|
||||||
|
APP_CFLAGS += -Werror -DHASH_NAMESPACE=__gnu_cxx -Wno-error=deprecated-register
|
||||||
|
APP_PLATFORM := android-16
|
||||||
|
APP_STL := gnustl_static
|
||||||
|
NDK_TOOLCHAIN_VERSION := clang
|
@ -66,7 +66,7 @@ libtesseract_la_LIBADD = \
|
|||||||
libtesseract_la_LDFLAGS += -version-info $(GENERIC_LIBRARY_VERSION)
|
libtesseract_la_LDFLAGS += -version-info $(GENERIC_LIBRARY_VERSION)
|
||||||
|
|
||||||
bin_PROGRAMS = tesseract
|
bin_PROGRAMS = tesseract
|
||||||
tesseract_SOURCES = $(top_srcdir)/api/tesseractmain.cpp
|
tesseract_SOURCES = tesseractmain.cpp
|
||||||
tesseract_CPPFLAGS = $(AM_CPPFLAGS)
|
tesseract_CPPFLAGS = $(AM_CPPFLAGS)
|
||||||
if VISIBILITY
|
if VISIBILITY
|
||||||
tesseract_CPPFLAGS += -DTESS_IMPORTS
|
tesseract_CPPFLAGS += -DTESS_IMPORTS
|
||||||
@ -78,7 +78,7 @@ if USE_OPENCL
|
|||||||
tesseract_LDADD += $(OPENCL_LIB)
|
tesseract_LDADD += $(OPENCL_LIB)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if MINGW
|
if T_WIN
|
||||||
tesseract_LDADD += -lws2_32
|
tesseract_LDADD += -lws2_32
|
||||||
libtesseract_la_LDFLAGS += -no-undefined -Wl,--as-needed -lws2_32
|
libtesseract_la_LDFLAGS += -no-undefined -Wl,--as-needed -lws2_32
|
||||||
endif
|
endif
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
#include "vcsversion.h"
|
||||||
#include "mathfix.h"
|
#include "mathfix.h"
|
||||||
#elif MINGW
|
#elif MINGW
|
||||||
// workaround for stdlib.h with -std=c++11 for _splitpath and _MAX_FNAME
|
// workaround for stdlib.h with -std=c++11 for _splitpath and _MAX_FNAME
|
||||||
@ -51,6 +52,7 @@
|
|||||||
#include "allheaders.h"
|
#include "allheaders.h"
|
||||||
|
|
||||||
#include "baseapi.h"
|
#include "baseapi.h"
|
||||||
|
#include "blobclass.h"
|
||||||
#include "resultiterator.h"
|
#include "resultiterator.h"
|
||||||
#include "mutableiterator.h"
|
#include "mutableiterator.h"
|
||||||
#include "thresholder.h"
|
#include "thresholder.h"
|
||||||
@ -138,7 +140,11 @@ TessBaseAPI::~TessBaseAPI() {
|
|||||||
* Returns the version identifier as a static string. Do not delete.
|
* Returns the version identifier as a static string. Do not delete.
|
||||||
*/
|
*/
|
||||||
const char* TessBaseAPI::Version() {
|
const char* TessBaseAPI::Version() {
|
||||||
|
#if defined(GIT_REV) && (defined(DEBUG) || defined(_DEBUG))
|
||||||
|
return GIT_REV;
|
||||||
|
#else
|
||||||
return TESSERACT_VERSION_STR;
|
return TESSERACT_VERSION_STR;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -741,6 +747,7 @@ void TessBaseAPI::DumpPGM(const char* filename) {
|
|||||||
fclose(fp);
|
fclose(fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
/**
|
/**
|
||||||
* Placeholder for call to Cube and test that the input data is correct.
|
* Placeholder for call to Cube and test that the input data is correct.
|
||||||
* reskew is the direction of baselines in the skewed image in
|
* reskew is the direction of baselines in the skewed image in
|
||||||
@ -785,6 +792,7 @@ int CubeAPITest(Boxa* boxa_blocks, Pixa* pixa_blocks,
|
|||||||
ASSERT_HOST(pr_word == word_count);
|
ASSERT_HOST(pr_word == word_count);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs page layout analysis in the mode set by SetPageSegMode.
|
* Runs page layout analysis in the mode set by SetPageSegMode.
|
||||||
@ -870,7 +878,9 @@ int TessBaseAPI::Recognize(ETEXT_DESC* monitor) {
|
|||||||
page_res_ = NULL;
|
page_res_ = NULL;
|
||||||
return -1;
|
return -1;
|
||||||
} else if (tesseract_->tessedit_train_from_boxes) {
|
} else if (tesseract_->tessedit_train_from_boxes) {
|
||||||
tesseract_->ApplyBoxTraining(*output_file_, page_res_);
|
STRING fontname;
|
||||||
|
ExtractFontName(*output_file_, &fontname);
|
||||||
|
tesseract_->ApplyBoxTraining(fontname, page_res_);
|
||||||
} else if (tesseract_->tessedit_ambigs_training) {
|
} else if (tesseract_->tessedit_ambigs_training) {
|
||||||
FILE *training_output_file = tesseract_->init_recog_training(*input_file_);
|
FILE *training_output_file = tesseract_->init_recog_training(*input_file_);
|
||||||
// OCR the page segmented into words by tesseract.
|
// OCR the page segmented into words by tesseract.
|
||||||
@ -1019,6 +1029,7 @@ bool TessBaseAPI::ProcessPagesMultipageTiff(const l_uint8 *data,
|
|||||||
int timeout_millisec,
|
int timeout_millisec,
|
||||||
TessResultRenderer* renderer,
|
TessResultRenderer* renderer,
|
||||||
int tessedit_page_number) {
|
int tessedit_page_number) {
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
Pix *pix = NULL;
|
Pix *pix = NULL;
|
||||||
#ifdef USE_OPENCL
|
#ifdef USE_OPENCL
|
||||||
OpenclDevice od;
|
OpenclDevice od;
|
||||||
@ -1049,6 +1060,26 @@ bool TessBaseAPI::ProcessPagesMultipageTiff(const l_uint8 *data,
|
|||||||
if (tessedit_page_number >= 0) break;
|
if (tessedit_page_number >= 0) break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Master ProcessPages calls ProcessPagesInternal and then does any post-
|
||||||
|
// processing required due to being in a training mode.
|
||||||
|
bool TessBaseAPI::ProcessPages(const char* filename, const char* retry_config,
|
||||||
|
int timeout_millisec,
|
||||||
|
TessResultRenderer* renderer) {
|
||||||
|
bool result =
|
||||||
|
ProcessPagesInternal(filename, retry_config, timeout_millisec, renderer);
|
||||||
|
if (result) {
|
||||||
|
if (tesseract_->tessedit_train_from_boxes &&
|
||||||
|
!tesseract_->WriteTRFile(*output_file_)) {
|
||||||
|
tprintf("Write of TR file failed: %s\n", output_file_->string());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the ideal scenario, Tesseract will start working on data as soon
|
// In the ideal scenario, Tesseract will start working on data as soon
|
||||||
@ -1063,9 +1094,11 @@ bool TessBaseAPI::ProcessPagesMultipageTiff(const l_uint8 *data,
|
|||||||
// identify the scenario that really matters: filelists on
|
// identify the scenario that really matters: filelists on
|
||||||
// stdin. We'll still do our best if the user likes pipes. That means
|
// stdin. We'll still do our best if the user likes pipes. That means
|
||||||
// piling up any data coming into stdin into a memory buffer.
|
// piling up any data coming into stdin into a memory buffer.
|
||||||
bool TessBaseAPI::ProcessPages(const char* filename,
|
bool TessBaseAPI::ProcessPagesInternal(const char* filename,
|
||||||
const char* retry_config, int timeout_millisec,
|
const char* retry_config,
|
||||||
TessResultRenderer* renderer) {
|
int timeout_millisec,
|
||||||
|
TessResultRenderer* renderer) {
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
PERF_COUNT_START("ProcessPages")
|
PERF_COUNT_START("ProcessPages")
|
||||||
bool stdInput = !strcmp(filename, "stdin") || !strcmp(filename, "-");
|
bool stdInput = !strcmp(filename, "stdin") || !strcmp(filename, "-");
|
||||||
if (stdInput) {
|
if (stdInput) {
|
||||||
@ -1153,6 +1186,9 @@ bool TessBaseAPI::ProcessPages(const char* filename,
|
|||||||
}
|
}
|
||||||
PERF_COUNT_END
|
PERF_COUNT_END
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TessBaseAPI::ProcessPage(Pix* pix, int page_index, const char* filename,
|
bool TessBaseAPI::ProcessPage(Pix* pix, int page_index, const char* filename,
|
||||||
@ -1186,8 +1222,10 @@ bool TessBaseAPI::ProcessPage(Pix* pix, int page_index, const char* filename,
|
|||||||
failed = Recognize(NULL) < 0;
|
failed = Recognize(NULL) < 0;
|
||||||
}
|
}
|
||||||
if (tesseract_->tessedit_write_images) {
|
if (tesseract_->tessedit_write_images) {
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
Pix* page_pix = GetThresholdedImage();
|
Pix* page_pix = GetThresholdedImage();
|
||||||
pixWrite("tessinput.tif", page_pix, IFF_TIFF_G4);
|
pixWrite("tessinput.tif", page_pix, IFF_TIFF_G4);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
if (failed && retry_config != NULL && retry_config[0] != '\0') {
|
if (failed && retry_config != NULL && retry_config[0] != '\0') {
|
||||||
// Save current config variables before switching modes.
|
// Save current config variables before switching modes.
|
||||||
@ -1477,11 +1515,7 @@ char* TessBaseAPI::GetHOCRText(int page_number) {
|
|||||||
do {
|
do {
|
||||||
const char *grapheme = res_it->GetUTF8Text(RIL_SYMBOL);
|
const char *grapheme = res_it->GetUTF8Text(RIL_SYMBOL);
|
||||||
if (grapheme && grapheme[0] != 0) {
|
if (grapheme && grapheme[0] != 0) {
|
||||||
if (grapheme[1] == 0) {
|
hocr_str += HOcrEscape(grapheme);
|
||||||
hocr_str += HOcrEscape(grapheme);
|
|
||||||
} else {
|
|
||||||
hocr_str += grapheme;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
delete []grapheme;
|
delete []grapheme;
|
||||||
res_it->Next(RIL_SYMBOL);
|
res_it->Next(RIL_SYMBOL);
|
||||||
@ -1892,6 +1926,10 @@ void TessBaseAPI::ClearPersistentCache() {
|
|||||||
int TessBaseAPI::IsValidWord(const char *word) {
|
int TessBaseAPI::IsValidWord(const char *word) {
|
||||||
return tesseract_->getDict().valid_word(word);
|
return tesseract_->getDict().valid_word(word);
|
||||||
}
|
}
|
||||||
|
// Returns true if utf8_character is defined in the UniCharset.
|
||||||
|
bool TessBaseAPI::IsValidCharacter(const char *utf8_character) {
|
||||||
|
return tesseract_->unicharset.contains_unichar(utf8_character);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO(rays) Obsolete this function and replace with a more aptly named
|
// TODO(rays) Obsolete this function and replace with a more aptly named
|
||||||
@ -1941,8 +1979,8 @@ void TessBaseAPI::SetDictFunc(DictFunc f) {
|
|||||||
* Sets Dict::probability_in_context_ function to point to the given
|
* Sets Dict::probability_in_context_ function to point to the given
|
||||||
* function.
|
* function.
|
||||||
*
|
*
|
||||||
* @param f A single function that returns the probability of the current
|
* @param f A single function that returns the probability of the current
|
||||||
* "character" (in general a utf-8 string), given the context of a previous
|
* "character" (in general a utf-8 string), given the context of a previous
|
||||||
* utf-8 string.
|
* utf-8 string.
|
||||||
*/
|
*/
|
||||||
void TessBaseAPI::SetProbabilityInContextFunc(ProbabilityInContextFunc f) {
|
void TessBaseAPI::SetProbabilityInContextFunc(ProbabilityInContextFunc f) {
|
||||||
@ -2592,10 +2630,12 @@ int TessBaseAPI::NumDawgs() const {
|
|||||||
return tesseract_ == NULL ? 0 : tesseract_->getDict().NumDawgs();
|
return tesseract_ == NULL ? 0 : tesseract_->getDict().NumDawgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
/** Return a pointer to underlying CubeRecoContext object if present. */
|
/** Return a pointer to underlying CubeRecoContext object if present. */
|
||||||
CubeRecoContext *TessBaseAPI::GetCubeRecoContext() const {
|
CubeRecoContext *TessBaseAPI::GetCubeRecoContext() const {
|
||||||
return (tesseract_ == NULL) ? NULL : tesseract_->GetCubeRecoContext();
|
return (tesseract_ == NULL) ? NULL : tesseract_->GetCubeRecoContext();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/** Escape a char string - remove <>&"' with HTML codes. */
|
/** Escape a char string - remove <>&"' with HTML codes. */
|
||||||
STRING HOcrEscape(const char* text) {
|
STRING HOcrEscape(const char* text) {
|
||||||
|
@ -538,9 +538,11 @@ class TESS_API TessBaseAPI {
|
|||||||
*
|
*
|
||||||
* Returns true if successful, false on error.
|
* Returns true if successful, false on error.
|
||||||
*/
|
*/
|
||||||
bool ProcessPages(const char* filename,
|
bool ProcessPages(const char* filename, const char* retry_config,
|
||||||
const char* retry_config, int timeout_millisec,
|
int timeout_millisec, TessResultRenderer* renderer);
|
||||||
TessResultRenderer* renderer);
|
// Does the real work of ProcessPages.
|
||||||
|
bool ProcessPagesInternal(const char* filename, const char* retry_config,
|
||||||
|
int timeout_millisec, TessResultRenderer* renderer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn a single image into symbolic text.
|
* Turn a single image into symbolic text.
|
||||||
@ -656,6 +658,9 @@ class TESS_API TessBaseAPI {
|
|||||||
* in a separate API at some future time.
|
* in a separate API at some future time.
|
||||||
*/
|
*/
|
||||||
int IsValidWord(const char *word);
|
int IsValidWord(const char *word);
|
||||||
|
// Returns true if utf8_character is defined in the UniCharset.
|
||||||
|
bool IsValidCharacter(const char *utf8_character);
|
||||||
|
|
||||||
|
|
||||||
bool GetTextDirection(int* out_offset, float* out_slope);
|
bool GetTextDirection(int* out_offset, float* out_slope);
|
||||||
|
|
||||||
|
14
api/capi.cpp
14
api/capi.cpp
@ -667,6 +667,18 @@ TESS_API void TESS_CALL TessPageIteratorOrientation(TessPageIterator* handle, Te
|
|||||||
handle->Orientation(orientation, writing_direction, textline_order, deskew_angle);
|
handle->Orientation(orientation, writing_direction, textline_order, deskew_angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TESS_API void TESS_CALL TessPageIteratorParagraphInfo(TessPageIterator* handle, TessParagraphJustification* justification,
|
||||||
|
BOOL *is_list_item, BOOL *is_crown, int *first_line_indent)
|
||||||
|
{
|
||||||
|
bool bool_is_list_item, bool_is_crown;
|
||||||
|
handle->ParagraphInfo(justification, &bool_is_list_item, &bool_is_crown, first_line_indent);
|
||||||
|
if (is_list_item)
|
||||||
|
*is_list_item = bool_is_list_item ? TRUE : FALSE;
|
||||||
|
if (is_crown)
|
||||||
|
*is_crown = bool_is_crown ? TRUE : FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TESS_API void TESS_CALL TessResultIteratorDelete(TessResultIterator* handle)
|
TESS_API void TESS_CALL TessResultIteratorDelete(TessResultIterator* handle)
|
||||||
{
|
{
|
||||||
delete handle;
|
delete handle;
|
||||||
@ -687,7 +699,7 @@ TESS_API const TessPageIterator* TESS_CALL TessResultIteratorGetPageIteratorCons
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
TESS_API const TessChoiceIterator* TESS_CALL TessResultIteratorGetChoiceIterator(const TessResultIterator* handle)
|
TESS_API TessChoiceIterator* TESS_CALL TessResultIteratorGetChoiceIterator(const TessResultIterator* handle)
|
||||||
{
|
{
|
||||||
return new TessChoiceIterator(*handle);
|
return new TessChoiceIterator(*handle);
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ typedef tesseract::Dawg TessDawg;
|
|||||||
typedef tesseract::TruthCallback TessTruthCallback;
|
typedef tesseract::TruthCallback TessTruthCallback;
|
||||||
typedef tesseract::CubeRecoContext TessCubeRecoContext;
|
typedef tesseract::CubeRecoContext TessCubeRecoContext;
|
||||||
typedef tesseract::Orientation TessOrientation;
|
typedef tesseract::Orientation TessOrientation;
|
||||||
|
typedef tesseract::ParagraphJustification TessParagraphJustification;
|
||||||
typedef tesseract::WritingDirection TessWritingDirection;
|
typedef tesseract::WritingDirection TessWritingDirection;
|
||||||
typedef tesseract::TextlineOrder TessTextlineOrder;
|
typedef tesseract::TextlineOrder TessTextlineOrder;
|
||||||
typedef PolyBlockType TessPolyBlockType;
|
typedef PolyBlockType TessPolyBlockType;
|
||||||
@ -77,6 +78,7 @@ typedef enum TessPolyBlockType { PT_UNKNOWN, PT_FLOWING_TEXT, PT_HEADING_TEX
|
|||||||
PT_TABLE, PT_VERTICAL_TEXT, PT_CAPTION_TEXT, PT_FLOWING_IMAGE, PT_HEADING_IMAGE,
|
PT_TABLE, PT_VERTICAL_TEXT, PT_CAPTION_TEXT, PT_FLOWING_IMAGE, PT_HEADING_IMAGE,
|
||||||
PT_PULLOUT_IMAGE, PT_HORZ_LINE, PT_VERT_LINE, PT_NOISE, PT_COUNT } TessPolyBlockType;
|
PT_PULLOUT_IMAGE, PT_HORZ_LINE, PT_VERT_LINE, PT_NOISE, PT_COUNT } TessPolyBlockType;
|
||||||
typedef enum TessOrientation { ORIENTATION_PAGE_UP, ORIENTATION_PAGE_RIGHT, ORIENTATION_PAGE_DOWN, ORIENTATION_PAGE_LEFT } TessOrientation;
|
typedef enum TessOrientation { ORIENTATION_PAGE_UP, ORIENTATION_PAGE_RIGHT, ORIENTATION_PAGE_DOWN, ORIENTATION_PAGE_LEFT } TessOrientation;
|
||||||
|
typedef enum TessParagraphJustification { JUSTIFICATION_UNKNOWN, JUSTIFICATION_LEFT, JUSTIFICATION_CENTER, JUSTIFICATION_RIGHT } TessParagraphJustification;
|
||||||
typedef enum TessWritingDirection { WRITING_DIRECTION_LEFT_TO_RIGHT, WRITING_DIRECTION_RIGHT_TO_LEFT, WRITING_DIRECTION_TOP_TO_BOTTOM } TessWritingDirection;
|
typedef enum TessWritingDirection { WRITING_DIRECTION_LEFT_TO_RIGHT, WRITING_DIRECTION_RIGHT_TO_LEFT, WRITING_DIRECTION_TOP_TO_BOTTOM } TessWritingDirection;
|
||||||
typedef enum TessTextlineOrder { TEXTLINE_ORDER_LEFT_TO_RIGHT, TEXTLINE_ORDER_RIGHT_TO_LEFT, TEXTLINE_ORDER_TOP_TO_BOTTOM } TessTextlineOrder;
|
typedef enum TessTextlineOrder { TEXTLINE_ORDER_LEFT_TO_RIGHT, TEXTLINE_ORDER_RIGHT_TO_LEFT, TEXTLINE_ORDER_TOP_TO_BOTTOM } TessTextlineOrder;
|
||||||
typedef struct ETEXT_DESC ETEXT_DESC;
|
typedef struct ETEXT_DESC ETEXT_DESC;
|
||||||
@ -299,7 +301,7 @@ TESS_API TessCubeRecoContext*
|
|||||||
|
|
||||||
TESS_API void TESS_CALL TessBaseAPISetMinOrientationMargin(TessBaseAPI* handle, double margin);
|
TESS_API void TESS_CALL TessBaseAPISetMinOrientationMargin(TessBaseAPI* handle, double margin);
|
||||||
#ifdef TESS_CAPI_INCLUDE_BASEAPI
|
#ifdef TESS_CAPI_INCLUDE_BASEAPI
|
||||||
TESS_API void TESS_CALL TessBaseGetBlockTextOrientations(TessBaseAPI* handle, int** block_orientation, bool** vertical_writing);
|
TESS_API void TESS_CALL TessBaseGetBlockTextOrientations(TessBaseAPI* handle, int** block_orientation, BOOL** vertical_writing);
|
||||||
|
|
||||||
TESS_API BLOCK_LIST*
|
TESS_API BLOCK_LIST*
|
||||||
TESS_CALL TessBaseAPIFindLinesCreateBlockList(TessBaseAPI* handle);
|
TESS_CALL TessBaseAPIFindLinesCreateBlockList(TessBaseAPI* handle);
|
||||||
@ -335,6 +337,9 @@ TESS_API void TESS_CALL TessPageIteratorOrientation(TessPageIterator* handle, T
|
|||||||
TessWritingDirection* writing_direction, TessTextlineOrder* textline_order,
|
TessWritingDirection* writing_direction, TessTextlineOrder* textline_order,
|
||||||
float* deskew_angle);
|
float* deskew_angle);
|
||||||
|
|
||||||
|
TESS_API void TESS_CALL TessPageIteratorParagraphInfo(TessPageIterator* handle, TessParagraphJustification* justification,
|
||||||
|
BOOL *is_list_item, BOOL *is_crown, int *first_line_indent);
|
||||||
|
|
||||||
/* Result iterator */
|
/* Result iterator */
|
||||||
|
|
||||||
TESS_API void TESS_CALL TessResultIteratorDelete(TessResultIterator* handle);
|
TESS_API void TESS_CALL TessResultIteratorDelete(TessResultIterator* handle);
|
||||||
@ -344,7 +349,7 @@ TESS_API TessPageIterator*
|
|||||||
TESS_CALL TessResultIteratorGetPageIterator(TessResultIterator* handle);
|
TESS_CALL TessResultIteratorGetPageIterator(TessResultIterator* handle);
|
||||||
TESS_API const TessPageIterator*
|
TESS_API const TessPageIterator*
|
||||||
TESS_CALL TessResultIteratorGetPageIteratorConst(const TessResultIterator* handle);
|
TESS_CALL TessResultIteratorGetPageIteratorConst(const TessResultIterator* handle);
|
||||||
TESS_API const TessChoiceIterator*
|
TESS_API TessChoiceIterator*
|
||||||
TESS_CALL TessResultIteratorGetChoiceIterator(const TessResultIterator* handle);
|
TESS_CALL TessResultIteratorGetChoiceIterator(const TessResultIterator* handle);
|
||||||
|
|
||||||
TESS_API BOOL TESS_CALL TessResultIteratorNext(TessResultIterator* handle, TessPageIteratorLevel level);
|
TESS_API BOOL TESS_CALL TessResultIteratorNext(TessResultIterator* handle, TessPageIteratorLevel level);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -114,6 +114,13 @@ bool TessTextRenderer::AddImageHandler(TessBaseAPI* api) {
|
|||||||
AppendString(utf8);
|
AppendString(utf8);
|
||||||
delete[] utf8;
|
delete[] utf8;
|
||||||
|
|
||||||
|
bool pageBreak = false;
|
||||||
|
api->GetBoolVariable("include_page_breaks", &pageBreak);
|
||||||
|
const char* pageSeparator = api->GetStringVariable("page_separator");
|
||||||
|
if (pageBreak) {
|
||||||
|
AppendString(pageSeparator);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,9 +194,8 @@ private:
|
|||||||
static char* GetPDFTextObjects(TessBaseAPI* api,
|
static char* GetPDFTextObjects(TessBaseAPI* api,
|
||||||
double width, double height);
|
double width, double height);
|
||||||
// Turn an image into a PDF object. Only transcode if we have to.
|
// Turn an image into a PDF object. Only transcode if we have to.
|
||||||
static bool imageToPDFObj(tesseract::TessBaseAPI *api, Pix *pix,
|
static bool imageToPDFObj(Pix *pix, char *filename, long int objnum,
|
||||||
char *filename, long int objnum, char **pdf_object,
|
char **pdf_object, long int *pdf_object_size);
|
||||||
long int *pdf_object_size);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -287,36 +287,38 @@ int main(int argc, char **argv) {
|
|||||||
exit(ret_val);
|
exit(ret_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
tesseract::TessResultRenderer* renderer = NULL;
|
|
||||||
bool b;
|
bool b;
|
||||||
|
tesseract::PointerVector<tesseract::TessResultRenderer> renderers;
|
||||||
api.GetBoolVariable("tessedit_create_hocr", &b);
|
api.GetBoolVariable("tessedit_create_hocr", &b);
|
||||||
if (b) {
|
if (b) {
|
||||||
bool font_info;
|
bool font_info;
|
||||||
api.GetBoolVariable("hocr_font_info", &font_info);
|
api.GetBoolVariable("hocr_font_info", &font_info);
|
||||||
renderer = new tesseract::TessHOcrRenderer(outputbase, font_info);
|
renderers.push_back(new tesseract::TessHOcrRenderer(outputbase, font_info));
|
||||||
}
|
}
|
||||||
|
|
||||||
api.GetBoolVariable("tessedit_create_pdf", &b);
|
api.GetBoolVariable("tessedit_create_pdf", &b);
|
||||||
if (b && renderer == NULL)
|
if (b) {
|
||||||
renderer = new tesseract::TessPDFRenderer(outputbase, api.GetDatapath());
|
renderers.push_back(new tesseract::TessPDFRenderer(outputbase,
|
||||||
|
api.GetDatapath()));
|
||||||
api.GetBoolVariable("tessedit_write_unlv", &b);
|
|
||||||
if (b && renderer == NULL)
|
|
||||||
renderer = new tesseract::TessUnlvRenderer(outputbase);
|
|
||||||
|
|
||||||
api.GetBoolVariable("tessedit_create_boxfile", &b);
|
|
||||||
if (b && renderer == NULL)
|
|
||||||
renderer = new tesseract::TessBoxTextRenderer(outputbase);
|
|
||||||
|
|
||||||
if (renderer == NULL)
|
|
||||||
renderer = new tesseract::TessTextRenderer(outputbase);
|
|
||||||
|
|
||||||
if (!api.ProcessPages(image, NULL, 0, renderer)) {
|
|
||||||
delete renderer;
|
|
||||||
fprintf(stderr, "Error during processing.\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
delete renderer;
|
api.GetBoolVariable("tessedit_write_unlv", &b);
|
||||||
|
if (b) renderers.push_back(new tesseract::TessUnlvRenderer(outputbase));
|
||||||
|
api.GetBoolVariable("tessedit_create_boxfile", &b);
|
||||||
|
if (b) renderers.push_back(new tesseract::TessBoxTextRenderer(outputbase));
|
||||||
|
api.GetBoolVariable("tessedit_create_txt", &b);
|
||||||
|
if (b) renderers.push_back(new tesseract::TessTextRenderer(outputbase));
|
||||||
|
if (!renderers.empty()) {
|
||||||
|
// Since the PointerVector auto-deletes, null-out the renderers that are
|
||||||
|
// added to the root, and leave the root in the vector.
|
||||||
|
for (int r = 1; r < renderers.size(); ++r) {
|
||||||
|
renderers[0]->insert(renderers[r]);
|
||||||
|
renderers[r] = NULL;
|
||||||
|
}
|
||||||
|
if (!api.ProcessPages(image, NULL, 0, renderers[0])) {
|
||||||
|
fprintf(stderr, "Error during processing.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PERF_COUNT_END
|
PERF_COUNT_END
|
||||||
return 0; // Normal exit
|
return 0; // Normal exit
|
||||||
}
|
}
|
||||||
|
@ -272,7 +272,7 @@ void Tesseract::MaximallyChopWord(const GenericVector<TBOX>& boxes,
|
|||||||
// limited by the ability of the chopper to find suitable chop points,
|
// limited by the ability of the chopper to find suitable chop points,
|
||||||
// and not by the value of the certainties.
|
// and not by the value of the certainties.
|
||||||
BLOB_CHOICE* choice =
|
BLOB_CHOICE* choice =
|
||||||
new BLOB_CHOICE(0, rating, -rating, -1, -1, 0, 0, 0, 0, BCC_FAKE);
|
new BLOB_CHOICE(0, rating, -rating, -1, 0.0f, 0.0f, 0.0f, BCC_FAKE);
|
||||||
blob_choices.push_back(choice);
|
blob_choices.push_back(choice);
|
||||||
rating -= 0.125f;
|
rating -= 0.125f;
|
||||||
}
|
}
|
||||||
@ -291,8 +291,8 @@ void Tesseract::MaximallyChopWord(const GenericVector<TBOX>& boxes,
|
|||||||
left_choice->set_certainty(-rating);
|
left_choice->set_certainty(-rating);
|
||||||
// combine confidence w/ serial #
|
// combine confidence w/ serial #
|
||||||
BLOB_CHOICE* right_choice = new BLOB_CHOICE(++right_chop_index,
|
BLOB_CHOICE* right_choice = new BLOB_CHOICE(++right_chop_index,
|
||||||
rating - 0.125f, -rating,
|
rating - 0.125f, -rating, -1,
|
||||||
-1, -1, 0, 0, 0, 0, BCC_FAKE);
|
0.0f, 0.0f, 0.0f, BCC_FAKE);
|
||||||
blob_choices.insert(right_choice, blob_number + 1);
|
blob_choices.insert(right_choice, blob_number + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -582,7 +582,7 @@ bool Tesseract::FindSegmentation(const GenericVector<UNICHAR_ID>& target_text,
|
|||||||
int blob_count = 1;
|
int blob_count = 1;
|
||||||
for (int s = 0; s < word_res->seam_array.size(); ++s) {
|
for (int s = 0; s < word_res->seam_array.size(); ++s) {
|
||||||
SEAM* seam = word_res->seam_array[s];
|
SEAM* seam = word_res->seam_array[s];
|
||||||
if (seam->split1 == NULL) {
|
if (!seam->HasAnySplits()) {
|
||||||
word_res->best_state.push_back(blob_count);
|
word_res->best_state.push_back(blob_count);
|
||||||
blob_count = 1;
|
blob_count = 1;
|
||||||
} else {
|
} else {
|
||||||
@ -775,13 +775,13 @@ void Tesseract::CorrectClassifyWords(PAGE_RES* page_res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calls LearnWord to extract features for labelled blobs within each word.
|
// Calls LearnWord to extract features for labelled blobs within each word.
|
||||||
// Features are written to the given filename.
|
// Features are stored in an internal buffer.
|
||||||
void Tesseract::ApplyBoxTraining(const STRING& filename, PAGE_RES* page_res) {
|
void Tesseract::ApplyBoxTraining(const STRING& fontname, PAGE_RES* page_res) {
|
||||||
PAGE_RES_IT pr_it(page_res);
|
PAGE_RES_IT pr_it(page_res);
|
||||||
int word_count = 0;
|
int word_count = 0;
|
||||||
for (WERD_RES *word_res = pr_it.word(); word_res != NULL;
|
for (WERD_RES *word_res = pr_it.word(); word_res != NULL;
|
||||||
word_res = pr_it.forward()) {
|
word_res = pr_it.forward()) {
|
||||||
LearnWord(filename.string(), word_res);
|
LearnWord(fontname.string(), word_res);
|
||||||
++word_count;
|
++word_count;
|
||||||
}
|
}
|
||||||
tprintf("Generated training data for %d words\n", word_count);
|
tprintf("Generated training data for %d words\n", word_count);
|
||||||
|
@ -93,8 +93,7 @@ BOOL8 Tesseract::recog_interactive(PAGE_RES_IT* pr_it) {
|
|||||||
|
|
||||||
WordData word_data(*pr_it);
|
WordData word_data(*pr_it);
|
||||||
SetupWordPassN(2, &word_data);
|
SetupWordPassN(2, &word_data);
|
||||||
classify_word_and_language(&Tesseract::classify_word_pass2, pr_it,
|
classify_word_and_language(2, pr_it, &word_data);
|
||||||
&word_data);
|
|
||||||
if (tessedit_debug_quality_metrics) {
|
if (tessedit_debug_quality_metrics) {
|
||||||
WERD_RES* word_res = pr_it->word();
|
WERD_RES* word_res = pr_it->word();
|
||||||
word_char_quality(word_res, pr_it->row()->row, &char_qual, &good_char_qual);
|
word_char_quality(word_res, pr_it->row()->row, &char_qual, &good_char_qual);
|
||||||
@ -190,6 +189,7 @@ void Tesseract::SetupWordPassN(int pass_n, WordData* word) {
|
|||||||
if (word->word->x_height == 0.0f)
|
if (word->word->x_height == 0.0f)
|
||||||
word->word->x_height = word->row->x_height();
|
word->word->x_height = word->row->x_height();
|
||||||
}
|
}
|
||||||
|
word->lang_words.truncate(0);
|
||||||
for (int s = 0; s <= sub_langs_.size(); ++s) {
|
for (int s = 0; s <= sub_langs_.size(); ++s) {
|
||||||
// The sub_langs_.size() entry is for the master language.
|
// The sub_langs_.size() entry is for the master language.
|
||||||
Tesseract* lang_t = s < sub_langs_.size() ? sub_langs_[s] : this;
|
Tesseract* lang_t = s < sub_langs_.size() ? sub_langs_[s] : this;
|
||||||
@ -249,15 +249,22 @@ bool Tesseract::RecogAllWordsPassN(int pass_n, ETEXT_DESC* monitor,
|
|||||||
while (pr_it->word() != NULL && pr_it->word() != word->word)
|
while (pr_it->word() != NULL && pr_it->word() != word->word)
|
||||||
pr_it->forward();
|
pr_it->forward();
|
||||||
ASSERT_HOST(pr_it->word() != NULL);
|
ASSERT_HOST(pr_it->word() != NULL);
|
||||||
WordRecognizer recognizer = pass_n == 1 ? &Tesseract::classify_word_pass1
|
bool make_next_word_fuzzy = false;
|
||||||
: &Tesseract::classify_word_pass2;
|
if (ReassignDiacritics(pass_n, pr_it, &make_next_word_fuzzy)) {
|
||||||
classify_word_and_language(recognizer, pr_it, word);
|
// Needs to be setup again to see the new outlines in the chopped_word.
|
||||||
if (tessedit_dump_choices) {
|
SetupWordPassN(pass_n, word);
|
||||||
|
}
|
||||||
|
|
||||||
|
classify_word_and_language(pass_n, pr_it, word);
|
||||||
|
if (tessedit_dump_choices || debug_noise_removal) {
|
||||||
tprintf("Pass%d: %s [%s]\n", pass_n,
|
tprintf("Pass%d: %s [%s]\n", pass_n,
|
||||||
word->word->best_choice->unichar_string().string(),
|
word->word->best_choice->unichar_string().string(),
|
||||||
word->word->best_choice->debug_string().string());
|
word->word->best_choice->debug_string().string());
|
||||||
}
|
}
|
||||||
pr_it->forward();
|
pr_it->forward();
|
||||||
|
if (make_next_word_fuzzy && pr_it->word() != NULL) {
|
||||||
|
pr_it->MakeCurrentWordFuzzy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -357,7 +364,7 @@ bool Tesseract::recog_all_words(PAGE_RES* page_res,
|
|||||||
|
|
||||||
// ****************** Pass 2 *******************
|
// ****************** Pass 2 *******************
|
||||||
if (tessedit_tess_adaption_mode != 0x0 && !tessedit_test_adaption &&
|
if (tessedit_tess_adaption_mode != 0x0 && !tessedit_test_adaption &&
|
||||||
tessedit_ocr_engine_mode != OEM_CUBE_ONLY ) {
|
AnyTessLang()) {
|
||||||
page_res_it.restart_page();
|
page_res_it.restart_page();
|
||||||
GenericVector<WordData> words;
|
GenericVector<WordData> words;
|
||||||
SetupAllWordsPassN(2, target_word_box, word_config, page_res, &words);
|
SetupAllWordsPassN(2, target_word_box, word_config, page_res, &words);
|
||||||
@ -371,8 +378,7 @@ bool Tesseract::recog_all_words(PAGE_RES* page_res,
|
|||||||
|
|
||||||
// The next passes can only be run if tesseract has been used, as cube
|
// The next passes can only be run if tesseract has been used, as cube
|
||||||
// doesn't set all the necessary outputs in WERD_RES.
|
// doesn't set all the necessary outputs in WERD_RES.
|
||||||
if (tessedit_ocr_engine_mode == OEM_TESSERACT_ONLY ||
|
if (AnyTessLang()) {
|
||||||
tessedit_ocr_engine_mode == OEM_TESSERACT_CUBE_COMBINED) {
|
|
||||||
// ****************** Pass 3 *******************
|
// ****************** Pass 3 *******************
|
||||||
// Fix fuzzy spaces.
|
// Fix fuzzy spaces.
|
||||||
set_global_loc_code(LOC_FUZZY_SPACE);
|
set_global_loc_code(LOC_FUZZY_SPACE);
|
||||||
@ -388,12 +394,14 @@ bool Tesseract::recog_all_words(PAGE_RES* page_res,
|
|||||||
// ****************** Pass 5,6 *******************
|
// ****************** Pass 5,6 *******************
|
||||||
rejection_passes(page_res, monitor, target_word_box, word_config);
|
rejection_passes(page_res, monitor, target_word_box, word_config);
|
||||||
|
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
// ****************** Pass 7 *******************
|
// ****************** Pass 7 *******************
|
||||||
// Cube combiner.
|
// Cube combiner.
|
||||||
// If cube is loaded and its combiner is present, run it.
|
// If cube is loaded and its combiner is present, run it.
|
||||||
if (tessedit_ocr_engine_mode == OEM_TESSERACT_CUBE_COMBINED) {
|
if (tessedit_ocr_engine_mode == OEM_TESSERACT_CUBE_COMBINED) {
|
||||||
run_cube_combiner(page_res);
|
run_cube_combiner(page_res);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// ****************** Pass 8 *******************
|
// ****************** Pass 8 *******************
|
||||||
font_recognition_pass(page_res);
|
font_recognition_pass(page_res);
|
||||||
@ -897,6 +905,359 @@ static bool WordsAcceptable(const PointerVector<WERD_RES>& words) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Moves good-looking "noise"/diacritics from the reject list to the main
|
||||||
|
// blob list on the current word. Returns true if anything was done, and
|
||||||
|
// sets make_next_word_fuzzy if blob(s) were added to the end of the word.
|
||||||
|
bool Tesseract::ReassignDiacritics(int pass, PAGE_RES_IT* pr_it,
|
||||||
|
bool* make_next_word_fuzzy) {
|
||||||
|
*make_next_word_fuzzy = false;
|
||||||
|
WERD* real_word = pr_it->word()->word;
|
||||||
|
if (real_word->rej_cblob_list()->empty() ||
|
||||||
|
real_word->cblob_list()->empty() ||
|
||||||
|
real_word->rej_cblob_list()->length() > noise_maxperword)
|
||||||
|
return false;
|
||||||
|
real_word->rej_cblob_list()->sort(&C_BLOB::SortByXMiddle);
|
||||||
|
// Get the noise outlines into a vector with matching bool map.
|
||||||
|
GenericVector<C_OUTLINE*> outlines;
|
||||||
|
real_word->GetNoiseOutlines(&outlines);
|
||||||
|
GenericVector<bool> word_wanted;
|
||||||
|
GenericVector<bool> overlapped_any_blob;
|
||||||
|
GenericVector<C_BLOB*> target_blobs;
|
||||||
|
AssignDiacriticsToOverlappingBlobs(outlines, pass, real_word, pr_it,
|
||||||
|
&word_wanted, &overlapped_any_blob,
|
||||||
|
&target_blobs);
|
||||||
|
// Filter the outlines that overlapped any blob and put them into the word
|
||||||
|
// now. This simplifies the remaining task and also makes it more accurate
|
||||||
|
// as it has more completed blobs to work on.
|
||||||
|
GenericVector<bool> wanted;
|
||||||
|
GenericVector<C_BLOB*> wanted_blobs;
|
||||||
|
GenericVector<C_OUTLINE*> wanted_outlines;
|
||||||
|
int num_overlapped = 0;
|
||||||
|
int num_overlapped_used = 0;
|
||||||
|
for (int i = 0; i < overlapped_any_blob.size(); ++i) {
|
||||||
|
if (overlapped_any_blob[i]) {
|
||||||
|
++num_overlapped;
|
||||||
|
if (word_wanted[i]) ++num_overlapped_used;
|
||||||
|
wanted.push_back(word_wanted[i]);
|
||||||
|
wanted_blobs.push_back(target_blobs[i]);
|
||||||
|
wanted_outlines.push_back(outlines[i]);
|
||||||
|
outlines[i] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
real_word->AddSelectedOutlines(wanted, wanted_blobs, wanted_outlines, NULL);
|
||||||
|
AssignDiacriticsToNewBlobs(outlines, pass, real_word, pr_it, &word_wanted,
|
||||||
|
&target_blobs);
|
||||||
|
int non_overlapped = 0;
|
||||||
|
int non_overlapped_used = 0;
|
||||||
|
for (int i = 0; i < word_wanted.size(); ++i) {
|
||||||
|
if (word_wanted[i]) ++non_overlapped_used;
|
||||||
|
if (outlines[i] != NULL) ++non_overlapped_used;
|
||||||
|
}
|
||||||
|
if (debug_noise_removal) {
|
||||||
|
tprintf("Used %d/%d overlapped %d/%d non-overlaped diacritics on word:",
|
||||||
|
num_overlapped_used, num_overlapped, non_overlapped_used,
|
||||||
|
non_overlapped);
|
||||||
|
real_word->bounding_box().print();
|
||||||
|
}
|
||||||
|
// Now we have decided which outlines we want, put them into the real_word.
|
||||||
|
if (real_word->AddSelectedOutlines(word_wanted, target_blobs, outlines,
|
||||||
|
make_next_word_fuzzy)) {
|
||||||
|
pr_it->MakeCurrentWordFuzzy();
|
||||||
|
}
|
||||||
|
// TODO(rays) Parts of combos have a deep copy of the real word, and need
|
||||||
|
// to have their noise outlines moved/assigned in the same way!!
|
||||||
|
return num_overlapped_used != 0 || non_overlapped_used != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to put noise/diacritic outlines into the blobs that they overlap.
|
||||||
|
// Input: a set of noisy outlines that probably belong to the real_word.
|
||||||
|
// Output: word_wanted indicates which outlines are to be assigned to a blob,
|
||||||
|
// target_blobs indicates which to assign to, and overlapped_any_blob is
|
||||||
|
// true for all outlines that overlapped a blob.
|
||||||
|
void Tesseract::AssignDiacriticsToOverlappingBlobs(
|
||||||
|
const GenericVector<C_OUTLINE*>& outlines, int pass, WERD* real_word,
|
||||||
|
PAGE_RES_IT* pr_it, GenericVector<bool>* word_wanted,
|
||||||
|
GenericVector<bool>* overlapped_any_blob,
|
||||||
|
GenericVector<C_BLOB*>* target_blobs) {
|
||||||
|
GenericVector<bool> blob_wanted;
|
||||||
|
word_wanted->init_to_size(outlines.size(), false);
|
||||||
|
overlapped_any_blob->init_to_size(outlines.size(), false);
|
||||||
|
target_blobs->init_to_size(outlines.size(), NULL);
|
||||||
|
// For each real blob, find the outlines that seriously overlap it.
|
||||||
|
// A single blob could be several merged characters, so there can be quite
|
||||||
|
// a few outlines overlapping, and the full engine needs to be used to chop
|
||||||
|
// and join to get a sensible result.
|
||||||
|
C_BLOB_IT blob_it(real_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();
|
||||||
|
blob_wanted.init_to_size(outlines.size(), false);
|
||||||
|
int num_blob_outlines = 0;
|
||||||
|
for (int i = 0; i < outlines.size(); ++i) {
|
||||||
|
if (blob_box.major_x_overlap(outlines[i]->bounding_box()) &&
|
||||||
|
!(*word_wanted)[i]) {
|
||||||
|
blob_wanted[i] = true;
|
||||||
|
(*overlapped_any_blob)[i] = true;
|
||||||
|
++num_blob_outlines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (debug_noise_removal) {
|
||||||
|
tprintf("%d noise outlines overlap blob at:", num_blob_outlines);
|
||||||
|
blob_box.print();
|
||||||
|
}
|
||||||
|
// If any outlines overlap the blob, and not too many, classify the blob
|
||||||
|
// (using the full engine, languages and all), and choose the maximal
|
||||||
|
// combination of outlines that doesn't hurt the end-result classification
|
||||||
|
// by too much. Mark them as wanted.
|
||||||
|
if (0 < num_blob_outlines && num_blob_outlines < noise_maxperblob) {
|
||||||
|
if (SelectGoodDiacriticOutlines(pass, noise_cert_basechar, pr_it, blob,
|
||||||
|
outlines, num_blob_outlines,
|
||||||
|
&blob_wanted)) {
|
||||||
|
for (int i = 0; i < blob_wanted.size(); ++i) {
|
||||||
|
if (blob_wanted[i]) {
|
||||||
|
// Claim the outline and record where it is going.
|
||||||
|
(*word_wanted)[i] = true;
|
||||||
|
(*target_blobs)[i] = blob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to assign non-overlapping outlines to their nearest blobs or
|
||||||
|
// make new blobs out of them.
|
||||||
|
void Tesseract::AssignDiacriticsToNewBlobs(
|
||||||
|
const GenericVector<C_OUTLINE*>& outlines, int pass, WERD* real_word,
|
||||||
|
PAGE_RES_IT* pr_it, GenericVector<bool>* word_wanted,
|
||||||
|
GenericVector<C_BLOB*>* target_blobs) {
|
||||||
|
GenericVector<bool> blob_wanted;
|
||||||
|
word_wanted->init_to_size(outlines.size(), false);
|
||||||
|
target_blobs->init_to_size(outlines.size(), NULL);
|
||||||
|
// Check for outlines that need to be turned into stand-alone blobs.
|
||||||
|
for (int i = 0; i < outlines.size(); ++i) {
|
||||||
|
if (outlines[i] == NULL) continue;
|
||||||
|
// Get a set of adjacent outlines that don't overlap any existing blob.
|
||||||
|
blob_wanted.init_to_size(outlines.size(), false);
|
||||||
|
int num_blob_outlines = 0;
|
||||||
|
TBOX total_ol_box(outlines[i]->bounding_box());
|
||||||
|
while (i < outlines.size() && outlines[i] != NULL) {
|
||||||
|
blob_wanted[i] = true;
|
||||||
|
total_ol_box += outlines[i]->bounding_box();
|
||||||
|
++i;
|
||||||
|
++num_blob_outlines;
|
||||||
|
}
|
||||||
|
// Find the insertion point.
|
||||||
|
C_BLOB_IT blob_it(real_word->cblob_list());
|
||||||
|
while (!blob_it.at_last() &&
|
||||||
|
blob_it.data_relative(1)->bounding_box().left() <=
|
||||||
|
total_ol_box.left()) {
|
||||||
|
blob_it.forward();
|
||||||
|
}
|
||||||
|
// Choose which combination of them we actually want and where to put
|
||||||
|
// them.
|
||||||
|
if (debug_noise_removal)
|
||||||
|
tprintf("Num blobless outlines = %d\n", num_blob_outlines);
|
||||||
|
C_BLOB* left_blob = blob_it.data();
|
||||||
|
TBOX left_box = left_blob->bounding_box();
|
||||||
|
C_BLOB* right_blob = blob_it.at_last() ? NULL : blob_it.data_relative(1);
|
||||||
|
if ((left_box.x_overlap(total_ol_box) || right_blob == NULL ||
|
||||||
|
!right_blob->bounding_box().x_overlap(total_ol_box)) &&
|
||||||
|
SelectGoodDiacriticOutlines(pass, noise_cert_disjoint, pr_it, left_blob,
|
||||||
|
outlines, num_blob_outlines,
|
||||||
|
&blob_wanted)) {
|
||||||
|
if (debug_noise_removal) tprintf("Added to left blob\n");
|
||||||
|
for (int j = 0; j < blob_wanted.size(); ++j) {
|
||||||
|
if (blob_wanted[j]) {
|
||||||
|
(*word_wanted)[j] = true;
|
||||||
|
(*target_blobs)[j] = left_blob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (right_blob != NULL &&
|
||||||
|
(!left_box.x_overlap(total_ol_box) ||
|
||||||
|
right_blob->bounding_box().x_overlap(total_ol_box)) &&
|
||||||
|
SelectGoodDiacriticOutlines(pass, noise_cert_disjoint, pr_it,
|
||||||
|
right_blob, outlines,
|
||||||
|
num_blob_outlines, &blob_wanted)) {
|
||||||
|
if (debug_noise_removal) tprintf("Added to right blob\n");
|
||||||
|
for (int j = 0; j < blob_wanted.size(); ++j) {
|
||||||
|
if (blob_wanted[j]) {
|
||||||
|
(*word_wanted)[j] = true;
|
||||||
|
(*target_blobs)[j] = right_blob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (SelectGoodDiacriticOutlines(pass, noise_cert_punc, pr_it, NULL,
|
||||||
|
outlines, num_blob_outlines,
|
||||||
|
&blob_wanted)) {
|
||||||
|
if (debug_noise_removal) tprintf("Fitted between blobs\n");
|
||||||
|
for (int j = 0; j < blob_wanted.size(); ++j) {
|
||||||
|
if (blob_wanted[j]) {
|
||||||
|
(*word_wanted)[j] = true;
|
||||||
|
(*target_blobs)[j] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starting with ok_outlines set to indicate which outlines overlap the blob,
|
||||||
|
// chooses the optimal set (approximately) and returns true if any outlines
|
||||||
|
// are desired, in which case ok_outlines indicates which ones.
|
||||||
|
bool Tesseract::SelectGoodDiacriticOutlines(
|
||||||
|
int pass, float certainty_threshold, PAGE_RES_IT* pr_it, C_BLOB* blob,
|
||||||
|
const GenericVector<C_OUTLINE*>& outlines, int num_outlines,
|
||||||
|
GenericVector<bool>* ok_outlines) {
|
||||||
|
STRING best_str;
|
||||||
|
float target_cert = certainty_threshold;
|
||||||
|
if (blob != NULL) {
|
||||||
|
float target_c2;
|
||||||
|
target_cert = ClassifyBlobAsWord(pass, pr_it, blob, &best_str, &target_c2);
|
||||||
|
if (debug_noise_removal) {
|
||||||
|
tprintf("No Noise blob classified as %s=%g(%g) at:", best_str.string(),
|
||||||
|
target_cert, target_c2);
|
||||||
|
blob->bounding_box().print();
|
||||||
|
}
|
||||||
|
target_cert -= (target_cert - certainty_threshold) * noise_cert_factor;
|
||||||
|
}
|
||||||
|
GenericVector<bool> test_outlines = *ok_outlines;
|
||||||
|
// Start with all the outlines in.
|
||||||
|
STRING all_str;
|
||||||
|
GenericVector<bool> best_outlines = *ok_outlines;
|
||||||
|
float best_cert = ClassifyBlobPlusOutlines(test_outlines, outlines, pass,
|
||||||
|
pr_it, blob, &all_str);
|
||||||
|
if (debug_noise_removal) {
|
||||||
|
TBOX ol_box;
|
||||||
|
for (int i = 0; i < test_outlines.size(); ++i) {
|
||||||
|
if (test_outlines[i]) ol_box += outlines[i]->bounding_box();
|
||||||
|
}
|
||||||
|
tprintf("All Noise blob classified as %s=%g, delta=%g at:",
|
||||||
|
all_str.string(), best_cert, best_cert - target_cert);
|
||||||
|
ol_box.print();
|
||||||
|
}
|
||||||
|
// Iteratively zero out the bit that improves the certainty the most, until
|
||||||
|
// we get past the threshold, have zero bits, or fail to improve.
|
||||||
|
int best_index = 0; // To zero out.
|
||||||
|
while (num_outlines > 1 && best_index >= 0 &&
|
||||||
|
(blob == NULL || best_cert < target_cert || blob != NULL)) {
|
||||||
|
// Find the best bit to zero out.
|
||||||
|
best_index = -1;
|
||||||
|
for (int i = 0; i < outlines.size(); ++i) {
|
||||||
|
if (test_outlines[i]) {
|
||||||
|
test_outlines[i] = false;
|
||||||
|
STRING str;
|
||||||
|
float cert = ClassifyBlobPlusOutlines(test_outlines, outlines, pass,
|
||||||
|
pr_it, blob, &str);
|
||||||
|
if (debug_noise_removal) {
|
||||||
|
TBOX ol_box;
|
||||||
|
for (int j = 0; j < outlines.size(); ++j) {
|
||||||
|
if (test_outlines[j]) ol_box += outlines[j]->bounding_box();
|
||||||
|
tprintf("%d", test_outlines[j]);
|
||||||
|
}
|
||||||
|
tprintf(" blob classified as %s=%g, delta=%g) at:", str.string(),
|
||||||
|
cert, cert - target_cert);
|
||||||
|
ol_box.print();
|
||||||
|
}
|
||||||
|
if (cert > best_cert) {
|
||||||
|
best_cert = cert;
|
||||||
|
best_index = i;
|
||||||
|
best_outlines = test_outlines;
|
||||||
|
}
|
||||||
|
test_outlines[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best_index >= 0) {
|
||||||
|
test_outlines[best_index] = false;
|
||||||
|
--num_outlines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best_cert >= target_cert) {
|
||||||
|
// Save the best combination.
|
||||||
|
*ok_outlines = best_outlines;
|
||||||
|
if (debug_noise_removal) {
|
||||||
|
tprintf("%s noise combination ", blob ? "Adding" : "New");
|
||||||
|
for (int i = 0; i < best_outlines.size(); ++i) {
|
||||||
|
tprintf("%d", best_outlines[i]);
|
||||||
|
}
|
||||||
|
tprintf(" yields certainty %g, beating target of %g\n", best_cert,
|
||||||
|
target_cert);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classifies the given blob plus the outlines flagged by ok_outlines, undoes
|
||||||
|
// the inclusion of the outlines, and returns the certainty of the raw choice.
|
||||||
|
float Tesseract::ClassifyBlobPlusOutlines(
|
||||||
|
const GenericVector<bool>& ok_outlines,
|
||||||
|
const GenericVector<C_OUTLINE*>& outlines, int pass_n, PAGE_RES_IT* pr_it,
|
||||||
|
C_BLOB* blob, STRING* best_str) {
|
||||||
|
C_OUTLINE_IT ol_it;
|
||||||
|
C_OUTLINE* first_to_keep = NULL;
|
||||||
|
if (blob != NULL) {
|
||||||
|
// Add the required outlines to the blob.
|
||||||
|
ol_it.set_to_list(blob->out_list());
|
||||||
|
first_to_keep = ol_it.data();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < ok_outlines.size(); ++i) {
|
||||||
|
if (ok_outlines[i]) {
|
||||||
|
// This outline is to be added.
|
||||||
|
if (blob == NULL) {
|
||||||
|
blob = new C_BLOB(outlines[i]);
|
||||||
|
ol_it.set_to_list(blob->out_list());
|
||||||
|
} else {
|
||||||
|
ol_it.add_before_stay_put(outlines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float c2;
|
||||||
|
float cert = ClassifyBlobAsWord(pass_n, pr_it, blob, best_str, &c2);
|
||||||
|
ol_it.move_to_first();
|
||||||
|
if (first_to_keep == NULL) {
|
||||||
|
// We created blob. Empty its outlines and delete it.
|
||||||
|
for (; !ol_it.empty(); ol_it.forward()) ol_it.extract();
|
||||||
|
delete blob;
|
||||||
|
cert = -c2;
|
||||||
|
} else {
|
||||||
|
// Remove the outlines that we put in.
|
||||||
|
for (; ol_it.data() != first_to_keep; ol_it.forward()) {
|
||||||
|
ol_it.extract();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classifies the given blob (part of word_data->word->word) as an individual
|
||||||
|
// word, using languages, chopper etc, returning only the certainty of the
|
||||||
|
// best raw choice, and undoing all the work done to fake out the word.
|
||||||
|
float Tesseract::ClassifyBlobAsWord(int pass_n, PAGE_RES_IT* pr_it,
|
||||||
|
C_BLOB* blob, STRING* best_str, float* c2) {
|
||||||
|
WERD* real_word = pr_it->word()->word;
|
||||||
|
WERD* word = real_word->ConstructFromSingleBlob(
|
||||||
|
real_word->flag(W_BOL), real_word->flag(W_EOL), C_BLOB::deep_copy(blob));
|
||||||
|
WERD_RES* word_res = pr_it->InsertSimpleCloneWord(*pr_it->word(), word);
|
||||||
|
// Get a new iterator that points to the new word.
|
||||||
|
PAGE_RES_IT it(pr_it->page_res);
|
||||||
|
while (it.word() != word_res && it.word() != NULL) it.forward();
|
||||||
|
ASSERT_HOST(it.word() == word_res);
|
||||||
|
WordData wd(it);
|
||||||
|
// Force full initialization.
|
||||||
|
SetupWordPassN(1, &wd);
|
||||||
|
classify_word_and_language(pass_n, &it, &wd);
|
||||||
|
if (debug_noise_removal) {
|
||||||
|
tprintf("word xheight=%g, row=%g, range=[%g,%g]\n", word_res->x_height,
|
||||||
|
wd.row->x_height(), wd.word->raw_choice->min_x_height(),
|
||||||
|
wd.word->raw_choice->max_x_height());
|
||||||
|
}
|
||||||
|
float cert = wd.word->raw_choice->certainty();
|
||||||
|
float rat = wd.word->raw_choice->rating();
|
||||||
|
*c2 = rat > 0.0f ? cert * cert / rat : 0.0f;
|
||||||
|
*best_str = wd.word->raw_choice->unichar_string();
|
||||||
|
it.DeleteCurrentWord();
|
||||||
|
pr_it->ResetWordIterator();
|
||||||
|
return cert;
|
||||||
|
}
|
||||||
|
|
||||||
// Generic function for classifying a word. Can be used either for pass1 or
|
// Generic function for classifying a word. Can be used either for pass1 or
|
||||||
// pass2 according to the function passed to recognizer.
|
// pass2 according to the function passed to recognizer.
|
||||||
// word_data holds the word to be recognized, and its block and row, and
|
// word_data holds the word to be recognized, and its block and row, and
|
||||||
@ -905,9 +1266,10 @@ static bool WordsAcceptable(const PointerVector<WERD_RES>& words) {
|
|||||||
// Recognizes in the current language, and if successful that is all.
|
// Recognizes in the current language, and if successful that is all.
|
||||||
// If recognition was not successful, tries all available languages until
|
// If recognition was not successful, tries all available languages until
|
||||||
// it gets a successful result or runs out of languages. Keeps the best result.
|
// it gets a successful result or runs out of languages. Keeps the best result.
|
||||||
void Tesseract::classify_word_and_language(WordRecognizer recognizer,
|
void Tesseract::classify_word_and_language(int pass_n, PAGE_RES_IT* pr_it,
|
||||||
PAGE_RES_IT* pr_it,
|
|
||||||
WordData* word_data) {
|
WordData* word_data) {
|
||||||
|
WordRecognizer recognizer = pass_n == 1 ? &Tesseract::classify_word_pass1
|
||||||
|
: &Tesseract::classify_word_pass2;
|
||||||
// Best result so far.
|
// Best result so far.
|
||||||
PointerVector<WERD_RES> best_words;
|
PointerVector<WERD_RES> best_words;
|
||||||
// Points to the best result. May be word or in lang_words.
|
// Points to the best result. May be word or in lang_words.
|
||||||
@ -987,11 +1349,13 @@ void Tesseract::classify_word_pass1(const WordData& word_data,
|
|||||||
BLOCK* block = word_data.block;
|
BLOCK* block = word_data.block;
|
||||||
prev_word_best_choice_ = word_data.prev_word != NULL
|
prev_word_best_choice_ = word_data.prev_word != NULL
|
||||||
? word_data.prev_word->word->best_choice : NULL;
|
? word_data.prev_word->word->best_choice : NULL;
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
// If we only intend to run cube - run it and return.
|
// If we only intend to run cube - run it and return.
|
||||||
if (tessedit_ocr_engine_mode == OEM_CUBE_ONLY) {
|
if (tessedit_ocr_engine_mode == OEM_CUBE_ONLY) {
|
||||||
cube_word_pass1(block, row, *in_word);
|
cube_word_pass1(block, row, *in_word);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
WERD_RES* word = *in_word;
|
WERD_RES* word = *in_word;
|
||||||
match_word_pass_n(1, word, row, block);
|
match_word_pass_n(1, word, row, block);
|
||||||
if (!word->tess_failed && !word->word->flag(W_REP_CHAR)) {
|
if (!word->tess_failed && !word->word->flag(W_REP_CHAR)) {
|
||||||
@ -1041,45 +1405,77 @@ bool Tesseract::TrainedXheightFix(WERD_RES *word, BLOCK* block, ROW *row) {
|
|||||||
int original_misfits = CountMisfitTops(word);
|
int original_misfits = CountMisfitTops(word);
|
||||||
if (original_misfits == 0)
|
if (original_misfits == 0)
|
||||||
return false;
|
return false;
|
||||||
float new_x_ht = ComputeCompatibleXheight(word);
|
float baseline_shift = 0.0f;
|
||||||
if (new_x_ht >= kMinRefitXHeightFraction * word->x_height) {
|
float new_x_ht = ComputeCompatibleXheight(word, &baseline_shift);
|
||||||
WERD_RES new_x_ht_word(word->word);
|
if (baseline_shift != 0.0f) {
|
||||||
if (word->blamer_bundle != NULL) {
|
// Try the shift on its own first.
|
||||||
new_x_ht_word.blamer_bundle = new BlamerBundle();
|
if (!TestNewNormalization(original_misfits, baseline_shift, word->x_height,
|
||||||
new_x_ht_word.blamer_bundle->CopyTruth(*(word->blamer_bundle));
|
word, block, row))
|
||||||
}
|
return false;
|
||||||
new_x_ht_word.x_height = new_x_ht;
|
original_misfits = CountMisfitTops(word);
|
||||||
new_x_ht_word.caps_height = 0.0;
|
if (original_misfits > 0) {
|
||||||
new_x_ht_word.SetupForRecognition(
|
float new_baseline_shift;
|
||||||
unicharset, this, BestPix(), tessedit_ocr_engine_mode, NULL,
|
// Now recompute the new x_height.
|
||||||
classify_bln_numeric_mode, textord_use_cjk_fp_model,
|
new_x_ht = ComputeCompatibleXheight(word, &new_baseline_shift);
|
||||||
poly_allow_detailed_fx, row, block);
|
if (new_x_ht >= kMinRefitXHeightFraction * word->x_height) {
|
||||||
match_word_pass_n(2, &new_x_ht_word, row, block);
|
// No test of return value here, as we are definitely making a change
|
||||||
if (!new_x_ht_word.tess_failed) {
|
// to the word by shifting the baseline.
|
||||||
int new_misfits = CountMisfitTops(&new_x_ht_word);
|
TestNewNormalization(original_misfits, baseline_shift, new_x_ht,
|
||||||
if (debug_x_ht_level >= 1) {
|
word, block, row);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (accept_new_x_ht) {
|
return true;
|
||||||
word->ConsumeWordResults(&new_x_ht_word);
|
} else if (new_x_ht >= kMinRefitXHeightFraction * word->x_height) {
|
||||||
return true;
|
return TestNewNormalization(original_misfits, 0.0f, new_x_ht,
|
||||||
|
word, block, row);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs recognition with the test baseline shift and x-height and returns true
|
||||||
|
// if there was an improvement in recognition result.
|
||||||
|
bool Tesseract::TestNewNormalization(int original_misfits,
|
||||||
|
float baseline_shift, float new_x_ht,
|
||||||
|
WERD_RES *word, BLOCK* block, ROW *row) {
|
||||||
|
bool accept_new_x_ht = false;
|
||||||
|
WERD_RES new_x_ht_word(word->word);
|
||||||
|
if (word->blamer_bundle != NULL) {
|
||||||
|
new_x_ht_word.blamer_bundle = new BlamerBundle();
|
||||||
|
new_x_ht_word.blamer_bundle->CopyTruth(*(word->blamer_bundle));
|
||||||
|
}
|
||||||
|
new_x_ht_word.x_height = new_x_ht;
|
||||||
|
new_x_ht_word.baseline_shift = baseline_shift;
|
||||||
|
new_x_ht_word.caps_height = 0.0;
|
||||||
|
new_x_ht_word.SetupForRecognition(
|
||||||
|
unicharset, this, BestPix(), tessedit_ocr_engine_mode, NULL,
|
||||||
|
classify_bln_numeric_mode, textord_use_cjk_fp_model,
|
||||||
|
poly_allow_detailed_fx, row, block);
|
||||||
|
match_word_pass_n(2, &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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (accept_new_x_ht) {
|
||||||
|
word->ConsumeWordResults(&new_x_ht_word);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1098,6 +1494,9 @@ void Tesseract::classify_word_pass2(const WordData& word_data,
|
|||||||
tessedit_ocr_engine_mode != OEM_TESSERACT_CUBE_COMBINED &&
|
tessedit_ocr_engine_mode != OEM_TESSERACT_CUBE_COMBINED &&
|
||||||
word_data.word->best_choice != NULL)
|
word_data.word->best_choice != NULL)
|
||||||
return;
|
return;
|
||||||
|
if (tessedit_ocr_engine_mode == OEM_CUBE_ONLY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ROW* row = word_data.row;
|
ROW* row = word_data.row;
|
||||||
BLOCK* block = word_data.block;
|
BLOCK* block = word_data.block;
|
||||||
WERD_RES* word = *in_word;
|
WERD_RES* word = *in_word;
|
||||||
@ -1246,7 +1645,6 @@ void Tesseract::fix_rep_char(PAGE_RES_IT* page_res_it) {
|
|||||||
word_res->done = TRUE;
|
word_res->done = TRUE;
|
||||||
|
|
||||||
// Measure the mean space.
|
// Measure the mean space.
|
||||||
int total_gap = 0;
|
|
||||||
int gap_count = 0;
|
int gap_count = 0;
|
||||||
WERD* werd = word_res->word;
|
WERD* werd = word_res->word;
|
||||||
C_BLOB_IT blob_it(werd->cblob_list());
|
C_BLOB_IT blob_it(werd->cblob_list());
|
||||||
@ -1255,7 +1653,6 @@ void Tesseract::fix_rep_char(PAGE_RES_IT* page_res_it) {
|
|||||||
C_BLOB* blob = blob_it.data();
|
C_BLOB* blob = blob_it.data();
|
||||||
int gap = blob->bounding_box().left();
|
int gap = blob->bounding_box().left();
|
||||||
gap -= prev_blob->bounding_box().right();
|
gap -= prev_blob->bounding_box().right();
|
||||||
total_gap += gap;
|
|
||||||
++gap_count;
|
++gap_count;
|
||||||
prev_blob = blob;
|
prev_blob = blob;
|
||||||
}
|
}
|
||||||
@ -1376,13 +1773,13 @@ BOOL8 Tesseract::check_debug_pt(WERD_RES *word, int location) {
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
tessedit_rejection_debug.set_value (FALSE);
|
tessedit_rejection_debug.set_value (FALSE);
|
||||||
debug_x_ht_level.set_value (0);
|
debug_x_ht_level.set_value(0);
|
||||||
|
|
||||||
if (word->word->bounding_box ().contains (FCOORD (test_pt_x, test_pt_y))) {
|
if (word->word->bounding_box ().contains (FCOORD (test_pt_x, test_pt_y))) {
|
||||||
if (location < 0)
|
if (location < 0)
|
||||||
return TRUE; // For breakpoint use
|
return TRUE; // For breakpoint use
|
||||||
tessedit_rejection_debug.set_value (TRUE);
|
tessedit_rejection_debug.set_value (TRUE);
|
||||||
debug_x_ht_level.set_value (20);
|
debug_x_ht_level.set_value(2);
|
||||||
tprintf ("\n\nTESTWD::");
|
tprintf ("\n\nTESTWD::");
|
||||||
switch (location) {
|
switch (location) {
|
||||||
case 0:
|
case 0:
|
||||||
@ -1487,62 +1884,54 @@ void Tesseract::set_word_fonts(WERD_RES *word) {
|
|||||||
if (word->chopped_word == NULL) return;
|
if (word->chopped_word == NULL) return;
|
||||||
ASSERT_HOST(word->best_choice != NULL);
|
ASSERT_HOST(word->best_choice != NULL);
|
||||||
|
|
||||||
inT32 index; // char id index
|
|
||||||
// character iterator
|
|
||||||
BLOB_CHOICE_IT choice_it; // choice iterator
|
|
||||||
int fontinfo_size = get_fontinfo_table().size();
|
int fontinfo_size = get_fontinfo_table().size();
|
||||||
int fontset_size = get_fontset_table().size();
|
if (fontinfo_size == 0) return;
|
||||||
if (fontinfo_size == 0 || fontset_size == 0) return;
|
GenericVector<int> font_total_score;
|
||||||
STATS fonts(0, fontinfo_size); // font counters
|
font_total_score.init_to_size(fontinfo_size, 0);
|
||||||
|
|
||||||
word->italic = 0;
|
word->italic = 0;
|
||||||
word->bold = 0;
|
word->bold = 0;
|
||||||
if (!word->best_choice_fontinfo_ids.empty()) {
|
// Compute the font scores for the word
|
||||||
word->best_choice_fontinfo_ids.clear();
|
if (tessedit_debug_fonts) {
|
||||||
|
tprintf("Examining fonts in %s\n",
|
||||||
|
word->best_choice->debug_string().string());
|
||||||
}
|
}
|
||||||
// Compute the modal font for the word
|
for (int b = 0; b < word->best_choice->length(); ++b) {
|
||||||
for (index = 0; index < word->best_choice->length(); ++index) {
|
BLOB_CHOICE* choice = word->GetBlobChoice(b);
|
||||||
UNICHAR_ID word_ch_id = word->best_choice->unichar_id(index);
|
if (choice == NULL) continue;
|
||||||
choice_it.set_to_list(word->GetBlobChoices(index));
|
const GenericVector<ScoredFont>& fonts = choice->fonts();
|
||||||
if (tessedit_debug_fonts) {
|
for (int f = 0; f < fonts.size(); ++f) {
|
||||||
tprintf("Examining fonts in %s\n",
|
int fontinfo_id = fonts[f].fontinfo_id;
|
||||||
word->best_choice->debug_string().string());
|
if (0 <= fontinfo_id && fontinfo_id < fontinfo_size) {
|
||||||
}
|
font_total_score[fontinfo_id] += fonts[f].score;
|
||||||
for (choice_it.mark_cycle_pt(); !choice_it.cycled_list();
|
|
||||||
choice_it.forward()) {
|
|
||||||
UNICHAR_ID blob_ch_id = choice_it.data()->unichar_id();
|
|
||||||
if (blob_ch_id == word_ch_id) {
|
|
||||||
if (tessedit_debug_fonts) {
|
|
||||||
tprintf("%s font %s (%d) font2 %s (%d)\n",
|
|
||||||
word->uch_set->id_to_unichar(blob_ch_id),
|
|
||||||
choice_it.data()->fontinfo_id() < 0 ? "unknown" :
|
|
||||||
fontinfo_table_.get(choice_it.data()->fontinfo_id()).name,
|
|
||||||
choice_it.data()->fontinfo_id(),
|
|
||||||
choice_it.data()->fontinfo_id2() < 0 ? "unknown" :
|
|
||||||
fontinfo_table_.get(choice_it.data()->fontinfo_id2()).name,
|
|
||||||
choice_it.data()->fontinfo_id2());
|
|
||||||
}
|
|
||||||
// 1st choice font gets 2 pts, 2nd choice 1 pt.
|
|
||||||
if (choice_it.data()->fontinfo_id() >= 0) {
|
|
||||||
fonts.add(choice_it.data()->fontinfo_id(), 2);
|
|
||||||
}
|
|
||||||
if (choice_it.data()->fontinfo_id2() >= 0) {
|
|
||||||
fonts.add(choice_it.data()->fontinfo_id2(), 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inT16 font_id1, font_id2;
|
// Find the top and 2nd choice for the word.
|
||||||
find_modal_font(&fonts, &font_id1, &word->fontinfo_id_count);
|
int score1 = 0, score2 = 0;
|
||||||
find_modal_font(&fonts, &font_id2, &word->fontinfo_id2_count);
|
inT16 font_id1 = -1, font_id2 = -1;
|
||||||
|
for (int f = 0; f < fontinfo_size; ++f) {
|
||||||
|
if (tessedit_debug_fonts && font_total_score[f] > 0) {
|
||||||
|
tprintf("Font %s, total score = %d\n",
|
||||||
|
fontinfo_table_.get(f).name, font_total_score[f]);
|
||||||
|
}
|
||||||
|
if (font_total_score[f] > score1) {
|
||||||
|
score2 = score1;
|
||||||
|
font_id2 = font_id1;
|
||||||
|
score1 = font_total_score[f];
|
||||||
|
font_id1 = f;
|
||||||
|
} else if (font_total_score[f] > score2) {
|
||||||
|
score2 = font_total_score[f];
|
||||||
|
font_id2 = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
word->fontinfo = font_id1 >= 0 ? &fontinfo_table_.get(font_id1) : NULL;
|
word->fontinfo = font_id1 >= 0 ? &fontinfo_table_.get(font_id1) : NULL;
|
||||||
word->fontinfo2 = font_id2 >= 0 ? &fontinfo_table_.get(font_id2) : NULL;
|
word->fontinfo2 = font_id2 >= 0 ? &fontinfo_table_.get(font_id2) : NULL;
|
||||||
// All the blobs get the word's best choice font.
|
// Each score has a limit of MAX_UINT16, so divide by that to get the number
|
||||||
for (int i = 0; i < word->best_choice->length(); ++i) {
|
// of "votes" for that font, ie number of perfect scores.
|
||||||
word->best_choice_fontinfo_ids.push_back(font_id1);
|
word->fontinfo_id_count = ClipToRange(score1 / MAX_UINT16, 1, MAX_INT8);
|
||||||
}
|
word->fontinfo_id2_count = ClipToRange(score2 / MAX_UINT16, 0, MAX_INT8);
|
||||||
if (word->fontinfo_id_count > 0) {
|
if (score1 > 0) {
|
||||||
FontInfo fi = fontinfo_table_.get(font_id1);
|
FontInfo fi = fontinfo_table_.get(font_id1);
|
||||||
if (tessedit_debug_fonts) {
|
if (tessedit_debug_fonts) {
|
||||||
if (word->fontinfo_id2_count > 0) {
|
if (word->fontinfo_id2_count > 0) {
|
||||||
@ -1555,9 +1944,8 @@ void Tesseract::set_word_fonts(WERD_RES *word) {
|
|||||||
fi.name, word->fontinfo_id_count);
|
fi.name, word->fontinfo_id_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 1st choices got 2 pts, so we need to halve the score for the mode.
|
word->italic = (fi.is_italic() ? 1 : -1) * word->fontinfo_id_count;
|
||||||
word->italic = (fi.is_italic() ? 1 : -1) * (word->fontinfo_id_count + 1) / 2;
|
word->bold = (fi.is_bold() ? 1 : -1) * word->fontinfo_id_count;
|
||||||
word->bold = (fi.is_bold() ? 1 : -1) * (word->fontinfo_id_count + 1) / 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1611,8 +1999,7 @@ void Tesseract::font_recognition_pass(PAGE_RES* page_res) {
|
|||||||
word = page_res_it.word();
|
word = page_res_it.word();
|
||||||
int length = word->best_choice->length();
|
int length = word->best_choice->length();
|
||||||
|
|
||||||
// 1st choices got 2 pts, so we need to halve the score for the mode.
|
int count = word->fontinfo_id_count;
|
||||||
int count = (word->fontinfo_id_count + 1) / 2;
|
|
||||||
if (!(count == length || (length > 3 && count >= length * 3 / 4))) {
|
if (!(count == length || (length > 3 && count >= length * 3 / 4))) {
|
||||||
word->fontinfo = modal_font;
|
word->fontinfo = modal_font;
|
||||||
// Counts only get 1 as it came from the doc.
|
// Counts only get 1 as it came from the doc.
|
||||||
|
@ -383,8 +383,8 @@ bool Tesseract::cube_recognize(CubeObject *cube_obj, BLOCK* block,
|
|||||||
for (int i = 0; i < num_chars; ++i) {
|
for (int i = 0; i < num_chars; ++i) {
|
||||||
UNICHAR_ID uch_id =
|
UNICHAR_ID uch_id =
|
||||||
cube_cntxt_->CharacterSet()->UnicharID(char_samples[i]->StrLabel());
|
cube_cntxt_->CharacterSet()->UnicharID(char_samples[i]->StrLabel());
|
||||||
choices[i] = new BLOB_CHOICE(uch_id, 0.0, cube_certainty, -1, -1,
|
choices[i] = new BLOB_CHOICE(uch_id, -cube_certainty, cube_certainty,
|
||||||
0, 0, 0, 0, BCC_STATIC_CLASSIFIER);
|
-1, 0.0f, 0.0f, 0.0f, BCC_STATIC_CLASSIFIER);
|
||||||
}
|
}
|
||||||
word->FakeClassifyWord(num_chars, choices);
|
word->FakeClassifyWord(num_chars, choices);
|
||||||
// within a word, cube recognizes the word in reading order.
|
// within a word, cube recognizes the word in reading order.
|
||||||
|
@ -205,8 +205,7 @@ void Tesseract::match_current_words(WERD_RES_LIST &words, ROW *row,
|
|||||||
if ((!word->part_of_combo) && (word->box_word == NULL)) {
|
if ((!word->part_of_combo) && (word->box_word == NULL)) {
|
||||||
WordData word_data(block, row, word);
|
WordData word_data(block, row, word);
|
||||||
SetupWordPassN(2, &word_data);
|
SetupWordPassN(2, &word_data);
|
||||||
classify_word_and_language(&Tesseract::classify_word_pass2, NULL,
|
classify_word_and_language(2, NULL, &word_data);
|
||||||
&word_data);
|
|
||||||
}
|
}
|
||||||
prev_word_best_choice_ = word->best_choice;
|
prev_word_best_choice_ = word->best_choice;
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,8 @@ namespace tesseract {
|
|||||||
// guessed that the blob tops are caps and will have placed the xheight too low.
|
// 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
|
// 3. Noise/logos beside words, or changes in font size on a line. Such
|
||||||
// things can blow the statistics and cause an incorrect estimate.
|
// things can blow the statistics and cause an incorrect estimate.
|
||||||
|
// 4. Incorrect baseline. Can happen when 2 columns are incorrectly merged.
|
||||||
|
// In this case the x-height is often still correct.
|
||||||
//
|
//
|
||||||
// Algorithm.
|
// Algorithm.
|
||||||
// Compare the vertical position (top only) of alphnumerics in a word with
|
// Compare the vertical position (top only) of alphnumerics in a word with
|
||||||
@ -54,6 +56,10 @@ namespace tesseract {
|
|||||||
// even if the x-height is incorrect. This is not a terrible assumption, but
|
// 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
|
// it is not great. An improvement would be to use a classifier that does
|
||||||
// not care about vertical position or scaling at all.
|
// not care about vertical position or scaling at all.
|
||||||
|
// Separately collect stats on shifted baselines and apply the same logic to
|
||||||
|
// computing a best-fit shift to fix the error. If the baseline needs to be
|
||||||
|
// shifted, but the x-height is OK, returns the original x-height along with
|
||||||
|
// the baseline shift to indicate that recognition needs to re-run.
|
||||||
|
|
||||||
// If the max-min top of a unicharset char is bigger than kMaxCharTopRange
|
// 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.
|
// then the char top cannot be used to judge misfits or suggest a new top.
|
||||||
@ -92,65 +98,108 @@ int Tesseract::CountMisfitTops(WERD_RES *word_res) {
|
|||||||
|
|
||||||
// Returns a new x-height maximally compatible with the result in word_res.
|
// Returns a new x-height maximally compatible with the result in word_res.
|
||||||
// See comment above for overall algorithm.
|
// See comment above for overall algorithm.
|
||||||
float Tesseract::ComputeCompatibleXheight(WERD_RES *word_res) {
|
float Tesseract::ComputeCompatibleXheight(WERD_RES *word_res,
|
||||||
|
float* baseline_shift) {
|
||||||
STATS top_stats(0, MAX_UINT8);
|
STATS top_stats(0, MAX_UINT8);
|
||||||
|
STATS shift_stats(-MAX_UINT8, MAX_UINT8);
|
||||||
|
int bottom_shift = 0;
|
||||||
int num_blobs = word_res->rebuild_word->NumBlobs();
|
int num_blobs = word_res->rebuild_word->NumBlobs();
|
||||||
for (int blob_id = 0; blob_id < num_blobs; ++blob_id) {
|
do {
|
||||||
TBLOB* blob = word_res->rebuild_word->blobs[blob_id];
|
top_stats.clear();
|
||||||
UNICHAR_ID class_id = word_res->best_choice->unichar_id(blob_id);
|
shift_stats.clear();
|
||||||
if (unicharset.get_isalpha(class_id) || unicharset.get_isdigit(class_id)) {
|
for (int blob_id = 0; blob_id < num_blobs; ++blob_id) {
|
||||||
int top = blob->bounding_box().top();
|
TBLOB* blob = word_res->rebuild_word->blobs[blob_id];
|
||||||
// Clip the top to the limit of normalized feature space.
|
UNICHAR_ID class_id = word_res->best_choice->unichar_id(blob_id);
|
||||||
if (top >= INT_FEAT_RANGE)
|
if (unicharset.get_isalpha(class_id) ||
|
||||||
top = INT_FEAT_RANGE - 1;
|
unicharset.get_isdigit(class_id)) {
|
||||||
int bottom = blob->bounding_box().bottom();
|
int top = blob->bounding_box().top() + bottom_shift;
|
||||||
int min_bottom, max_bottom, min_top, max_top;
|
// Clip the top to the limit of normalized feature space.
|
||||||
unicharset.get_top_bottom(class_id, &min_bottom, &max_bottom,
|
if (top >= INT_FEAT_RANGE)
|
||||||
&min_top, &max_top);
|
top = INT_FEAT_RANGE - 1;
|
||||||
// Chars with a wild top range would mess up the result so ignore them.
|
int bottom = blob->bounding_box().bottom() + bottom_shift;
|
||||||
if (max_top - min_top > kMaxCharTopRange)
|
int min_bottom, max_bottom, min_top, max_top;
|
||||||
continue;
|
unicharset.get_top_bottom(class_id, &min_bottom, &max_bottom,
|
||||||
int misfit_dist = MAX((min_top - x_ht_acceptance_tolerance) - top,
|
&min_top, &max_top);
|
||||||
top - (max_top + x_ht_acceptance_tolerance));
|
// Chars with a wild top range would mess up the result so ignore them.
|
||||||
int height = top - kBlnBaselineOffset;
|
if (max_top - min_top > kMaxCharTopRange)
|
||||||
if (debug_x_ht_level >= 20) {
|
continue;
|
||||||
tprintf("Class %s: height=%d, bottom=%d,%d top=%d,%d, actual=%d,%d : ",
|
int misfit_dist = MAX((min_top - x_ht_acceptance_tolerance) - top,
|
||||||
unicharset.id_to_unichar(class_id),
|
top - (max_top + x_ht_acceptance_tolerance));
|
||||||
height, min_bottom, max_bottom, min_top, max_top,
|
int height = top - kBlnBaselineOffset;
|
||||||
bottom, top);
|
if (debug_x_ht_level >= 2) {
|
||||||
}
|
tprintf("Class %s: height=%d, bottom=%d,%d top=%d,%d, actual=%d,%d: ",
|
||||||
// Use only chars that fit in the expected bottom range, and where
|
unicharset.id_to_unichar(class_id),
|
||||||
// the range of tops is sensibly near the xheight.
|
height, min_bottom, max_bottom, min_top, max_top,
|
||||||
if (min_bottom <= bottom + x_ht_acceptance_tolerance &&
|
bottom, top);
|
||||||
bottom - x_ht_acceptance_tolerance <= max_bottom &&
|
}
|
||||||
min_top > kBlnBaselineOffset &&
|
// Use only chars that fit in the expected bottom range, and where
|
||||||
max_top - kBlnBaselineOffset >= kBlnXHeight &&
|
// the range of tops is sensibly near the xheight.
|
||||||
misfit_dist > 0) {
|
if (min_bottom <= bottom + x_ht_acceptance_tolerance &&
|
||||||
// Compute the x-height position using proportionality between the
|
bottom - x_ht_acceptance_tolerance <= max_bottom &&
|
||||||
// actual height and expected height.
|
min_top > kBlnBaselineOffset &&
|
||||||
int min_xht = DivRounded(height * kBlnXHeight,
|
max_top - kBlnBaselineOffset >= kBlnXHeight &&
|
||||||
max_top - kBlnBaselineOffset);
|
misfit_dist > 0) {
|
||||||
int max_xht = DivRounded(height * kBlnXHeight,
|
// Compute the x-height position using proportionality between the
|
||||||
min_top - kBlnBaselineOffset);
|
// actual height and expected height.
|
||||||
if (debug_x_ht_level >= 20) {
|
int min_xht = DivRounded(height * kBlnXHeight,
|
||||||
tprintf(" xht range min=%d, max=%d\n",
|
max_top - kBlnBaselineOffset);
|
||||||
min_xht, max_xht);
|
int max_xht = DivRounded(height * kBlnXHeight,
|
||||||
|
min_top - kBlnBaselineOffset);
|
||||||
|
if (debug_x_ht_level >= 2) {
|
||||||
|
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 ((min_bottom > bottom + x_ht_acceptance_tolerance ||
|
||||||
|
bottom - x_ht_acceptance_tolerance > max_bottom) &&
|
||||||
|
bottom_shift == 0) {
|
||||||
|
// Get the range of required bottom shift.
|
||||||
|
int min_shift = min_bottom - bottom;
|
||||||
|
int max_shift = max_bottom - bottom;
|
||||||
|
if (debug_x_ht_level >= 2) {
|
||||||
|
tprintf(" bottom shift min=%d, max=%d\n", min_shift, max_shift);
|
||||||
|
}
|
||||||
|
// The range of expected shifts gets a vote equal to the min distance
|
||||||
|
// of the actual bottom from the expected bottom, spread over the
|
||||||
|
// range of its acceptance.
|
||||||
|
int misfit_weight = abs(min_shift);
|
||||||
|
if (max_shift > min_shift)
|
||||||
|
misfit_weight /= max_shift - min_shift;
|
||||||
|
for (int y = min_shift; y <= max_shift; ++y)
|
||||||
|
shift_stats.add(y, misfit_weight);
|
||||||
|
} else {
|
||||||
|
if (bottom_shift == 0) {
|
||||||
|
// Things with bottoms that are already ok need to say so, on the
|
||||||
|
// 1st iteration only.
|
||||||
|
shift_stats.add(0, kBlnBaselineOffset);
|
||||||
|
}
|
||||||
|
if (debug_x_ht_level >= 2) {
|
||||||
|
tprintf(" already OK\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The range of expected heights gets a vote equal to the distance
|
|
||||||
// of the actual top from the expected top.
|
|
||||||
for (int y = min_xht; y <= max_xht; ++y)
|
|
||||||
top_stats.add(y, misfit_dist);
|
|
||||||
} else if (debug_x_ht_level >= 20) {
|
|
||||||
tprintf(" already OK\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (shift_stats.get_total() > top_stats.get_total()) {
|
||||||
|
bottom_shift = IntCastRounded(shift_stats.median());
|
||||||
|
if (debug_x_ht_level >= 2) {
|
||||||
|
tprintf("Applying bottom shift=%d\n", bottom_shift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (bottom_shift != 0 &&
|
||||||
|
top_stats.get_total() < shift_stats.get_total());
|
||||||
|
// Baseline shift is opposite sign to the bottom shift.
|
||||||
|
*baseline_shift = -bottom_shift / word_res->denorm.y_scale();
|
||||||
|
if (debug_x_ht_level >= 2) {
|
||||||
|
tprintf("baseline shift=%g\n", *baseline_shift);
|
||||||
}
|
}
|
||||||
if (top_stats.get_total() == 0)
|
if (top_stats.get_total() == 0)
|
||||||
return 0.0f;
|
return bottom_shift != 0 ? word_res->x_height : 0.0f;
|
||||||
// The new xheight is just the median vote, which is then scaled out
|
// 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.
|
// of BLN space back to pixel space to get the x-height in pixel space.
|
||||||
float new_xht = top_stats.median();
|
float new_xht = top_stats.median();
|
||||||
if (debug_x_ht_level >= 20) {
|
if (debug_x_ht_level >= 2) {
|
||||||
tprintf("Median xht=%f\n", new_xht);
|
tprintf("Median xht=%f\n", new_xht);
|
||||||
tprintf("Mode20:A: New x-height = %f (norm), %f (orig)\n",
|
tprintf("Mode20:A: New x-height = %f (norm), %f (orig)\n",
|
||||||
new_xht, new_xht / word_res->denorm.y_scale());
|
new_xht, new_xht / word_res->denorm.y_scale());
|
||||||
@ -159,7 +208,7 @@ float Tesseract::ComputeCompatibleXheight(WERD_RES *word_res) {
|
|||||||
if (fabs(new_xht - kBlnXHeight) >= x_ht_min_change)
|
if (fabs(new_xht - kBlnXHeight) >= x_ht_min_change)
|
||||||
return new_xht / word_res->denorm.y_scale();
|
return new_xht / word_res->denorm.y_scale();
|
||||||
else
|
else
|
||||||
return 0.0f;
|
return bottom_shift != 0 ? word_res->x_height : 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace tesseract
|
} // namespace tesseract
|
||||||
|
@ -26,15 +26,23 @@
|
|||||||
|
|
||||||
namespace tesseract {
|
namespace tesseract {
|
||||||
|
|
||||||
PageIterator::PageIterator(PAGE_RES* page_res, Tesseract* tesseract,
|
PageIterator::PageIterator(PAGE_RES* page_res, Tesseract* tesseract, int scale,
|
||||||
int scale, int scaled_yres,
|
int scaled_yres, int rect_left, int rect_top,
|
||||||
int rect_left, int rect_top,
|
|
||||||
int rect_width, int rect_height)
|
int rect_width, int rect_height)
|
||||||
: page_res_(page_res), tesseract_(tesseract),
|
: page_res_(page_res),
|
||||||
word_(NULL), word_length_(0), blob_index_(0), cblob_it_(NULL),
|
tesseract_(tesseract),
|
||||||
scale_(scale), scaled_yres_(scaled_yres),
|
word_(NULL),
|
||||||
rect_left_(rect_left), rect_top_(rect_top),
|
word_length_(0),
|
||||||
rect_width_(rect_width), rect_height_(rect_height) {
|
blob_index_(0),
|
||||||
|
cblob_it_(NULL),
|
||||||
|
include_upper_dots_(false),
|
||||||
|
include_lower_dots_(false),
|
||||||
|
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);
|
it_ = new PAGE_RES_IT(page_res);
|
||||||
PageIterator::Begin();
|
PageIterator::Begin();
|
||||||
}
|
}
|
||||||
@ -50,12 +58,20 @@ PageIterator::~PageIterator() {
|
|||||||
* objects at a higher level.
|
* objects at a higher level.
|
||||||
*/
|
*/
|
||||||
PageIterator::PageIterator(const PageIterator& src)
|
PageIterator::PageIterator(const PageIterator& src)
|
||||||
: page_res_(src.page_res_), tesseract_(src.tesseract_),
|
: page_res_(src.page_res_),
|
||||||
word_(NULL), word_length_(src.word_length_),
|
tesseract_(src.tesseract_),
|
||||||
blob_index_(src.blob_index_), cblob_it_(NULL),
|
word_(NULL),
|
||||||
scale_(src.scale_), scaled_yres_(src.scaled_yres_),
|
word_length_(src.word_length_),
|
||||||
rect_left_(src.rect_left_), rect_top_(src.rect_top_),
|
blob_index_(src.blob_index_),
|
||||||
rect_width_(src.rect_width_), rect_height_(src.rect_height_) {
|
cblob_it_(NULL),
|
||||||
|
include_upper_dots_(src.include_upper_dots_),
|
||||||
|
include_lower_dots_(src.include_lower_dots_),
|
||||||
|
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_);
|
it_ = new PAGE_RES_IT(*src.it_);
|
||||||
BeginWord(src.blob_index_);
|
BeginWord(src.blob_index_);
|
||||||
}
|
}
|
||||||
@ -63,6 +79,8 @@ PageIterator::PageIterator(const PageIterator& src)
|
|||||||
const PageIterator& PageIterator::operator=(const PageIterator& src) {
|
const PageIterator& PageIterator::operator=(const PageIterator& src) {
|
||||||
page_res_ = src.page_res_;
|
page_res_ = src.page_res_;
|
||||||
tesseract_ = src.tesseract_;
|
tesseract_ = src.tesseract_;
|
||||||
|
include_upper_dots_ = src.include_upper_dots_;
|
||||||
|
include_lower_dots_ = src.include_lower_dots_;
|
||||||
scale_ = src.scale_;
|
scale_ = src.scale_;
|
||||||
scaled_yres_ = src.scaled_yres_;
|
scaled_yres_ = src.scaled_yres_;
|
||||||
rect_left_ = src.rect_left_;
|
rect_left_ = src.rect_left_;
|
||||||
@ -252,16 +270,19 @@ bool PageIterator::BoundingBoxInternal(PageIteratorLevel level,
|
|||||||
PARA *para = NULL;
|
PARA *para = NULL;
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case RIL_BLOCK:
|
case RIL_BLOCK:
|
||||||
box = it_->block()->block->bounding_box();
|
box = it_->block()->block->restricted_bounding_box(include_upper_dots_,
|
||||||
|
include_lower_dots_);
|
||||||
break;
|
break;
|
||||||
case RIL_PARA:
|
case RIL_PARA:
|
||||||
para = it_->row()->row->para();
|
para = it_->row()->row->para();
|
||||||
// explicit fall-through.
|
// explicit fall-through.
|
||||||
case RIL_TEXTLINE:
|
case RIL_TEXTLINE:
|
||||||
box = it_->row()->row->bounding_box();
|
box = it_->row()->row->restricted_bounding_box(include_upper_dots_,
|
||||||
|
include_lower_dots_);
|
||||||
break;
|
break;
|
||||||
case RIL_WORD:
|
case RIL_WORD:
|
||||||
box = it_->word()->word->bounding_box();
|
box = it_->word()->word->restricted_bounding_box(include_upper_dots_,
|
||||||
|
include_lower_dots_);
|
||||||
break;
|
break;
|
||||||
case RIL_SYMBOL:
|
case RIL_SYMBOL:
|
||||||
if (cblob_it_ == NULL)
|
if (cblob_it_ == NULL)
|
||||||
@ -387,39 +408,23 @@ Pix* PageIterator::GetBinaryImage(PageIteratorLevel level) const {
|
|||||||
int left, top, right, bottom;
|
int left, top, right, bottom;
|
||||||
if (!BoundingBoxInternal(level, &left, &top, &right, &bottom))
|
if (!BoundingBoxInternal(level, &left, &top, &right, &bottom))
|
||||||
return NULL;
|
return NULL;
|
||||||
Pix* pix = NULL;
|
if (level == RIL_SYMBOL && cblob_it_ != NULL &&
|
||||||
switch (level) {
|
cblob_it_->data()->area() != 0)
|
||||||
case RIL_BLOCK:
|
return cblob_it_->data()->render();
|
||||||
case RIL_PARA:
|
Box* box = boxCreate(left, top, right - left, bottom - top);
|
||||||
int bleft, btop, bright, bbottom;
|
Pix* pix = pixClipRectangle(tesseract_->pix_binary(), box, NULL);
|
||||||
BoundingBoxInternal(RIL_BLOCK, &bleft, &btop, &bright, &bbottom);
|
boxDestroy(&box);
|
||||||
pix = it_->block()->block->render_mask();
|
if (level == RIL_BLOCK || level == RIL_PARA) {
|
||||||
// AND the mask and the image.
|
// Clip to the block polygon as well.
|
||||||
pixRasterop(pix, 0, 0, pixGetWidth(pix), pixGetHeight(pix),
|
TBOX mask_box;
|
||||||
PIX_SRC & PIX_DST, tesseract_->pix_binary(),
|
Pix* mask = it_->block()->block->render_mask(&mask_box);
|
||||||
bleft, btop);
|
int mask_x = left - mask_box.left();
|
||||||
if (level == RIL_PARA) {
|
int mask_y = top - (tesseract_->ImageHeight() - mask_box.top());
|
||||||
// RIL_PARA needs further attention:
|
// AND the mask and pix, putting the result in pix.
|
||||||
// clip the paragraph from the block mask.
|
pixRasterop(pix, MAX(0, -mask_x), MAX(0, -mask_y), pixGetWidth(pix),
|
||||||
Box* box = boxCreate(left - bleft, top - btop,
|
pixGetHeight(pix), PIX_SRC & PIX_DST, mask, MAX(0, mask_x),
|
||||||
right - left, bottom - top);
|
MAX(0, mask_y));
|
||||||
Pix* pix2 = pixClipRectangle(pix, box, NULL);
|
pixDestroy(&mask);
|
||||||
boxDestroy(&box);
|
|
||||||
pixDestroy(&pix);
|
|
||||||
pix = pix2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RIL_TEXTLINE:
|
|
||||||
case RIL_WORD:
|
|
||||||
case RIL_SYMBOL:
|
|
||||||
if (level == RIL_SYMBOL && cblob_it_ != NULL &&
|
|
||||||
cblob_it_->data()->area() != 0)
|
|
||||||
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;
|
return pix;
|
||||||
}
|
}
|
||||||
@ -452,17 +457,24 @@ Pix* PageIterator::GetImage(PageIteratorLevel level, int padding,
|
|||||||
Box* box = boxCreate(*left, *top, right - *left, bottom - *top);
|
Box* box = boxCreate(*left, *top, right - *left, bottom - *top);
|
||||||
Pix* grey_pix = pixClipRectangle(original_img, box, NULL);
|
Pix* grey_pix = pixClipRectangle(original_img, box, NULL);
|
||||||
boxDestroy(&box);
|
boxDestroy(&box);
|
||||||
if (level == RIL_BLOCK) {
|
if (level == RIL_BLOCK || level == RIL_PARA) {
|
||||||
Pix* mask = it_->block()->block->render_mask();
|
// Clip to the block polygon as well.
|
||||||
Pix* expanded_mask = pixCreate(right - *left, bottom - *top, 1);
|
TBOX mask_box;
|
||||||
pixRasterop(expanded_mask, padding, padding,
|
Pix* mask = it_->block()->block->render_mask(&mask_box);
|
||||||
pixGetWidth(mask), pixGetHeight(mask),
|
// Copy the mask registered correctly into an image the size of grey_pix.
|
||||||
PIX_SRC, mask, 0, 0);
|
int mask_x = *left - mask_box.left();
|
||||||
|
int mask_y = *top - (pixGetHeight(original_img) - mask_box.top());
|
||||||
|
int width = pixGetWidth(grey_pix);
|
||||||
|
int height = pixGetHeight(grey_pix);
|
||||||
|
Pix* resized_mask = pixCreate(width, height, 1);
|
||||||
|
pixRasterop(resized_mask, MAX(0, -mask_x), MAX(0, -mask_y), width, height,
|
||||||
|
PIX_SRC, mask, MAX(0, mask_x), MAX(0, mask_y));
|
||||||
pixDestroy(&mask);
|
pixDestroy(&mask);
|
||||||
pixDilateBrick(expanded_mask, expanded_mask, 2*padding + 1, 2*padding + 1);
|
pixDilateBrick(resized_mask, resized_mask, 2 * padding + 1,
|
||||||
pixInvert(expanded_mask, expanded_mask);
|
2 * padding + 1);
|
||||||
pixSetMasked(grey_pix, expanded_mask, MAX_UINT32);
|
pixInvert(resized_mask, resized_mask);
|
||||||
pixDestroy(&expanded_mask);
|
pixSetMasked(grey_pix, resized_mask, MAX_UINT32);
|
||||||
|
pixDestroy(&resized_mask);
|
||||||
}
|
}
|
||||||
return grey_pix;
|
return grey_pix;
|
||||||
}
|
}
|
||||||
|
@ -179,6 +179,21 @@ class TESS_API PageIterator {
|
|||||||
// If an image rectangle has been set in the API, then returned coordinates
|
// If an image rectangle has been set in the API, then returned coordinates
|
||||||
// relate to the original (full) image, rather than the rectangle.
|
// relate to the original (full) image, rather than the rectangle.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls what to include in a bounding box. Bounding boxes of all levels
|
||||||
|
* between RIL_WORD and RIL_BLOCK can include or exclude potential diacritics.
|
||||||
|
* Between layout analysis and recognition, it isn't known where all
|
||||||
|
* diacritics belong, so this control is used to include or exclude some
|
||||||
|
* diacritics that are above or below the main body of the word. In most cases
|
||||||
|
* where the placement is obvious, and after recognition, it doesn't make as
|
||||||
|
* much difference, as the diacritics will already be included in the word.
|
||||||
|
*/
|
||||||
|
void SetBoundingBoxComponents(bool include_upper_dots,
|
||||||
|
bool include_lower_dots) {
|
||||||
|
include_upper_dots_ = include_upper_dots;
|
||||||
|
include_lower_dots_ = include_lower_dots;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the bounding rectangle of the current object at the given level.
|
* Returns the bounding rectangle of the current object at the given level.
|
||||||
* See comment on coordinate system above.
|
* See comment on coordinate system above.
|
||||||
@ -332,6 +347,9 @@ class TESS_API PageIterator {
|
|||||||
* Owned by this ResultIterator.
|
* Owned by this ResultIterator.
|
||||||
*/
|
*/
|
||||||
C_BLOB_IT* cblob_it_;
|
C_BLOB_IT* cblob_it_;
|
||||||
|
/** Control over what to include in bounding boxes. */
|
||||||
|
bool include_upper_dots_;
|
||||||
|
bool include_lower_dots_;
|
||||||
/** Parameters saved from the Thresholder. Needed to rebuild coordinates.*/
|
/** Parameters saved from the Thresholder. Needed to rebuild coordinates.*/
|
||||||
int scale_;
|
int scale_;
|
||||||
int scaled_yres_;
|
int scaled_yres_;
|
||||||
|
@ -134,12 +134,20 @@ int Tesseract::SegmentPage(const STRING* input_file, BLOCK_LIST* blocks,
|
|||||||
// UNLV file present. Use PSM_SINGLE_BLOCK.
|
// UNLV file present. Use PSM_SINGLE_BLOCK.
|
||||||
pageseg_mode = PSM_SINGLE_BLOCK;
|
pageseg_mode = PSM_SINGLE_BLOCK;
|
||||||
}
|
}
|
||||||
|
// The diacritic_blobs holds noise blobs that may be diacritics. They
|
||||||
|
// are separated out on areas of the image that seem noisy and short-circuit
|
||||||
|
// the layout process, going straight from the initial partition creation
|
||||||
|
// right through to after word segmentation, where they are added to the
|
||||||
|
// rej_cblobs list of the most appropriate word. From there classification
|
||||||
|
// will determine whether they are used.
|
||||||
|
BLOBNBOX_LIST diacritic_blobs;
|
||||||
int auto_page_seg_ret_val = 0;
|
int auto_page_seg_ret_val = 0;
|
||||||
TO_BLOCK_LIST to_blocks;
|
TO_BLOCK_LIST to_blocks;
|
||||||
if (PSM_OSD_ENABLED(pageseg_mode) || PSM_BLOCK_FIND_ENABLED(pageseg_mode) ||
|
if (PSM_OSD_ENABLED(pageseg_mode) || PSM_BLOCK_FIND_ENABLED(pageseg_mode) ||
|
||||||
PSM_SPARSE(pageseg_mode)) {
|
PSM_SPARSE(pageseg_mode)) {
|
||||||
auto_page_seg_ret_val =
|
auto_page_seg_ret_val = AutoPageSeg(
|
||||||
AutoPageSeg(pageseg_mode, blocks, &to_blocks, osd_tess, osr);
|
pageseg_mode, blocks, &to_blocks,
|
||||||
|
enable_noise_removal ? &diacritic_blobs : NULL, osd_tess, osr);
|
||||||
if (pageseg_mode == PSM_OSD_ONLY)
|
if (pageseg_mode == PSM_OSD_ONLY)
|
||||||
return auto_page_seg_ret_val;
|
return auto_page_seg_ret_val;
|
||||||
// To create blobs from the image region bounds uncomment this line:
|
// To create blobs from the image region bounds uncomment this line:
|
||||||
@ -171,7 +179,7 @@ int Tesseract::SegmentPage(const STRING* input_file, BLOCK_LIST* blocks,
|
|||||||
|
|
||||||
textord_.TextordPage(pageseg_mode, reskew_, width, height, pix_binary_,
|
textord_.TextordPage(pageseg_mode, reskew_, width, height, pix_binary_,
|
||||||
pix_thresholds_, pix_grey_, splitting || cjk_mode,
|
pix_thresholds_, pix_grey_, splitting || cjk_mode,
|
||||||
blocks, &to_blocks);
|
&diacritic_blobs, blocks, &to_blocks);
|
||||||
return auto_page_seg_ret_val;
|
return auto_page_seg_ret_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +205,6 @@ static void WriteDebugBackgroundImage(bool printable, Pix* pix_binary) {
|
|||||||
pixDestroy(&grey_pix);
|
pixDestroy(&grey_pix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto page segmentation. Divide the page image into blocks of uniform
|
* Auto page segmentation. Divide the page image into blocks of uniform
|
||||||
* text linespacing and images.
|
* text linespacing and images.
|
||||||
@ -207,9 +214,14 @@ static void WriteDebugBackgroundImage(bool printable, Pix* pix_binary) {
|
|||||||
* The output goes in the blocks list with corresponding TO_BLOCKs in the
|
* The output goes in the blocks list with corresponding TO_BLOCKs in the
|
||||||
* to_blocks list.
|
* to_blocks list.
|
||||||
*
|
*
|
||||||
* If single_column is true, then no attempt is made to divide the image
|
* If !PSM_COL_FIND_ENABLED(pageseg_mode), then no attempt is made to divide
|
||||||
* into columns, but multiple blocks are still made if the text is of
|
* the image into columns, but multiple blocks are still made if the text is
|
||||||
* non-uniform linespacing.
|
* of non-uniform linespacing.
|
||||||
|
*
|
||||||
|
* If diacritic_blobs is non-null, then diacritics/noise blobs, that would
|
||||||
|
* confuse layout anaylsis by causing textline overlap, are placed there,
|
||||||
|
* with the expectation that they will be reassigned to words later and
|
||||||
|
* noise/diacriticness determined via classification.
|
||||||
*
|
*
|
||||||
* If osd (orientation and script detection) is true then that is performed
|
* If osd (orientation and script detection) is true then that is performed
|
||||||
* as well. If only_osd is true, then only orientation and script detection is
|
* as well. If only_osd is true, then only orientation and script detection is
|
||||||
@ -217,9 +229,10 @@ static void WriteDebugBackgroundImage(bool printable, Pix* pix_binary) {
|
|||||||
* another Tesseract that was initialized especially for osd, and the results
|
* another Tesseract that was initialized especially for osd, and the results
|
||||||
* will be output into osr (orientation and script result).
|
* will be output into osr (orientation and script result).
|
||||||
*/
|
*/
|
||||||
int Tesseract::AutoPageSeg(PageSegMode pageseg_mode,
|
int Tesseract::AutoPageSeg(PageSegMode pageseg_mode, BLOCK_LIST* blocks,
|
||||||
BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks,
|
TO_BLOCK_LIST* to_blocks,
|
||||||
Tesseract* osd_tess, OSResults* osr) {
|
BLOBNBOX_LIST* diacritic_blobs, Tesseract* osd_tess,
|
||||||
|
OSResults* osr) {
|
||||||
if (textord_debug_images) {
|
if (textord_debug_images) {
|
||||||
WriteDebugBackgroundImage(textord_debug_printable, pix_binary_);
|
WriteDebugBackgroundImage(textord_debug_printable, pix_binary_);
|
||||||
}
|
}
|
||||||
@ -247,10 +260,9 @@ int Tesseract::AutoPageSeg(PageSegMode pageseg_mode,
|
|||||||
if (equ_detect_) {
|
if (equ_detect_) {
|
||||||
finder->SetEquationDetect(equ_detect_);
|
finder->SetEquationDetect(equ_detect_);
|
||||||
}
|
}
|
||||||
result = finder->FindBlocks(pageseg_mode, scaled_color_, scaled_factor_,
|
result = finder->FindBlocks(
|
||||||
to_block, photomask_pix,
|
pageseg_mode, scaled_color_, scaled_factor_, to_block, photomask_pix,
|
||||||
pix_thresholds_, pix_grey_,
|
pix_thresholds_, pix_grey_, &found_blocks, diacritic_blobs, to_blocks);
|
||||||
&found_blocks, to_blocks);
|
|
||||||
if (result >= 0)
|
if (result >= 0)
|
||||||
finder->GetDeskewVectors(&deskew_, &reskew_);
|
finder->GetDeskewVectors(&deskew_, &reskew_);
|
||||||
delete finder;
|
delete finder;
|
||||||
@ -340,6 +352,7 @@ ColumnFinder* Tesseract::SetupPageSegAndDetectOrientation(
|
|||||||
finder = new ColumnFinder(static_cast<int>(to_block->line_size),
|
finder = new ColumnFinder(static_cast<int>(to_block->line_size),
|
||||||
blkbox.botleft(), blkbox.topright(),
|
blkbox.botleft(), blkbox.topright(),
|
||||||
source_resolution_, textord_use_cjk_fp_model,
|
source_resolution_, textord_use_cjk_fp_model,
|
||||||
|
textord_tabfind_aligned_gap_fraction,
|
||||||
&v_lines, &h_lines, vertical_x, vertical_y);
|
&v_lines, &h_lines, vertical_x, vertical_y);
|
||||||
|
|
||||||
finder->SetupAndFilterNoise(*photo_mask_pix, to_block);
|
finder->SetupAndFilterNoise(*photo_mask_pix, to_block);
|
||||||
@ -354,7 +367,12 @@ ColumnFinder* Tesseract::SetupPageSegAndDetectOrientation(
|
|||||||
// We want the text lines horizontal, (vertical text indicates vertical
|
// We want the text lines horizontal, (vertical text indicates vertical
|
||||||
// textlines) which may conflict (eg vertically written CJK).
|
// textlines) which may conflict (eg vertically written CJK).
|
||||||
int osd_orientation = 0;
|
int osd_orientation = 0;
|
||||||
bool vertical_text = finder->IsVerticallyAlignedText(to_block, &osd_blobs);
|
bool vertical_text = textord_tabfind_force_vertical_text;
|
||||||
|
if (!vertical_text && textord_tabfind_vertical_text) {
|
||||||
|
vertical_text =
|
||||||
|
finder->IsVerticallyAlignedText(textord_tabfind_vertical_text_ratio,
|
||||||
|
to_block, &osd_blobs);
|
||||||
|
}
|
||||||
if (osd && osd_tess != NULL && osr != NULL) {
|
if (osd && osd_tess != NULL && osr != NULL) {
|
||||||
GenericVector<int> osd_scripts;
|
GenericVector<int> osd_scripts;
|
||||||
if (osd_tess != this) {
|
if (osd_tess != this) {
|
||||||
|
@ -24,7 +24,9 @@
|
|||||||
#define VARABLED_H
|
#define VARABLED_H
|
||||||
|
|
||||||
#include "elst.h"
|
#include "elst.h"
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
#include "scrollview.h"
|
#include "scrollview.h"
|
||||||
|
#endif
|
||||||
#include "params.h"
|
#include "params.h"
|
||||||
#include "tesseractclass.h"
|
#include "tesseractclass.h"
|
||||||
|
|
||||||
|
@ -655,7 +655,8 @@ void show_point(PAGE_RES* page_res, float x, float y) {
|
|||||||
FCOORD pt(x, y);
|
FCOORD pt(x, y);
|
||||||
PAGE_RES_IT pr_it(page_res);
|
PAGE_RES_IT pr_it(page_res);
|
||||||
|
|
||||||
char msg[160];
|
const int kBufsize = 512;
|
||||||
|
char msg[kBufsize];
|
||||||
char *msg_ptr = msg;
|
char *msg_ptr = msg;
|
||||||
|
|
||||||
msg_ptr += sprintf(msg_ptr, "Pt:(%0.3f, %0.3f) ", x, y);
|
msg_ptr += sprintf(msg_ptr, "Pt:(%0.3f, %0.3f) ", x, y);
|
||||||
|
@ -207,8 +207,7 @@ void Tesseract::ambigs_classify_and_output(const char *label,
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
WordData word_data(*pr_it);
|
WordData word_data(*pr_it);
|
||||||
SetupWordPassN(1, &word_data);
|
SetupWordPassN(1, &word_data);
|
||||||
classify_word_and_language(&Tesseract::classify_word_pass1,
|
classify_word_and_language(1, pr_it, &word_data);
|
||||||
pr_it, &word_data);
|
|
||||||
WERD_RES* werd_res = word_data.word;
|
WERD_RES* werd_res = word_data.word;
|
||||||
WERD_CHOICE *best_choice = werd_res->best_choice;
|
WERD_CHOICE *best_choice = werd_res->best_choice;
|
||||||
ASSERT_HOST(best_choice != NULL);
|
ASSERT_HOST(best_choice != NULL);
|
||||||
|
@ -34,6 +34,13 @@ ResultIterator::ResultIterator(const LTRResultIterator &resit)
|
|||||||
: LTRResultIterator(resit) {
|
: LTRResultIterator(resit) {
|
||||||
in_minor_direction_ = false;
|
in_minor_direction_ = false;
|
||||||
at_beginning_of_minor_run_ = false;
|
at_beginning_of_minor_run_ = false;
|
||||||
|
preserve_interword_spaces_ = false;
|
||||||
|
|
||||||
|
BoolParam *p = ParamUtils::FindParam<BoolParam>(
|
||||||
|
"preserve_interword_spaces", GlobalParams()->bool_params,
|
||||||
|
tesseract_->params()->bool_params);
|
||||||
|
if (p != NULL) preserve_interword_spaces_ = (bool)(*p);
|
||||||
|
|
||||||
current_paragraph_is_ltr_ = CurrentParagraphIsLtr();
|
current_paragraph_is_ltr_ = CurrentParagraphIsLtr();
|
||||||
MoveToLogicalStartOfTextline();
|
MoveToLogicalStartOfTextline();
|
||||||
}
|
}
|
||||||
@ -629,14 +636,17 @@ void ResultIterator::IterateAndAppendUTF8TextlineText(STRING *text) {
|
|||||||
|
|
||||||
int words_appended = 0;
|
int words_appended = 0;
|
||||||
do {
|
do {
|
||||||
|
int numSpaces = preserve_interword_spaces_ ? it_->word()->word->space()
|
||||||
|
: (words_appended > 0);
|
||||||
|
for (int i = 0; i < numSpaces; ++i) {
|
||||||
|
*text += " ";
|
||||||
|
}
|
||||||
AppendUTF8WordText(text);
|
AppendUTF8WordText(text);
|
||||||
words_appended++;
|
words_appended++;
|
||||||
*text += " ";
|
|
||||||
} while (Next(RIL_WORD) && !IsAtBeginningOf(RIL_TEXTLINE));
|
} while (Next(RIL_WORD) && !IsAtBeginningOf(RIL_TEXTLINE));
|
||||||
if (BidiDebug(1)) {
|
if (BidiDebug(1)) {
|
||||||
tprintf("%d words printed\n", words_appended);
|
tprintf("%d words printed\n", words_appended);
|
||||||
}
|
}
|
||||||
text->truncate_at(text->length() - 1);
|
|
||||||
*text += line_separator_;
|
*text += line_separator_;
|
||||||
// If we just finished a paragraph, add an extra newline.
|
// If we just finished a paragraph, add an extra newline.
|
||||||
if (it_->block() == NULL || IsAtBeginningOf(RIL_PARA))
|
if (it_->block() == NULL || IsAtBeginningOf(RIL_PARA))
|
||||||
|
@ -46,8 +46,8 @@ class TESS_API ResultIterator : public LTRResultIterator {
|
|||||||
virtual ~ResultIterator() {}
|
virtual ~ResultIterator() {}
|
||||||
|
|
||||||
// ============= Moving around within the page ============.
|
// ============= Moving around within the page ============.
|
||||||
/**
|
/**
|
||||||
* Moves the iterator to point to the start of the page to begin
|
* Moves the iterator to point to the start of the page to begin
|
||||||
* an iteration.
|
* an iteration.
|
||||||
*/
|
*/
|
||||||
virtual void Begin();
|
virtual void Begin();
|
||||||
@ -181,7 +181,7 @@ class TESS_API ResultIterator : public LTRResultIterator {
|
|||||||
void MoveToLogicalStartOfTextline();
|
void MoveToLogicalStartOfTextline();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Precondition: current_paragraph_is_ltr_ and in_minor_direction_
|
* Precondition: current_paragraph_is_ltr_ and in_minor_direction_
|
||||||
* are set.
|
* are set.
|
||||||
*/
|
*/
|
||||||
void MoveToLogicalStartOfWord();
|
void MoveToLogicalStartOfWord();
|
||||||
@ -231,6 +231,12 @@ class TESS_API ResultIterator : public LTRResultIterator {
|
|||||||
|
|
||||||
/** Is the currently pointed-at character in a minor-direction sequence? */
|
/** Is the currently pointed-at character in a minor-direction sequence? */
|
||||||
bool in_minor_direction_;
|
bool in_minor_direction_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should detected inter-word spaces be preserved, or "compressed" to a single
|
||||||
|
* space character (default behavior).
|
||||||
|
*/
|
||||||
|
bool preserve_interword_spaces_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tesseract.
|
} // namespace tesseract.
|
||||||
|
@ -194,7 +194,11 @@ bool Tesseract::init_tesseract_lang_data(
|
|||||||
if (tessdata_manager_debug_level) tprintf("Loaded ambigs\n");
|
if (tessdata_manager_debug_level) tprintf("Loaded ambigs\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Cube objects if necessary.
|
// The various OcrEngineMode settings (see publictypes.h) determine which
|
||||||
|
// engine-specific data files need to be loaded. Currently everything needs
|
||||||
|
// the base tesseract data, which supplies other useful information, but
|
||||||
|
// alternative engines, such as cube and LSTM are optional.
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
if (tessedit_ocr_engine_mode == OEM_CUBE_ONLY) {
|
if (tessedit_ocr_engine_mode == OEM_CUBE_ONLY) {
|
||||||
ASSERT_HOST(init_cube_objects(false, &tessdata_manager));
|
ASSERT_HOST(init_cube_objects(false, &tessdata_manager));
|
||||||
if (tessdata_manager_debug_level)
|
if (tessdata_manager_debug_level)
|
||||||
@ -204,7 +208,7 @@ bool Tesseract::init_tesseract_lang_data(
|
|||||||
if (tessdata_manager_debug_level)
|
if (tessdata_manager_debug_level)
|
||||||
tprintf("Loaded Cube with combiner\n");
|
tprintf("Loaded Cube with combiner\n");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
// Init ParamsModel.
|
// Init ParamsModel.
|
||||||
// Load pass1 and pass2 weights (for now these two sets are the same, but in
|
// Load pass1 and pass2 weights (for now these two sets are the same, but in
|
||||||
// the future separate sets of weights can be generated).
|
// the future separate sets of weights can be generated).
|
||||||
@ -475,5 +479,4 @@ enum CMD_EVENTS
|
|||||||
RECOG_PSEUDO,
|
RECOG_PSEUDO,
|
||||||
ACTION_2_CMD_EVENT
|
ACTION_2_CMD_EVENT
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tesseract
|
} // namespace tesseract
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,12 @@
|
|||||||
///////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////
|
||||||
// File: tesseractclass.h
|
// File: tesseractclass.h
|
||||||
// Description: An instance of Tesseract. For thread safety, *every*
|
// Description: The Tesseract class. It holds/owns everything needed
|
||||||
|
// to run Tesseract on a single language, and also a set of
|
||||||
|
// sub-Tesseracts to run sub-languages. For thread safety, *every*
|
||||||
// global variable goes in here, directly, or indirectly.
|
// global variable goes in here, directly, or indirectly.
|
||||||
|
// This makes it safe to run multiple Tesseracts in different
|
||||||
|
// threads in parallel, and keeps the different language
|
||||||
|
// instances separate.
|
||||||
// Author: Ray Smith
|
// Author: Ray Smith
|
||||||
// Created: Fri Mar 07 08:17:01 PST 2008
|
// Created: Fri Mar 07 08:17:01 PST 2008
|
||||||
//
|
//
|
||||||
@ -92,12 +97,16 @@ class WERD_RES;
|
|||||||
namespace tesseract {
|
namespace tesseract {
|
||||||
|
|
||||||
class ColumnFinder;
|
class ColumnFinder;
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
class CubeLineObject;
|
class CubeLineObject;
|
||||||
class CubeObject;
|
class CubeObject;
|
||||||
class CubeRecoContext;
|
class CubeRecoContext;
|
||||||
|
#endif
|
||||||
class EquationDetect;
|
class EquationDetect;
|
||||||
class Tesseract;
|
class Tesseract;
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
class TesseractCubeCombiner;
|
class TesseractCubeCombiner;
|
||||||
|
#endif
|
||||||
|
|
||||||
// A collection of various variables for statistics and debugging.
|
// A collection of various variables for statistics and debugging.
|
||||||
struct TesseractStats {
|
struct TesseractStats {
|
||||||
@ -245,6 +254,15 @@ class Tesseract : public Wordrec {
|
|||||||
Tesseract* get_sub_lang(int index) const {
|
Tesseract* get_sub_lang(int index) const {
|
||||||
return sub_langs_[index];
|
return sub_langs_[index];
|
||||||
}
|
}
|
||||||
|
// Returns true if any language uses Tesseract (as opposed to cube).
|
||||||
|
bool AnyTessLang() const {
|
||||||
|
if (tessedit_ocr_engine_mode != OEM_CUBE_ONLY) return true;
|
||||||
|
for (int i = 0; i < sub_langs_.size(); ++i) {
|
||||||
|
if (sub_langs_[i]->tessedit_ocr_engine_mode != OEM_CUBE_ONLY)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void SetBlackAndWhitelist();
|
void SetBlackAndWhitelist();
|
||||||
|
|
||||||
@ -265,8 +283,8 @@ class Tesseract : public Wordrec {
|
|||||||
int SegmentPage(const STRING* input_file, BLOCK_LIST* blocks,
|
int SegmentPage(const STRING* input_file, BLOCK_LIST* blocks,
|
||||||
Tesseract* osd_tess, OSResults* osr);
|
Tesseract* osd_tess, OSResults* osr);
|
||||||
void SetupWordScripts(BLOCK_LIST* blocks);
|
void SetupWordScripts(BLOCK_LIST* blocks);
|
||||||
int AutoPageSeg(PageSegMode pageseg_mode,
|
int AutoPageSeg(PageSegMode pageseg_mode, BLOCK_LIST* blocks,
|
||||||
BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks,
|
TO_BLOCK_LIST* to_blocks, BLOBNBOX_LIST* diacritic_blobs,
|
||||||
Tesseract* osd_tess, OSResults* osr);
|
Tesseract* osd_tess, OSResults* osr);
|
||||||
ColumnFinder* SetupPageSegAndDetectOrientation(
|
ColumnFinder* SetupPageSegAndDetectOrientation(
|
||||||
bool single_column, bool osd, bool only_osd,
|
bool single_column, bool osd, bool only_osd,
|
||||||
@ -310,8 +328,46 @@ class Tesseract : public Wordrec {
|
|||||||
WordRecognizer recognizer,
|
WordRecognizer recognizer,
|
||||||
WERD_RES** in_word,
|
WERD_RES** in_word,
|
||||||
PointerVector<WERD_RES>* best_words);
|
PointerVector<WERD_RES>* best_words);
|
||||||
void classify_word_and_language(WordRecognizer recognizer,
|
// Moves good-looking "noise"/diacritics from the reject list to the main
|
||||||
PAGE_RES_IT* pr_it,
|
// blob list on the current word. Returns true if anything was done, and
|
||||||
|
// sets make_next_word_fuzzy if blob(s) were added to the end of the word.
|
||||||
|
bool ReassignDiacritics(int pass, PAGE_RES_IT* pr_it,
|
||||||
|
bool* make_next_word_fuzzy);
|
||||||
|
// Attempts to put noise/diacritic outlines into the blobs that they overlap.
|
||||||
|
// Input: a set of noisy outlines that probably belong to the real_word.
|
||||||
|
// Output: outlines that overlapped blobs are set to NULL and put back into
|
||||||
|
// the word, either in the blobs or in the reject list.
|
||||||
|
void AssignDiacriticsToOverlappingBlobs(
|
||||||
|
const GenericVector<C_OUTLINE*>& outlines, int pass, WERD* real_word,
|
||||||
|
PAGE_RES_IT* pr_it, GenericVector<bool>* word_wanted,
|
||||||
|
GenericVector<bool>* overlapped_any_blob,
|
||||||
|
GenericVector<C_BLOB*>* target_blobs);
|
||||||
|
// Attempts to assign non-overlapping outlines to their nearest blobs or
|
||||||
|
// make new blobs out of them.
|
||||||
|
void AssignDiacriticsToNewBlobs(const GenericVector<C_OUTLINE*>& outlines,
|
||||||
|
int pass, WERD* real_word, PAGE_RES_IT* pr_it,
|
||||||
|
GenericVector<bool>* word_wanted,
|
||||||
|
GenericVector<C_BLOB*>* target_blobs);
|
||||||
|
// Starting with ok_outlines set to indicate which outlines overlap the blob,
|
||||||
|
// chooses the optimal set (approximately) and returns true if any outlines
|
||||||
|
// are desired, in which case ok_outlines indicates which ones.
|
||||||
|
bool SelectGoodDiacriticOutlines(int pass, float certainty_threshold,
|
||||||
|
PAGE_RES_IT* pr_it, C_BLOB* blob,
|
||||||
|
const GenericVector<C_OUTLINE*>& outlines,
|
||||||
|
int num_outlines,
|
||||||
|
GenericVector<bool>* ok_outlines);
|
||||||
|
// Classifies the given blob plus the outlines flagged by ok_outlines, undoes
|
||||||
|
// the inclusion of the outlines, and returns the certainty of the raw choice.
|
||||||
|
float ClassifyBlobPlusOutlines(const GenericVector<bool>& ok_outlines,
|
||||||
|
const GenericVector<C_OUTLINE*>& outlines,
|
||||||
|
int pass_n, PAGE_RES_IT* pr_it, C_BLOB* blob,
|
||||||
|
STRING* best_str);
|
||||||
|
// Classifies the given blob (part of word_data->word->word) as an individual
|
||||||
|
// word, using languages, chopper etc, returning only the certainty of the
|
||||||
|
// best raw choice, and undoing all the work done to fake out the word.
|
||||||
|
float ClassifyBlobAsWord(int pass_n, PAGE_RES_IT* pr_it, C_BLOB* blob,
|
||||||
|
STRING* best_str, float* c2);
|
||||||
|
void classify_word_and_language(int pass_n, PAGE_RES_IT* pr_it,
|
||||||
WordData* word_data);
|
WordData* word_data);
|
||||||
void classify_word_pass1(const WordData& word_data,
|
void classify_word_pass1(const WordData& word_data,
|
||||||
WERD_RES** in_word,
|
WERD_RES** in_word,
|
||||||
@ -332,6 +388,11 @@ class Tesseract : public Wordrec {
|
|||||||
WERD_RES* word, WERD_RES* new_word);
|
WERD_RES* word, WERD_RES* new_word);
|
||||||
bool RunOldFixXht(WERD_RES *word, BLOCK* block, ROW *row);
|
bool RunOldFixXht(WERD_RES *word, BLOCK* block, ROW *row);
|
||||||
bool TrainedXheightFix(WERD_RES *word, BLOCK* block, ROW *row);
|
bool TrainedXheightFix(WERD_RES *word, BLOCK* block, ROW *row);
|
||||||
|
// Runs recognition with the test baseline shift and x-height and returns true
|
||||||
|
// if there was an improvement in recognition result.
|
||||||
|
bool TestNewNormalization(int original_misfits, float baseline_shift,
|
||||||
|
float new_x_ht, WERD_RES *word, BLOCK* block,
|
||||||
|
ROW *row);
|
||||||
BOOL8 recog_interactive(PAGE_RES_IT* pr_it);
|
BOOL8 recog_interactive(PAGE_RES_IT* pr_it);
|
||||||
|
|
||||||
// Set fonts of this word.
|
// Set fonts of this word.
|
||||||
@ -368,6 +429,7 @@ class Tesseract : public Wordrec {
|
|||||||
int *right_ok) const;
|
int *right_ok) const;
|
||||||
|
|
||||||
//// cube_control.cpp ///////////////////////////////////////////////////
|
//// cube_control.cpp ///////////////////////////////////////////////////
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
bool init_cube_objects(bool load_combiner,
|
bool init_cube_objects(bool load_combiner,
|
||||||
TessdataManager *tessdata_manager);
|
TessdataManager *tessdata_manager);
|
||||||
// Iterates through tesseract's results and calls cube on each word,
|
// Iterates through tesseract's results and calls cube on each word,
|
||||||
@ -393,6 +455,7 @@ class Tesseract : public Wordrec {
|
|||||||
Boxa** char_boxes, CharSamp*** char_samples);
|
Boxa** char_boxes, CharSamp*** char_samples);
|
||||||
bool create_cube_box_word(Boxa *char_boxes, int num_chars,
|
bool create_cube_box_word(Boxa *char_boxes, int num_chars,
|
||||||
TBOX word_box, BoxWord* box_word);
|
TBOX word_box, BoxWord* box_word);
|
||||||
|
#endif
|
||||||
//// output.h //////////////////////////////////////////////////////////
|
//// output.h //////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void output_pass(PAGE_RES_IT &page_res_it, const TBOX *target_word_box);
|
void output_pass(PAGE_RES_IT &page_res_it, const TBOX *target_word_box);
|
||||||
@ -699,8 +762,8 @@ class Tesseract : public Wordrec {
|
|||||||
// Creates a fake best_choice entry in each WERD_RES with the correct text.
|
// Creates a fake best_choice entry in each WERD_RES with the correct text.
|
||||||
void CorrectClassifyWords(PAGE_RES* page_res);
|
void CorrectClassifyWords(PAGE_RES* page_res);
|
||||||
// Call LearnWord to extract features for labelled blobs within each word.
|
// Call LearnWord to extract features for labelled blobs within each word.
|
||||||
// Features are written to the given filename.
|
// Features are stored in an internal buffer.
|
||||||
void ApplyBoxTraining(const STRING& filename, PAGE_RES* page_res);
|
void ApplyBoxTraining(const STRING& fontname, PAGE_RES* page_res);
|
||||||
|
|
||||||
//// fixxht.cpp ///////////////////////////////////////////////////////
|
//// fixxht.cpp ///////////////////////////////////////////////////////
|
||||||
// Returns the number of misfit blob tops in this word.
|
// Returns the number of misfit blob tops in this word.
|
||||||
@ -709,7 +772,7 @@ class Tesseract : public Wordrec {
|
|||||||
// maximally compatible with the result in word_res.
|
// maximally compatible with the result in word_res.
|
||||||
// Returns 0.0f if no x-height is found that is better than the current
|
// Returns 0.0f if no x-height is found that is better than the current
|
||||||
// estimate.
|
// estimate.
|
||||||
float ComputeCompatibleXheight(WERD_RES *word_res);
|
float ComputeCompatibleXheight(WERD_RES *word_res, float* baseline_shift);
|
||||||
//// Data members ///////////////////////////////////////////////////////
|
//// Data members ///////////////////////////////////////////////////////
|
||||||
// TODO(ocr-team): Find and remove obsolete parameters.
|
// TODO(ocr-team): Find and remove obsolete parameters.
|
||||||
BOOL_VAR_H(tessedit_resegment_from_boxes, false,
|
BOOL_VAR_H(tessedit_resegment_from_boxes, false,
|
||||||
@ -734,6 +797,8 @@ class Tesseract : public Wordrec {
|
|||||||
"Blacklist of chars not to recognize");
|
"Blacklist of chars not to recognize");
|
||||||
STRING_VAR_H(tessedit_char_whitelist, "",
|
STRING_VAR_H(tessedit_char_whitelist, "",
|
||||||
"Whitelist of chars to recognize");
|
"Whitelist of chars to recognize");
|
||||||
|
STRING_VAR_H(tessedit_char_unblacklist, "",
|
||||||
|
"List of chars to override tessedit_char_blacklist");
|
||||||
BOOL_VAR_H(tessedit_ambigs_training, false,
|
BOOL_VAR_H(tessedit_ambigs_training, false,
|
||||||
"Perform training for ambiguities");
|
"Perform training for ambiguities");
|
||||||
INT_VAR_H(pageseg_devanagari_split_strategy,
|
INT_VAR_H(pageseg_devanagari_split_strategy,
|
||||||
@ -781,6 +846,24 @@ class Tesseract : public Wordrec {
|
|||||||
"Enable single word correction based on the dictionary.");
|
"Enable single word correction based on the dictionary.");
|
||||||
INT_VAR_H(tessedit_bigram_debug, 0, "Amount of debug output for bigram "
|
INT_VAR_H(tessedit_bigram_debug, 0, "Amount of debug output for bigram "
|
||||||
"correction.");
|
"correction.");
|
||||||
|
BOOL_VAR_H(enable_noise_removal, true,
|
||||||
|
"Remove and conditionally reassign small outlines when they"
|
||||||
|
" confuse layout analysis, determining diacritics vs noise");
|
||||||
|
INT_VAR_H(debug_noise_removal, 0, "Debug reassignment of small outlines");
|
||||||
|
// Worst (min) certainty, for which a diacritic is allowed to make the base
|
||||||
|
// character worse and still be included.
|
||||||
|
double_VAR_H(noise_cert_basechar, -8.0, "Hingepoint for base char certainty");
|
||||||
|
// Worst (min) certainty, for which a non-overlapping diacritic is allowed to
|
||||||
|
// make the base character worse and still be included.
|
||||||
|
double_VAR_H(noise_cert_disjoint, -2.5, "Hingepoint for disjoint certainty");
|
||||||
|
// Worst (min) certainty, for which a diacritic is allowed to make a new
|
||||||
|
// stand-alone blob.
|
||||||
|
double_VAR_H(noise_cert_punc, -2.5, "Threshold for new punc char certainty");
|
||||||
|
// Factor of certainty margin for adding diacritics to not count as worse.
|
||||||
|
double_VAR_H(noise_cert_factor, 0.375,
|
||||||
|
"Scaling on certainty diff from Hingepoint");
|
||||||
|
INT_VAR_H(noise_maxperblob, 8, "Max diacritics to apply to a blob");
|
||||||
|
INT_VAR_H(noise_maxperword, 16, "Max diacritics to apply to a word");
|
||||||
INT_VAR_H(debug_x_ht_level, 0, "Reestimate debug");
|
INT_VAR_H(debug_x_ht_level, 0, "Reestimate debug");
|
||||||
BOOL_VAR_H(debug_acceptable_wds, false, "Dump word pass/fail chk");
|
BOOL_VAR_H(debug_acceptable_wds, false, "Dump word pass/fail chk");
|
||||||
STRING_VAR_H(chs_leading_punct, "('`\"", "Leading punctuation");
|
STRING_VAR_H(chs_leading_punct, "('`\"", "Leading punctuation");
|
||||||
@ -918,15 +1001,9 @@ class Tesseract : public Wordrec {
|
|||||||
BOOL_VAR_H(tessedit_write_rep_codes, false,
|
BOOL_VAR_H(tessedit_write_rep_codes, false,
|
||||||
"Write repetition char code");
|
"Write repetition char code");
|
||||||
BOOL_VAR_H(tessedit_write_unlv, false, "Write .unlv output file");
|
BOOL_VAR_H(tessedit_write_unlv, false, "Write .unlv output file");
|
||||||
|
BOOL_VAR_H(tessedit_create_txt, true, "Write .txt output file");
|
||||||
BOOL_VAR_H(tessedit_create_hocr, false, "Write .html hOCR output file");
|
BOOL_VAR_H(tessedit_create_hocr, false, "Write .html hOCR output file");
|
||||||
BOOL_VAR_H(tessedit_create_pdf, false, "Write .pdf output file");
|
BOOL_VAR_H(tessedit_create_pdf, false, "Write .pdf output file");
|
||||||
INT_VAR_H(tessedit_pdf_compression, 0, "Type of image encoding in pdf output:"
|
|
||||||
"0 - autoselection (default); "
|
|
||||||
"1 - jpeg; "
|
|
||||||
"2 - G4; "
|
|
||||||
"3 - flate");
|
|
||||||
INT_VAR_H(tessedit_pdf_jpg_quality, 85, "Quality level of jpeg image "
|
|
||||||
"compression in pdf output");
|
|
||||||
STRING_VAR_H(unrecognised_char, "|",
|
STRING_VAR_H(unrecognised_char, "|",
|
||||||
"Output char for unidentified blobs");
|
"Output char for unidentified blobs");
|
||||||
INT_VAR_H(suspect_level, 99, "Suspect marker level");
|
INT_VAR_H(suspect_level, 99, "Suspect marker level");
|
||||||
@ -990,7 +1067,22 @@ class Tesseract : public Wordrec {
|
|||||||
"Only initialize with the config file. Useful if the instance is "
|
"Only initialize with the config file. Useful if the instance is "
|
||||||
"not going to be used for OCR but say only for layout analysis.");
|
"not going to be used for OCR but say only for layout analysis.");
|
||||||
BOOL_VAR_H(textord_equation_detect, false, "Turn on equation detector");
|
BOOL_VAR_H(textord_equation_detect, false, "Turn on equation detector");
|
||||||
|
BOOL_VAR_H(textord_tabfind_vertical_text, true, "Enable vertical detection");
|
||||||
|
BOOL_VAR_H(textord_tabfind_force_vertical_text, false,
|
||||||
|
"Force using vertical text page mode");
|
||||||
|
double_VAR_H(textord_tabfind_vertical_text_ratio, 0.5,
|
||||||
|
"Fraction of textlines deemed vertical to use vertical page "
|
||||||
|
"mode");
|
||||||
|
double_VAR_H(textord_tabfind_aligned_gap_fraction, 0.75,
|
||||||
|
"Fraction of height used as a minimum gap for aligned blobs.");
|
||||||
INT_VAR_H(tessedit_parallelize, 0, "Run in parallel where possible");
|
INT_VAR_H(tessedit_parallelize, 0, "Run in parallel where possible");
|
||||||
|
BOOL_VAR_H(preserve_interword_spaces, false,
|
||||||
|
"Preserve multiple interword spaces");
|
||||||
|
BOOL_VAR_H(include_page_breaks, false,
|
||||||
|
"Include page separator string in output text after each "
|
||||||
|
"image/page.");
|
||||||
|
STRING_VAR_H(page_separator, "\f",
|
||||||
|
"Page separator (default is form feed control character)");
|
||||||
|
|
||||||
// The following parameters were deprecated and removed from their original
|
// The following parameters were deprecated and removed from their original
|
||||||
// locations. The parameters are temporarily kept here to give Tesseract
|
// locations. The parameters are temporarily kept here to give Tesseract
|
||||||
@ -1000,6 +1092,8 @@ class Tesseract : public Wordrec {
|
|||||||
// reasonably sure that Tesseract users have updated their data files.
|
// reasonably sure that Tesseract users have updated their data files.
|
||||||
//
|
//
|
||||||
// BEGIN DEPRECATED PARAMETERS
|
// BEGIN DEPRECATED PARAMETERS
|
||||||
|
BOOL_VAR_H(textord_tabfind_vertical_horizontal_mix, true,
|
||||||
|
"find horizontal lines such as headers in vertical page mode");
|
||||||
INT_VAR_H(tessedit_ok_mode, 5, "Acceptance decision algorithm");
|
INT_VAR_H(tessedit_ok_mode, 5, "Acceptance decision algorithm");
|
||||||
BOOL_VAR_H(load_fixed_length_dawgs, true, "Load fixed length"
|
BOOL_VAR_H(load_fixed_length_dawgs, true, "Load fixed length"
|
||||||
" dawgs (e.g. for non-space delimited languages)");
|
" dawgs (e.g. for non-space delimited languages)");
|
||||||
@ -1062,7 +1156,9 @@ class Tesseract : public Wordrec {
|
|||||||
PAGE_RES_IT* pr_it,
|
PAGE_RES_IT* pr_it,
|
||||||
FILE *output_file);
|
FILE *output_file);
|
||||||
|
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
inline CubeRecoContext *GetCubeRecoContext() { return cube_cntxt_; }
|
inline CubeRecoContext *GetCubeRecoContext() { return cube_cntxt_; }
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The filename of a backup config file. If not null, then we currently
|
// The filename of a backup config file. If not null, then we currently
|
||||||
@ -1102,9 +1198,11 @@ class Tesseract : public Wordrec {
|
|||||||
Tesseract* most_recently_used_;
|
Tesseract* most_recently_used_;
|
||||||
// The size of the font table, ie max possible font id + 1.
|
// The size of the font table, ie max possible font id + 1.
|
||||||
int font_table_size_;
|
int font_table_size_;
|
||||||
|
#ifndef ANDROID_BUILD
|
||||||
// Cube objects.
|
// Cube objects.
|
||||||
CubeRecoContext* cube_cntxt_;
|
CubeRecoContext* cube_cntxt_;
|
||||||
TesseractCubeCombiner *tess_cube_combiner_;
|
TesseractCubeCombiner *tess_cube_combiner_;
|
||||||
|
#endif
|
||||||
// Equation detector. Note: this pointer is NOT owned by the class.
|
// Equation detector. Note: this pointer is NOT owned by the class.
|
||||||
EquationDetect* equ_detect_;
|
EquationDetect* equ_detect_;
|
||||||
};
|
};
|
||||||
|
@ -254,7 +254,7 @@ void Tesseract::join_words(WERD_RES *word,
|
|||||||
// Move the word2 seams onto the end of the word1 seam_array.
|
// Move the word2 seams onto the end of the word1 seam_array.
|
||||||
// Since the seam list is one element short, an empty seam marking the
|
// 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.
|
// end of the last blob in the first word is needed first.
|
||||||
word->seam_array.push_back(new SEAM(0.0f, split_pt, NULL, NULL, NULL));
|
word->seam_array.push_back(new SEAM(0.0f, split_pt));
|
||||||
word->seam_array += word2->seam_array;
|
word->seam_array += word2->seam_array;
|
||||||
word2->seam_array.truncate(0);
|
word2->seam_array.truncate(0);
|
||||||
// Fix widths and gaps.
|
// Fix widths and gaps.
|
||||||
|
@ -137,6 +137,9 @@ class BLOBNBOX:public ELIST_LINK
|
|||||||
cblob_ptr = srcblob;
|
cblob_ptr = srcblob;
|
||||||
area = static_cast<int>(srcblob->area());
|
area = static_cast<int>(srcblob->area());
|
||||||
}
|
}
|
||||||
|
~BLOBNBOX() {
|
||||||
|
if (owns_cblob_) delete cblob_ptr;
|
||||||
|
}
|
||||||
static BLOBNBOX* RealBlob(C_OUTLINE* outline) {
|
static BLOBNBOX* RealBlob(C_OUTLINE* outline) {
|
||||||
C_BLOB* blob = new C_BLOB(outline);
|
C_BLOB* blob = new C_BLOB(outline);
|
||||||
return new BLOBNBOX(blob);
|
return new BLOBNBOX(blob);
|
||||||
@ -387,6 +390,7 @@ class BLOBNBOX:public ELIST_LINK
|
|||||||
void set_base_char_blob(BLOBNBOX* blob) {
|
void set_base_char_blob(BLOBNBOX* blob) {
|
||||||
base_char_blob_ = blob;
|
base_char_blob_ = blob;
|
||||||
}
|
}
|
||||||
|
void set_owns_cblob(bool value) { owns_cblob_ = value; }
|
||||||
|
|
||||||
bool UniquelyVertical() const {
|
bool UniquelyVertical() const {
|
||||||
return vert_possible_ && !horz_possible_;
|
return vert_possible_ && !horz_possible_;
|
||||||
@ -450,6 +454,7 @@ class BLOBNBOX:public ELIST_LINK
|
|||||||
// construction time.
|
// construction time.
|
||||||
void ConstructionInit() {
|
void ConstructionInit() {
|
||||||
cblob_ptr = NULL;
|
cblob_ptr = NULL;
|
||||||
|
owns_cblob_ = false;
|
||||||
area = 0;
|
area = 0;
|
||||||
area_stroke_width_ = 0.0f;
|
area_stroke_width_ = 0.0f;
|
||||||
horz_stroke_width_ = 0.0f;
|
horz_stroke_width_ = 0.0f;
|
||||||
@ -525,6 +530,10 @@ class BLOBNBOX:public ELIST_LINK
|
|||||||
bool vert_possible_; // Could be part of vertical flow.
|
bool vert_possible_; // Could be part of vertical flow.
|
||||||
bool leader_on_left_; // There is a leader to the left.
|
bool leader_on_left_; // There is a leader to the left.
|
||||||
bool leader_on_right_; // There is a leader to the right.
|
bool leader_on_right_; // There is a leader to the right.
|
||||||
|
// Iff true, then the destructor should delete the cblob_ptr.
|
||||||
|
// TODO(rays) migrate all uses to correctly setting this flag instead of
|
||||||
|
// deleting the C_BLOB before deleting the BLOBNBOX.
|
||||||
|
bool owns_cblob_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TO_ROW: public ELIST2_LINK
|
class TO_ROW: public ELIST2_LINK
|
||||||
|
@ -64,6 +64,42 @@ const TPOINT kDivisibleVerticalItalic(1, 5);
|
|||||||
|
|
||||||
CLISTIZE(EDGEPT);
|
CLISTIZE(EDGEPT);
|
||||||
|
|
||||||
|
// Returns true when the two line segments cross each other.
|
||||||
|
// (Moved from outlines.cpp).
|
||||||
|
// Finds where the projected lines would cross and then checks to see if the
|
||||||
|
// point of intersection lies on both of the line segments. If it does
|
||||||
|
// then these two segments cross.
|
||||||
|
/* static */
|
||||||
|
bool TPOINT::IsCrossed(const TPOINT& a0, const TPOINT& a1, const TPOINT& b0,
|
||||||
|
const TPOINT& b1) {
|
||||||
|
int b0a1xb0b1, b0b1xb0a0;
|
||||||
|
int a1b1xa1a0, a1a0xa1b0;
|
||||||
|
|
||||||
|
TPOINT b0a1, b0a0, a1b1, b0b1, a1a0;
|
||||||
|
|
||||||
|
b0a1.x = a1.x - b0.x;
|
||||||
|
b0a0.x = a0.x - b0.x;
|
||||||
|
a1b1.x = b1.x - a1.x;
|
||||||
|
b0b1.x = b1.x - b0.x;
|
||||||
|
a1a0.x = a0.x - a1.x;
|
||||||
|
b0a1.y = a1.y - b0.y;
|
||||||
|
b0a0.y = a0.y - b0.y;
|
||||||
|
a1b1.y = b1.y - a1.y;
|
||||||
|
b0b1.y = b1.y - b0.y;
|
||||||
|
a1a0.y = a0.y - a1.y;
|
||||||
|
|
||||||
|
b0a1xb0b1 = CROSS(b0a1, b0b1);
|
||||||
|
b0b1xb0a0 = CROSS(b0b1, b0a0);
|
||||||
|
a1b1xa1a0 = CROSS(a1b1, a1a0);
|
||||||
|
// For clarity, we want CROSS(a1a0,a1b0) here but we have b0a1 instead of a1b0
|
||||||
|
// so use -CROSS(a1b0,b0a1) instead, which is the same.
|
||||||
|
a1a0xa1b0 = -CROSS(a1a0, b0a1);
|
||||||
|
|
||||||
|
return ((b0a1xb0b1 > 0 && b0b1xb0a0 > 0) ||
|
||||||
|
(b0a1xb0b1 < 0 && b0b1xb0a0 < 0)) &&
|
||||||
|
((a1b1xa1a0 > 0 && a1a0xa1b0 > 0) || (a1b1xa1a0 < 0 && a1a0xa1b0 < 0));
|
||||||
|
}
|
||||||
|
|
||||||
// Consume the circular list of EDGEPTs to make a TESSLINE.
|
// Consume the circular list of EDGEPTs to make a TESSLINE.
|
||||||
TESSLINE* TESSLINE::BuildFromOutlineList(EDGEPT* outline) {
|
TESSLINE* TESSLINE::BuildFromOutlineList(EDGEPT* outline) {
|
||||||
TESSLINE* result = new TESSLINE;
|
TESSLINE* result = new TESSLINE;
|
||||||
@ -454,6 +490,36 @@ TBOX TBLOB::bounding_box() const {
|
|||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finds and deletes any duplicate outlines in this blob, without deleting
|
||||||
|
// their EDGEPTs.
|
||||||
|
void TBLOB::EliminateDuplicateOutlines() {
|
||||||
|
for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) {
|
||||||
|
TESSLINE* last_outline = outline;
|
||||||
|
for (TESSLINE* other_outline = outline->next; other_outline != NULL;
|
||||||
|
last_outline = other_outline, other_outline = other_outline->next) {
|
||||||
|
if (outline->SameBox(*other_outline)) {
|
||||||
|
last_outline->next = other_outline->next;
|
||||||
|
// This doesn't leak - the outlines share the EDGEPTs.
|
||||||
|
other_outline->loop = NULL;
|
||||||
|
delete other_outline;
|
||||||
|
other_outline = last_outline;
|
||||||
|
// If it is part of a cut, then it can't be a hole any more.
|
||||||
|
outline->is_hole = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swaps the outlines of *this and next if needed to keep the centers in
|
||||||
|
// increasing x.
|
||||||
|
void TBLOB::CorrectBlobOrder(TBLOB* next) {
|
||||||
|
TBOX box = bounding_box();
|
||||||
|
TBOX next_box = next->bounding_box();
|
||||||
|
if (box.x_middle() > next_box.x_middle()) {
|
||||||
|
Swap(&outlines, &next->outlines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef GRAPHICS_DISABLED
|
#ifndef GRAPHICS_DISABLED
|
||||||
void TBLOB::plot(ScrollView* window, ScrollView::Color color,
|
void TBLOB::plot(ScrollView* window, ScrollView::Color color,
|
||||||
ScrollView::Color child_color) {
|
ScrollView::Color child_color) {
|
||||||
@ -739,8 +805,8 @@ TWERD* TWERD::PolygonalCopy(bool allow_detailed_fx, WERD* src) {
|
|||||||
// Baseline normalizes the blobs in-place, recording the normalization in the
|
// Baseline normalizes the blobs in-place, recording the normalization in the
|
||||||
// DENORMs in the blobs.
|
// DENORMs in the blobs.
|
||||||
void TWERD::BLNormalize(const BLOCK* block, const ROW* row, Pix* pix,
|
void TWERD::BLNormalize(const BLOCK* block, const ROW* row, Pix* pix,
|
||||||
bool inverse, float x_height, bool numeric_mode,
|
bool inverse, float x_height, float baseline_shift,
|
||||||
tesseract::OcrEngineMode hint,
|
bool numeric_mode, tesseract::OcrEngineMode hint,
|
||||||
const TBOX* norm_box,
|
const TBOX* norm_box,
|
||||||
DENORM* word_denorm) {
|
DENORM* word_denorm) {
|
||||||
TBOX word_box = bounding_box();
|
TBOX word_box = bounding_box();
|
||||||
@ -756,7 +822,7 @@ void TWERD::BLNormalize(const BLOCK* block, const ROW* row, Pix* pix,
|
|||||||
if (hint == tesseract::OEM_CUBE_ONLY)
|
if (hint == tesseract::OEM_CUBE_ONLY)
|
||||||
scale = 1.0f;
|
scale = 1.0f;
|
||||||
} else {
|
} else {
|
||||||
input_y_offset = row->base_line(word_middle);
|
input_y_offset = row->base_line(word_middle) + baseline_shift;
|
||||||
}
|
}
|
||||||
for (int b = 0; b < blobs.size(); ++b) {
|
for (int b = 0; b < blobs.size(); ++b) {
|
||||||
TBLOB* blob = blobs[b];
|
TBLOB* blob = blobs[b];
|
||||||
@ -769,7 +835,7 @@ void TWERD::BLNormalize(const BLOCK* block, const ROW* row, Pix* pix,
|
|||||||
blob_scale = ClipToRange(kBlnXHeight * 4.0f / (3 * blob_box.height()),
|
blob_scale = ClipToRange(kBlnXHeight * 4.0f / (3 * blob_box.height()),
|
||||||
scale, scale * 1.5f);
|
scale, scale * 1.5f);
|
||||||
} else if (row != NULL && hint != tesseract::OEM_CUBE_ONLY) {
|
} else if (row != NULL && hint != tesseract::OEM_CUBE_ONLY) {
|
||||||
baseline = row->base_line(mid_x);
|
baseline = row->base_line(mid_x) + baseline_shift;
|
||||||
}
|
}
|
||||||
// The image will be 8-bit grey if the input was grey or color. Note that in
|
// The image will be 8-bit grey if the input was grey or color. Note that in
|
||||||
// a grey image 0 is black and 255 is white. If the input was binary, then
|
// a grey image 0 is black and 255 is white. If the input was binary, then
|
||||||
@ -858,18 +924,6 @@ void TWERD::plot(ScrollView* window) {
|
|||||||
}
|
}
|
||||||
#endif // GRAPHICS_DISABLED
|
#endif // GRAPHICS_DISABLED
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* blob_origin
|
|
||||||
*
|
|
||||||
* Compute the origin of a compound blob, define to be the centre
|
|
||||||
* of the bounding box.
|
|
||||||
**********************************************************************/
|
|
||||||
void blob_origin(TBLOB *blob, /*blob to compute on */
|
|
||||||
TPOINT *origin) { /*return value */
|
|
||||||
TBOX bbox = blob->bounding_box();
|
|
||||||
*origin = (bbox.topleft() + bbox.botright()) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* divisible_blob
|
* divisible_blob
|
||||||
*
|
*
|
||||||
|
110
ccstruct/blobs.h
110
ccstruct/blobs.h
@ -60,6 +60,13 @@ struct TPOINT {
|
|||||||
x /= divisor;
|
x /= divisor;
|
||||||
y /= divisor;
|
y /= divisor;
|
||||||
}
|
}
|
||||||
|
bool operator==(const TPOINT& other) const {
|
||||||
|
return x == other.x && y == other.y;
|
||||||
|
}
|
||||||
|
// Returns true when the two line segments cross each other.
|
||||||
|
// (Moved from outlines.cpp).
|
||||||
|
static bool IsCrossed(const TPOINT& a0, const TPOINT& a1, const TPOINT& b0,
|
||||||
|
const TPOINT& b1);
|
||||||
|
|
||||||
inT16 x; // absolute x coord.
|
inT16 x; // absolute x coord.
|
||||||
inT16 y; // absolute y coord.
|
inT16 y; // absolute y coord.
|
||||||
@ -87,6 +94,55 @@ struct EDGEPT {
|
|||||||
start_step = src.start_step;
|
start_step = src.start_step;
|
||||||
step_count = src.step_count;
|
step_count = src.step_count;
|
||||||
}
|
}
|
||||||
|
// Returns the squared distance between the points, with the x-component
|
||||||
|
// weighted by x_factor.
|
||||||
|
int WeightedDistance(const EDGEPT& other, int x_factor) const {
|
||||||
|
int x_dist = pos.x - other.pos.x;
|
||||||
|
int y_dist = pos.y - other.pos.y;
|
||||||
|
return x_dist * x_dist * x_factor + y_dist * y_dist;
|
||||||
|
}
|
||||||
|
// Returns true if the positions are equal.
|
||||||
|
bool EqualPos(const EDGEPT& other) const { return pos == other.pos; }
|
||||||
|
// Returns the bounding box of the outline segment from *this to *end.
|
||||||
|
// Ignores hidden edge flags.
|
||||||
|
TBOX SegmentBox(const EDGEPT* end) const {
|
||||||
|
TBOX box(pos.x, pos.y, pos.x, pos.y);
|
||||||
|
const EDGEPT* pt = this;
|
||||||
|
do {
|
||||||
|
pt = pt->next;
|
||||||
|
if (pt->pos.x < box.left()) box.set_left(pt->pos.x);
|
||||||
|
if (pt->pos.x > box.right()) box.set_right(pt->pos.x);
|
||||||
|
if (pt->pos.y < box.bottom()) box.set_bottom(pt->pos.y);
|
||||||
|
if (pt->pos.y > box.top()) box.set_top(pt->pos.y);
|
||||||
|
} while (pt != end && pt != this);
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
// Returns the area of the outline segment from *this to *end.
|
||||||
|
// Ignores hidden edge flags.
|
||||||
|
int SegmentArea(const EDGEPT* end) const {
|
||||||
|
int area = 0;
|
||||||
|
const EDGEPT* pt = this->next;
|
||||||
|
do {
|
||||||
|
TPOINT origin_vec(pt->pos.x - pos.x, pt->pos.y - pos.y);
|
||||||
|
area += CROSS(origin_vec, pt->vec);
|
||||||
|
pt = pt->next;
|
||||||
|
} while (pt != end && pt != this);
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
// Returns true if the number of points in the outline segment from *this to
|
||||||
|
// *end is less that min_points and false if we get back to *this first.
|
||||||
|
// Ignores hidden edge flags.
|
||||||
|
bool ShortNonCircularSegment(int min_points, const EDGEPT* end) const {
|
||||||
|
int count = 0;
|
||||||
|
const EDGEPT* pt = this;
|
||||||
|
do {
|
||||||
|
if (pt == end) return true;
|
||||||
|
pt = pt->next;
|
||||||
|
++count;
|
||||||
|
} while (pt != this && count <= min_points);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Accessors to hide or reveal a cut edge from feature extractors.
|
// Accessors to hide or reveal a cut edge from feature extractors.
|
||||||
void Hide() {
|
void Hide() {
|
||||||
flags[0] = true;
|
flags[0] = true;
|
||||||
@ -100,9 +156,6 @@ struct EDGEPT {
|
|||||||
void MarkChop() {
|
void MarkChop() {
|
||||||
flags[2] = true;
|
flags[2] = true;
|
||||||
}
|
}
|
||||||
void UnmarkChop() {
|
|
||||||
flags[2] = false;
|
|
||||||
}
|
|
||||||
bool IsChopPt() const {
|
bool IsChopPt() const {
|
||||||
return flags[2] != 0;
|
return flags[2] != 0;
|
||||||
}
|
}
|
||||||
@ -162,8 +215,23 @@ struct TESSLINE {
|
|||||||
void MinMaxCrossProduct(const TPOINT vec, int* min_xp, int* max_xp) const;
|
void MinMaxCrossProduct(const TPOINT vec, int* min_xp, int* max_xp) const;
|
||||||
|
|
||||||
TBOX bounding_box() const;
|
TBOX bounding_box() const;
|
||||||
|
// Returns true if *this and other have equal bounding boxes.
|
||||||
|
bool SameBox(const TESSLINE& other) const {
|
||||||
|
return topleft == other.topleft && botright == other.botright;
|
||||||
|
}
|
||||||
|
// Returns true if the given line segment crosses any outline of this blob.
|
||||||
|
bool SegmentCrosses(const TPOINT& pt1, const TPOINT& pt2) const {
|
||||||
|
if (Contains(pt1) && Contains(pt2)) {
|
||||||
|
EDGEPT* pt = loop;
|
||||||
|
do {
|
||||||
|
if (TPOINT::IsCrossed(pt1, pt2, pt->pos, pt->next->pos)) return true;
|
||||||
|
pt = pt->next;
|
||||||
|
} while (pt != loop);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Returns true if the point is contained within the outline box.
|
// Returns true if the point is contained within the outline box.
|
||||||
bool Contains(const TPOINT& pt) {
|
bool Contains(const TPOINT& pt) const {
|
||||||
return topleft.x <= pt.x && pt.x <= botright.x &&
|
return topleft.x <= pt.x && pt.x <= botright.x &&
|
||||||
botright.y <= pt.y && pt.y <= topleft.y;
|
botright.y <= pt.y && pt.y <= topleft.y;
|
||||||
}
|
}
|
||||||
@ -244,6 +312,31 @@ struct TBLOB {
|
|||||||
|
|
||||||
TBOX bounding_box() const;
|
TBOX bounding_box() const;
|
||||||
|
|
||||||
|
// Returns true if the given line segment crosses any outline of this blob.
|
||||||
|
bool SegmentCrossesOutline(const TPOINT& pt1, const TPOINT& pt2) const {
|
||||||
|
for (const TESSLINE* outline = outlines; outline != NULL;
|
||||||
|
outline = outline->next) {
|
||||||
|
if (outline->SegmentCrosses(pt1, pt2)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Returns true if the point is contained within any of the outline boxes.
|
||||||
|
bool Contains(const TPOINT& pt) const {
|
||||||
|
for (const TESSLINE* outline = outlines; outline != NULL;
|
||||||
|
outline = outline->next) {
|
||||||
|
if (outline->Contains(pt)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds and deletes any duplicate outlines in this blob, without deleting
|
||||||
|
// their EDGEPTs.
|
||||||
|
void EliminateDuplicateOutlines();
|
||||||
|
|
||||||
|
// Swaps the outlines of *this and next if needed to keep the centers in
|
||||||
|
// increasing x.
|
||||||
|
void CorrectBlobOrder(TBLOB* next);
|
||||||
|
|
||||||
const DENORM& denorm() const {
|
const DENORM& denorm() const {
|
||||||
return denorm_;
|
return denorm_;
|
||||||
}
|
}
|
||||||
@ -317,7 +410,7 @@ struct TWERD {
|
|||||||
// Baseline normalizes the blobs in-place, recording the normalization in the
|
// Baseline normalizes the blobs in-place, recording the normalization in the
|
||||||
// DENORMs in the blobs.
|
// DENORMs in the blobs.
|
||||||
void BLNormalize(const BLOCK* block, const ROW* row, Pix* pix, bool inverse,
|
void BLNormalize(const BLOCK* block, const ROW* row, Pix* pix, bool inverse,
|
||||||
float x_height, bool numeric_mode,
|
float x_height, float baseline_shift, bool numeric_mode,
|
||||||
tesseract::OcrEngineMode hint,
|
tesseract::OcrEngineMode hint,
|
||||||
const TBOX* norm_box,
|
const TBOX* norm_box,
|
||||||
DENORM* word_denorm);
|
DENORM* word_denorm);
|
||||||
@ -358,12 +451,7 @@ if (w) memfree (w)
|
|||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
F u n c t i o n s
|
F u n c t i o n s
|
||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
// TODO(rays) This will become a member of TBLOB when TBLOB's definition
|
// TODO(rays) Make divisible_blob and divide_blobs members of TBLOB.
|
||||||
// moves to blobs.h
|
|
||||||
|
|
||||||
// Returns the center of blob's bounding box in origin.
|
|
||||||
void blob_origin(TBLOB *blob, TPOINT *origin);
|
|
||||||
|
|
||||||
bool divisible_blob(TBLOB *blob, bool italic_blob, TPOINT* location);
|
bool divisible_blob(TBLOB *blob, bool italic_blob, TPOINT* location);
|
||||||
|
|
||||||
void divide_blobs(TBLOB *blob, TBLOB *other_blob, bool italic_blob,
|
void divide_blobs(TBLOB *blob, TBLOB *other_blob, bool italic_blob,
|
||||||
|
@ -78,7 +78,7 @@ bool ReadMemBoxes(int target_page, bool skip_blanks, const char* box_data,
|
|||||||
if (!ParseBoxFileStr(lines[i].string(), &page, &utf8_str, &box)) {
|
if (!ParseBoxFileStr(lines[i].string(), &page, &utf8_str, &box)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (skip_blanks && utf8_str == " ") continue;
|
if (skip_blanks && (utf8_str == " " || utf8_str == "\t")) continue;
|
||||||
if (target_page >= 0 && page != target_page) continue;
|
if (target_page >= 0 && page != target_page) continue;
|
||||||
if (boxes != NULL) boxes->push_back(box);
|
if (boxes != NULL) boxes->push_back(box);
|
||||||
if (texts != NULL) texts->push_back(utf8_str);
|
if (texts != NULL) texts->push_back(utf8_str);
|
||||||
|
@ -59,10 +59,10 @@ bool FontInfoTable::DeSerialize(bool swap, FILE* fp) {
|
|||||||
// Returns true if the given set of fonts includes one with the same
|
// Returns true if the given set of fonts includes one with the same
|
||||||
// properties as font_id.
|
// properties as font_id.
|
||||||
bool FontInfoTable::SetContainsFontProperties(
|
bool FontInfoTable::SetContainsFontProperties(
|
||||||
int font_id, const GenericVector<int>& font_set) const {
|
int font_id, const GenericVector<ScoredFont>& font_set) const {
|
||||||
uinT32 properties = get(font_id).properties;
|
uinT32 properties = get(font_id).properties;
|
||||||
for (int f = 0; f < font_set.size(); ++f) {
|
for (int f = 0; f < font_set.size(); ++f) {
|
||||||
if (get(font_set[f]).properties == properties)
|
if (get(font_set[f].fontinfo_id).properties == properties)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -70,12 +70,12 @@ bool FontInfoTable::SetContainsFontProperties(
|
|||||||
|
|
||||||
// Returns true if the given set of fonts includes multiple properties.
|
// Returns true if the given set of fonts includes multiple properties.
|
||||||
bool FontInfoTable::SetContainsMultipleFontProperties(
|
bool FontInfoTable::SetContainsMultipleFontProperties(
|
||||||
const GenericVector<int>& font_set) const {
|
const GenericVector<ScoredFont>& font_set) const {
|
||||||
if (font_set.empty()) return false;
|
if (font_set.empty()) return false;
|
||||||
int first_font = font_set[0];
|
int first_font = font_set[0].fontinfo_id;
|
||||||
uinT32 properties = get(first_font).properties;
|
uinT32 properties = get(first_font).properties;
|
||||||
for (int f = 1; f < font_set.size(); ++f) {
|
for (int f = 1; f < font_set.size(); ++f) {
|
||||||
if (get(font_set[f]).properties != properties)
|
if (get(font_set[f].fontinfo_id).properties != properties)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -31,6 +31,22 @@ namespace tesseract {
|
|||||||
|
|
||||||
class BitVector;
|
class BitVector;
|
||||||
|
|
||||||
|
// Simple struct to hold a font and a score. The scores come from the low-level
|
||||||
|
// integer matcher, so they are in the uinT16 range. Fonts are an index to
|
||||||
|
// fontinfo_table.
|
||||||
|
// These get copied around a lot, so best to keep them small.
|
||||||
|
struct ScoredFont {
|
||||||
|
ScoredFont() : fontinfo_id(-1), score(0) {}
|
||||||
|
ScoredFont(int font_id, uinT16 classifier_score)
|
||||||
|
: fontinfo_id(font_id), score(classifier_score) {}
|
||||||
|
|
||||||
|
// Index into fontinfo table, but inside the classifier, may be a shapetable
|
||||||
|
// index.
|
||||||
|
inT32 fontinfo_id;
|
||||||
|
// Raw score from the low-level classifier.
|
||||||
|
uinT16 score;
|
||||||
|
};
|
||||||
|
|
||||||
// Struct for information about spacing between characters in a particular font.
|
// Struct for information about spacing between characters in a particular font.
|
||||||
struct FontSpacingInfo {
|
struct FontSpacingInfo {
|
||||||
inT16 x_gap_before;
|
inT16 x_gap_before;
|
||||||
@ -140,11 +156,11 @@ class FontInfoTable : public GenericVector<FontInfo> {
|
|||||||
|
|
||||||
// Returns true if the given set of fonts includes one with the same
|
// Returns true if the given set of fonts includes one with the same
|
||||||
// properties as font_id.
|
// properties as font_id.
|
||||||
bool SetContainsFontProperties(int font_id,
|
bool SetContainsFontProperties(
|
||||||
const GenericVector<int>& font_set) const;
|
int font_id, const GenericVector<ScoredFont>& font_set) const;
|
||||||
// Returns true if the given set of fonts includes multiple properties.
|
// Returns true if the given set of fonts includes multiple properties.
|
||||||
bool SetContainsMultipleFontProperties(
|
bool SetContainsMultipleFontProperties(
|
||||||
const GenericVector<int>& font_set) const;
|
const GenericVector<ScoredFont>& font_set) const;
|
||||||
|
|
||||||
// Moves any non-empty FontSpacingInfo entries from other to this.
|
// Moves any non-empty FontSpacingInfo entries from other to this.
|
||||||
void MoveSpacingInfoFrom(FontInfoTable* other);
|
void MoveSpacingInfoFrom(FontInfoTable* other);
|
||||||
|
@ -51,6 +51,7 @@ void WordFeature::ComputeSize(const GenericVector<WordFeature>& features,
|
|||||||
// Draws the features in the given window.
|
// Draws the features in the given window.
|
||||||
void WordFeature::Draw(const GenericVector<WordFeature>& features,
|
void WordFeature::Draw(const GenericVector<WordFeature>& features,
|
||||||
ScrollView* window) {
|
ScrollView* window) {
|
||||||
|
#ifndef GRAPHICS_DISABLED
|
||||||
for (int f = 0; f < features.size(); ++f) {
|
for (int f = 0; f < features.size(); ++f) {
|
||||||
FCOORD pos(features[f].x_, features[f].y_);
|
FCOORD pos(features[f].x_, features[f].y_);
|
||||||
FCOORD dir;
|
FCOORD dir;
|
||||||
@ -61,6 +62,7 @@ void WordFeature::Draw(const GenericVector<WordFeature>& features,
|
|||||||
window->DrawTo(IntCastRounded(pos.x() + dir.x()),
|
window->DrawTo(IntCastRounded(pos.x() + dir.x()),
|
||||||
IntCastRounded(pos.y() + dir.y()));
|
IntCastRounded(pos.y() + dir.y()));
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes to the given file. Returns false in case of error.
|
// Writes to the given file. Returns false in case of error.
|
||||||
@ -244,6 +246,7 @@ int ImageData::MemoryUsed() const {
|
|||||||
|
|
||||||
// Draws the data in a new window.
|
// Draws the data in a new window.
|
||||||
void ImageData::Display() const {
|
void ImageData::Display() const {
|
||||||
|
#ifndef GRAPHICS_DISABLED
|
||||||
const int kTextSize = 64;
|
const int kTextSize = 64;
|
||||||
// Draw the image.
|
// Draw the image.
|
||||||
Pix* pix = GetPix();
|
Pix* pix = GetPix();
|
||||||
@ -274,6 +277,7 @@ void ImageData::Display() const {
|
|||||||
win->Pen(ScrollView::GREEN);
|
win->Pen(ScrollView::GREEN);
|
||||||
win->Update();
|
win->Update();
|
||||||
window_wait(win);
|
window_wait(win);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds the supplied boxes and transcriptions that correspond to the correct
|
// Adds the supplied boxes and transcriptions that correspond to the correct
|
||||||
|
@ -487,7 +487,7 @@ void DENORM::XHeightRange(int unichar_id, const UNICHARSET& unicharset,
|
|||||||
top > kBlnCellHeight - kBlnBaselineOffset / 2)
|
top > kBlnCellHeight - kBlnBaselineOffset / 2)
|
||||||
max_top += kBlnBaselineOffset;
|
max_top += kBlnBaselineOffset;
|
||||||
top -= bln_yshift;
|
top -= bln_yshift;
|
||||||
int height = top - kBlnBaselineOffset - bottom_shift;
|
int height = top - kBlnBaselineOffset;
|
||||||
double min_height = min_top - kBlnBaselineOffset - tolerance;
|
double min_height = min_top - kBlnBaselineOffset - tolerance;
|
||||||
double max_height = max_top - kBlnBaselineOffset + tolerance;
|
double max_height = max_top - kBlnBaselineOffset + tolerance;
|
||||||
|
|
||||||
|
@ -86,6 +86,18 @@ void BLOCK::rotate(const FCOORD& rotation) {
|
|||||||
box = *poly_block()->bounding_box();
|
box = *poly_block()->bounding_box();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the bounding box including the desired combination of upper and
|
||||||
|
// lower noise/diacritic elements.
|
||||||
|
TBOX BLOCK::restricted_bounding_box(bool upper_dots, bool lower_dots) const {
|
||||||
|
TBOX box;
|
||||||
|
// This is a read-only iteration of the rows in the block.
|
||||||
|
ROW_IT it(const_cast<ROW_LIST*>(&rows));
|
||||||
|
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
|
||||||
|
box += it.data()->restricted_bounding_box(upper_dots, lower_dots);
|
||||||
|
}
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BLOCK::reflect_polygon_in_y_axis
|
* BLOCK::reflect_polygon_in_y_axis
|
||||||
*
|
*
|
||||||
|
@ -161,10 +161,14 @@ class BLOCK:public ELIST_LINK, public PDBLK
|
|||||||
median_size_.set_y(y);
|
median_size_.set_y(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
Pix* render_mask() {
|
Pix* render_mask(TBOX* mask_box) {
|
||||||
return PDBLK::render_mask(re_rotation_);
|
return PDBLK::render_mask(re_rotation_, mask_box);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the bounding box including the desired combination of upper and
|
||||||
|
// lower noise/diacritic elements.
|
||||||
|
TBOX restricted_bounding_box(bool upper_dots, bool lower_dots) const;
|
||||||
|
|
||||||
// Reflects the polygon in the y-axis and recomputes the bounding_box.
|
// Reflects the polygon in the y-axis and recomputes the bounding_box.
|
||||||
// Does nothing to any contained rows/words/blobs etc.
|
// Does nothing to any contained rows/words/blobs etc.
|
||||||
void reflect_polygon_in_y_axis();
|
void reflect_polygon_in_y_axis();
|
||||||
|
@ -80,6 +80,17 @@ ROW::ROW( //constructor
|
|||||||
rmargin_ = 0;
|
rmargin_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the bounding box including the desired combination of upper and
|
||||||
|
// lower noise/diacritic elements.
|
||||||
|
TBOX ROW::restricted_bounding_box(bool upper_dots, bool lower_dots) const {
|
||||||
|
TBOX box;
|
||||||
|
// This is a read-only iteration of the words in the row.
|
||||||
|
WERD_IT it(const_cast<WERD_LIST *>(&words));
|
||||||
|
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
|
||||||
|
box += it.data()->restricted_bounding_box(upper_dots, lower_dots);
|
||||||
|
}
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* ROW::recalc_bounding_box
|
* ROW::recalc_bounding_box
|
||||||
|
@ -85,6 +85,9 @@ class ROW:public ELIST_LINK
|
|||||||
TBOX bounding_box() const { //return bounding box
|
TBOX bounding_box() const { //return bounding box
|
||||||
return bound_box;
|
return bound_box;
|
||||||
}
|
}
|
||||||
|
// Returns the bounding box including the desired combination of upper and
|
||||||
|
// lower noise/diacritic elements.
|
||||||
|
TBOX restricted_bounding_box(bool upper_dots, bool lower_dots) const;
|
||||||
|
|
||||||
void set_lmargin(inT16 lmargin) {
|
void set_lmargin(inT16 lmargin) {
|
||||||
lmargin_ = lmargin;
|
lmargin_ = lmargin;
|
||||||
|
@ -148,6 +148,7 @@ ROW_RES::ROW_RES(bool merge_similar_words, ROW *the_row) {
|
|||||||
add_next_word = false;
|
add_next_word = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
next_word->set_flag(W_FUZZY_NON, add_next_word);
|
||||||
} else {
|
} else {
|
||||||
add_next_word = next_word->flag(W_FUZZY_NON);
|
add_next_word = next_word->flag(W_FUZZY_NON);
|
||||||
}
|
}
|
||||||
@ -206,12 +207,8 @@ WERD_RES& WERD_RES::operator=(const WERD_RES & source) {
|
|||||||
if (!wc_dest_it.empty()) {
|
if (!wc_dest_it.empty()) {
|
||||||
wc_dest_it.move_to_first();
|
wc_dest_it.move_to_first();
|
||||||
best_choice = wc_dest_it.data();
|
best_choice = wc_dest_it.data();
|
||||||
best_choice_fontinfo_ids = source.best_choice_fontinfo_ids;
|
|
||||||
} else {
|
} else {
|
||||||
best_choice = NULL;
|
best_choice = NULL;
|
||||||
if (!best_choice_fontinfo_ids.empty()) {
|
|
||||||
best_choice_fontinfo_ids.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.raw_choice != NULL) {
|
if (source.raw_choice != NULL) {
|
||||||
@ -252,6 +249,7 @@ void WERD_RES::CopySimpleFields(const WERD_RES& source) {
|
|||||||
fontinfo_id2_count = source.fontinfo_id2_count;
|
fontinfo_id2_count = source.fontinfo_id2_count;
|
||||||
x_height = source.x_height;
|
x_height = source.x_height;
|
||||||
caps_height = source.caps_height;
|
caps_height = source.caps_height;
|
||||||
|
baseline_shift = source.baseline_shift;
|
||||||
guessed_x_ht = source.guessed_x_ht;
|
guessed_x_ht = source.guessed_x_ht;
|
||||||
guessed_caps_ht = source.guessed_caps_ht;
|
guessed_caps_ht = source.guessed_caps_ht;
|
||||||
reject_spaces = source.reject_spaces;
|
reject_spaces = source.reject_spaces;
|
||||||
@ -314,8 +312,8 @@ bool WERD_RES::SetupForRecognition(const UNICHARSET& unicharset_in,
|
|||||||
float word_xheight = use_body_size && row != NULL && row->body_size() > 0.0f
|
float word_xheight = use_body_size && row != NULL && row->body_size() > 0.0f
|
||||||
? row->body_size() : x_height;
|
? row->body_size() : x_height;
|
||||||
chopped_word->BLNormalize(block, row, pix, word->flag(W_INVERSE),
|
chopped_word->BLNormalize(block, row, pix, word->flag(W_INVERSE),
|
||||||
word_xheight, numeric_mode, norm_mode_hint,
|
word_xheight, baseline_shift, numeric_mode,
|
||||||
norm_box, &denorm);
|
norm_mode_hint, norm_box, &denorm);
|
||||||
blob_row = row;
|
blob_row = row;
|
||||||
SetupBasicsFromChoppedWord(unicharset_in);
|
SetupBasicsFromChoppedWord(unicharset_in);
|
||||||
SetupBlamerBundle();
|
SetupBlamerBundle();
|
||||||
@ -366,6 +364,7 @@ void WERD_RES::SetupFake(const UNICHARSET& unicharset_in) {
|
|||||||
LogNewCookedChoice(1, false, word);
|
LogNewCookedChoice(1, false, word);
|
||||||
}
|
}
|
||||||
tess_failed = true;
|
tess_failed = true;
|
||||||
|
done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WERD_RES::SetupWordScript(const UNICHARSET& uch) {
|
void WERD_RES::SetupWordScript(const UNICHARSET& uch) {
|
||||||
@ -404,7 +403,8 @@ void WERD_RES::SetupBlobWidthsAndGaps() {
|
|||||||
// as the blob widths and gaps.
|
// as the blob widths and gaps.
|
||||||
void WERD_RES::InsertSeam(int blob_number, SEAM* seam) {
|
void WERD_RES::InsertSeam(int blob_number, SEAM* seam) {
|
||||||
// Insert the seam into the SEAMS array.
|
// Insert the seam into the SEAMS array.
|
||||||
insert_seam(chopped_word, blob_number, seam, &seam_array);
|
seam->PrepareToInsertSeam(seam_array, chopped_word->blobs, blob_number, true);
|
||||||
|
seam_array.insert(seam, blob_number);
|
||||||
if (ratings != NULL) {
|
if (ratings != NULL) {
|
||||||
// Expand the ratings matrix.
|
// Expand the ratings matrix.
|
||||||
ratings = ratings->ConsumeAndMakeBigger(blob_number);
|
ratings = ratings->ConsumeAndMakeBigger(blob_number);
|
||||||
@ -485,7 +485,10 @@ void WERD_RES::DebugWordChoices(bool debug, const char* word_to_debug) {
|
|||||||
void WERD_RES::DebugTopChoice(const char* msg) const {
|
void WERD_RES::DebugTopChoice(const char* msg) const {
|
||||||
tprintf("Best choice: accepted=%d, adaptable=%d, done=%d : ",
|
tprintf("Best choice: accepted=%d, adaptable=%d, done=%d : ",
|
||||||
tess_accepted, tess_would_adapt, done);
|
tess_accepted, tess_would_adapt, done);
|
||||||
best_choice->print(msg);
|
if (best_choice == NULL)
|
||||||
|
tprintf("<Null choice>\n");
|
||||||
|
else
|
||||||
|
best_choice->print(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes from best_choices all choices which are not within a reasonable
|
// Removes from best_choices all choices which are not within a reasonable
|
||||||
@ -801,12 +804,16 @@ void WERD_RES::RebuildBestState() {
|
|||||||
for (int i = 0; i < best_choice->length(); ++i) {
|
for (int i = 0; i < best_choice->length(); ++i) {
|
||||||
int length = best_choice->state(i);
|
int length = best_choice->state(i);
|
||||||
best_state.push_back(length);
|
best_state.push_back(length);
|
||||||
if (length > 1)
|
if (length > 1) {
|
||||||
join_pieces(seam_array, start, start + length - 1, chopped_word);
|
SEAM::JoinPieces(seam_array, chopped_word->blobs, start,
|
||||||
|
start + length - 1);
|
||||||
|
}
|
||||||
TBLOB* blob = chopped_word->blobs[start];
|
TBLOB* blob = chopped_word->blobs[start];
|
||||||
rebuild_word->blobs.push_back(new TBLOB(*blob));
|
rebuild_word->blobs.push_back(new TBLOB(*blob));
|
||||||
if (length > 1)
|
if (length > 1) {
|
||||||
break_pieces(seam_array, start, start + length - 1, chopped_word);
|
SEAM::BreakPieces(seam_array, chopped_word->blobs, start,
|
||||||
|
start + length - 1);
|
||||||
|
}
|
||||||
start += length;
|
start += length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1062,8 +1069,7 @@ bool WERD_RES::PiecesAllNatural(int start, int count) const {
|
|||||||
for (int index = start; index < start + count - 1; ++index) {
|
for (int index = start; index < start + count - 1; ++index) {
|
||||||
if (index >= 0 && index < seam_array.size()) {
|
if (index >= 0 && index < seam_array.size()) {
|
||||||
SEAM* seam = seam_array[index];
|
SEAM* seam = seam_array[index];
|
||||||
if (seam != NULL && seam->split1 != NULL)
|
if (seam != NULL && seam->HasAnySplits()) return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -1093,6 +1099,7 @@ void WERD_RES::InitNonPointers() {
|
|||||||
fontinfo_id2_count = 0;
|
fontinfo_id2_count = 0;
|
||||||
x_height = 0.0;
|
x_height = 0.0;
|
||||||
caps_height = 0.0;
|
caps_height = 0.0;
|
||||||
|
baseline_shift = 0.0f;
|
||||||
guessed_x_ht = TRUE;
|
guessed_x_ht = TRUE;
|
||||||
guessed_caps_ht = TRUE;
|
guessed_caps_ht = TRUE;
|
||||||
combination = FALSE;
|
combination = FALSE;
|
||||||
@ -1249,23 +1256,16 @@ int PAGE_RES_IT::cmp(const PAGE_RES_IT &other) const {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserts the new_word and a corresponding WERD_RES before the current
|
// Inserts the new_word as a combination owned by a corresponding WERD_RES
|
||||||
// position. The simple fields of the WERD_RES are copied from clone_res and
|
// before the current position. The simple fields of the WERD_RES are copied
|
||||||
// the resulting WERD_RES is returned for further setup with best_choice etc.
|
// from clone_res and the resulting WERD_RES is returned for further setup
|
||||||
|
// with best_choice etc.
|
||||||
WERD_RES* PAGE_RES_IT::InsertSimpleCloneWord(const WERD_RES& clone_res,
|
WERD_RES* PAGE_RES_IT::InsertSimpleCloneWord(const WERD_RES& clone_res,
|
||||||
WERD* new_word) {
|
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.
|
// Make a WERD_RES for the new_word.
|
||||||
WERD_RES* new_res = new WERD_RES(new_word);
|
WERD_RES* new_res = new WERD_RES(new_word);
|
||||||
new_res->CopySimpleFields(clone_res);
|
new_res->CopySimpleFields(clone_res);
|
||||||
|
new_res->combination = true;
|
||||||
// Insert into the appropriate place in the ROW_RES.
|
// Insert into the appropriate place in the ROW_RES.
|
||||||
WERD_RES_IT wr_it(&row()->word_res_list);
|
WERD_RES_IT wr_it(&row()->word_res_list);
|
||||||
for (wr_it.mark_cycle_pt(); !wr_it.cycled_list(); wr_it.forward()) {
|
for (wr_it.mark_cycle_pt(); !wr_it.cycled_list(); wr_it.forward()) {
|
||||||
@ -1315,6 +1315,10 @@ static void ComputeBlobEnds(const WERD_RES& word, C_BLOB_LIST* next_word_blobs,
|
|||||||
// replaced with real blobs from the current word as much as possible.
|
// replaced with real blobs from the current word as much as possible.
|
||||||
void PAGE_RES_IT::ReplaceCurrentWord(
|
void PAGE_RES_IT::ReplaceCurrentWord(
|
||||||
tesseract::PointerVector<WERD_RES>* words) {
|
tesseract::PointerVector<WERD_RES>* words) {
|
||||||
|
if (words->empty()) {
|
||||||
|
DeleteCurrentWord();
|
||||||
|
return;
|
||||||
|
}
|
||||||
WERD_RES* input_word = word();
|
WERD_RES* input_word = word();
|
||||||
// Set the BOL/EOL flags on the words from the input word.
|
// Set the BOL/EOL flags on the words from the input word.
|
||||||
if (input_word->word->flag(W_BOL)) {
|
if (input_word->word->flag(W_BOL)) {
|
||||||
@ -1468,6 +1472,33 @@ void PAGE_RES_IT::DeleteCurrentWord() {
|
|||||||
ResetWordIterator();
|
ResetWordIterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Makes the current word a fuzzy space if not already fuzzy. Updates
|
||||||
|
// corresponding part of combo if required.
|
||||||
|
void PAGE_RES_IT::MakeCurrentWordFuzzy() {
|
||||||
|
WERD* real_word = word_res->word;
|
||||||
|
if (!real_word->flag(W_FUZZY_SP) && !real_word->flag(W_FUZZY_NON)) {
|
||||||
|
real_word->set_flag(W_FUZZY_SP, true);
|
||||||
|
tprintf("Made word fuzzy at:");
|
||||||
|
real_word->bounding_box().print();
|
||||||
|
if (word_res->combination) {
|
||||||
|
// The next word should be the corresponding part of combo, but we have
|
||||||
|
// already stepped past it, so find it by search.
|
||||||
|
WERD_RES_IT wr_it(&row()->word_res_list);
|
||||||
|
for (wr_it.mark_cycle_pt();
|
||||||
|
!wr_it.cycled_list() && wr_it.data() != word_res; wr_it.forward()) {
|
||||||
|
}
|
||||||
|
wr_it.forward();
|
||||||
|
ASSERT_HOST(wr_it.data()->part_of_combo);
|
||||||
|
real_word = wr_it.data()->word;
|
||||||
|
ASSERT_HOST(!real_word->flag(W_FUZZY_SP) &&
|
||||||
|
!real_word->flag(W_FUZZY_NON));
|
||||||
|
real_word->set_flag(W_FUZZY_SP, true);
|
||||||
|
tprintf("Made part of combo word fuzzy at:");
|
||||||
|
real_word->bounding_box().print();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*************************************************************************
|
/*************************************************************************
|
||||||
* PAGE_RES_IT::restart_page
|
* PAGE_RES_IT::restart_page
|
||||||
*
|
*
|
||||||
@ -1502,12 +1533,13 @@ void PAGE_RES_IT::ResetWordIterator() {
|
|||||||
// Reset the member iterator so it can move forward and detect the
|
// Reset the member iterator so it can move forward and detect the
|
||||||
// cycled_list state correctly.
|
// cycled_list state correctly.
|
||||||
word_res_it.move_to_first();
|
word_res_it.move_to_first();
|
||||||
word_res_it.mark_cycle_pt();
|
for (word_res_it.mark_cycle_pt();
|
||||||
while (!word_res_it.cycled_list() && word_res_it.data() != next_word_res) {
|
!word_res_it.cycled_list() && word_res_it.data() != next_word_res;
|
||||||
if (prev_row_res == row_res)
|
word_res_it.forward()) {
|
||||||
prev_word_res = word_res;
|
if (!word_res_it.data()->part_of_combo) {
|
||||||
word_res = word_res_it.data();
|
if (prev_row_res == row_res) prev_word_res = word_res;
|
||||||
word_res_it.forward();
|
word_res = word_res_it.data();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ASSERT_HOST(!word_res_it.cycled_list());
|
ASSERT_HOST(!word_res_it.cycled_list());
|
||||||
word_res_it.forward();
|
word_res_it.forward();
|
||||||
@ -1515,9 +1547,10 @@ void PAGE_RES_IT::ResetWordIterator() {
|
|||||||
// word_res_it is OK, but reset word_res and prev_word_res if needed.
|
// word_res_it is OK, but reset word_res and prev_word_res if needed.
|
||||||
WERD_RES_IT wr_it(&row_res->word_res_list);
|
WERD_RES_IT wr_it(&row_res->word_res_list);
|
||||||
for (wr_it.mark_cycle_pt(); !wr_it.cycled_list(); wr_it.forward()) {
|
for (wr_it.mark_cycle_pt(); !wr_it.cycled_list(); wr_it.forward()) {
|
||||||
if (prev_row_res == row_res)
|
if (!wr_it.data()->part_of_combo) {
|
||||||
prev_word_res = word_res;
|
if (prev_row_res == row_res) prev_word_res = word_res;
|
||||||
word_res = wr_it.data();
|
word_res = wr_it.data();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,6 +294,7 @@ class WERD_RES : public ELIST_LINK {
|
|||||||
CRUNCH_MODE unlv_crunch_mode;
|
CRUNCH_MODE unlv_crunch_mode;
|
||||||
float x_height; // post match estimate
|
float x_height; // post match estimate
|
||||||
float caps_height; // post match estimate
|
float caps_height; // post match estimate
|
||||||
|
float baseline_shift; // post match estimate.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
To deal with fuzzy spaces we need to be able to combine "words" to form
|
To deal with fuzzy spaces we need to be able to combine "words" to form
|
||||||
@ -314,8 +315,6 @@ class WERD_RES : public ELIST_LINK {
|
|||||||
BOOL8 combination; //of two fuzzy gap wds
|
BOOL8 combination; //of two fuzzy gap wds
|
||||||
BOOL8 part_of_combo; //part of a combo
|
BOOL8 part_of_combo; //part of a combo
|
||||||
BOOL8 reject_spaces; //Reject spacing?
|
BOOL8 reject_spaces; //Reject spacing?
|
||||||
// FontInfo ids for each unichar in best_choice.
|
|
||||||
GenericVector<inT8> best_choice_fontinfo_ids;
|
|
||||||
|
|
||||||
WERD_RES() {
|
WERD_RES() {
|
||||||
InitNonPointers();
|
InitNonPointers();
|
||||||
@ -707,6 +706,10 @@ class PAGE_RES_IT {
|
|||||||
// Deletes the current WERD_RES and its underlying WERD.
|
// Deletes the current WERD_RES and its underlying WERD.
|
||||||
void DeleteCurrentWord();
|
void DeleteCurrentWord();
|
||||||
|
|
||||||
|
// Makes the current word a fuzzy space if not already fuzzy. Updates
|
||||||
|
// corresponding part of combo if required.
|
||||||
|
void MakeCurrentWordFuzzy();
|
||||||
|
|
||||||
WERD_RES *forward() { // Get next word.
|
WERD_RES *forward() { // Get next word.
|
||||||
return internal_forward(false, false);
|
return internal_forward(false, false);
|
||||||
}
|
}
|
||||||
@ -746,9 +749,9 @@ class PAGE_RES_IT {
|
|||||||
return next_block_res;
|
return next_block_res;
|
||||||
}
|
}
|
||||||
void rej_stat_word(); // for page/block/row
|
void rej_stat_word(); // for page/block/row
|
||||||
|
void ResetWordIterator();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ResetWordIterator();
|
|
||||||
WERD_RES *internal_forward(bool new_block, bool empty_ok);
|
WERD_RES *internal_forward(bool new_block, bool empty_ok);
|
||||||
|
|
||||||
WERD_RES * prev_word_res; // previous word
|
WERD_RES * prev_word_res; // previous word
|
||||||
|
@ -77,7 +77,6 @@ void PDBLK::set_sides( //set vertex lists
|
|||||||
right_it.add_list_before (right);
|
right_it.add_list_before (right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* PDBLK::contains
|
* PDBLK::contains
|
||||||
*
|
*
|
||||||
@ -126,7 +125,7 @@ void PDBLK::move( // reposition block
|
|||||||
|
|
||||||
// Returns a binary Pix mask with a 1 pixel for every pixel within the
|
// Returns a binary Pix mask with a 1 pixel for every pixel within the
|
||||||
// block. Rotates the coordinate system by rerotation prior to rendering.
|
// block. Rotates the coordinate system by rerotation prior to rendering.
|
||||||
Pix* PDBLK::render_mask(const FCOORD& rerotation) {
|
Pix* PDBLK::render_mask(const FCOORD& rerotation, TBOX* mask_box) {
|
||||||
TBOX rotated_box(box);
|
TBOX rotated_box(box);
|
||||||
rotated_box.rotate(rerotation);
|
rotated_box.rotate(rerotation);
|
||||||
Pix* pix = pixCreate(rotated_box.width(), rotated_box.height(), 1);
|
Pix* pix = pixCreate(rotated_box.width(), rotated_box.height(), 1);
|
||||||
@ -163,6 +162,7 @@ Pix* PDBLK::render_mask(const FCOORD& rerotation) {
|
|||||||
pixRasterop(pix, 0, 0, rotated_box.width(), rotated_box.height(),
|
pixRasterop(pix, 0, 0, rotated_box.width(), rotated_box.height(),
|
||||||
PIX_SET, NULL, 0, 0);
|
PIX_SET, NULL, 0, 0);
|
||||||
}
|
}
|
||||||
|
if (mask_box != NULL) *mask_box = rotated_box;
|
||||||
return pix;
|
return pix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,9 @@ class PDBLK
|
|||||||
|
|
||||||
// Returns a binary Pix mask with a 1 pixel for every pixel within the
|
// Returns a binary Pix mask with a 1 pixel for every pixel within the
|
||||||
// block. Rotates the coordinate system by rerotation prior to rendering.
|
// block. Rotates the coordinate system by rerotation prior to rendering.
|
||||||
Pix* render_mask(const FCOORD& rerotation);
|
// If not NULL, mask_box is filled with the position box of the returned
|
||||||
|
// mask image.
|
||||||
|
Pix *render_mask(const FCOORD &rerotation, TBOX *mask_box);
|
||||||
|
|
||||||
#ifndef GRAPHICS_DISABLED
|
#ifndef GRAPHICS_DISABLED
|
||||||
///draw histogram
|
///draw histogram
|
||||||
|
@ -90,8 +90,6 @@ static const char * const kPermuterTypeNames[] = {
|
|||||||
BLOB_CHOICE::BLOB_CHOICE(UNICHAR_ID src_unichar_id, // character id
|
BLOB_CHOICE::BLOB_CHOICE(UNICHAR_ID src_unichar_id, // character id
|
||||||
float src_rating, // rating
|
float src_rating, // rating
|
||||||
float src_cert, // certainty
|
float src_cert, // certainty
|
||||||
inT16 src_fontinfo_id, // font
|
|
||||||
inT16 src_fontinfo_id2, // 2nd choice font
|
|
||||||
int src_script_id, // script
|
int src_script_id, // script
|
||||||
float min_xheight, // min xheight allowed
|
float min_xheight, // min xheight allowed
|
||||||
float max_xheight, // max xheight by this char
|
float max_xheight, // max xheight by this char
|
||||||
@ -100,8 +98,8 @@ BLOB_CHOICE::BLOB_CHOICE(UNICHAR_ID src_unichar_id, // character id
|
|||||||
unichar_id_ = src_unichar_id;
|
unichar_id_ = src_unichar_id;
|
||||||
rating_ = src_rating;
|
rating_ = src_rating;
|
||||||
certainty_ = src_cert;
|
certainty_ = src_cert;
|
||||||
fontinfo_id_ = src_fontinfo_id;
|
fontinfo_id_ = -1;
|
||||||
fontinfo_id2_ = src_fontinfo_id2;
|
fontinfo_id2_ = -1;
|
||||||
script_id_ = src_script_id;
|
script_id_ = src_script_id;
|
||||||
min_xheight_ = min_xheight;
|
min_xheight_ = min_xheight;
|
||||||
max_xheight_ = max_xheight;
|
max_xheight_ = max_xheight;
|
||||||
@ -126,6 +124,7 @@ BLOB_CHOICE::BLOB_CHOICE(const BLOB_CHOICE &other) {
|
|||||||
max_xheight_ = other.max_xheight_;
|
max_xheight_ = other.max_xheight_;
|
||||||
yshift_ = other.yshift();
|
yshift_ = other.yshift();
|
||||||
classifier_ = other.classifier_;
|
classifier_ = other.classifier_;
|
||||||
|
fonts_ = other.fonts_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if *this and other agree on the baseline and x-height
|
// Returns true if *this and other agree on the baseline and x-height
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include "clst.h"
|
#include "clst.h"
|
||||||
#include "elst.h"
|
#include "elst.h"
|
||||||
|
#include "fontinfo.h"
|
||||||
#include "genericvector.h"
|
#include "genericvector.h"
|
||||||
#include "matrix.h"
|
#include "matrix.h"
|
||||||
#include "unichar.h"
|
#include "unichar.h"
|
||||||
@ -64,8 +65,6 @@ class BLOB_CHOICE: public ELIST_LINK
|
|||||||
BLOB_CHOICE(UNICHAR_ID src_unichar_id, // character id
|
BLOB_CHOICE(UNICHAR_ID src_unichar_id, // character id
|
||||||
float src_rating, // rating
|
float src_rating, // rating
|
||||||
float src_cert, // certainty
|
float src_cert, // certainty
|
||||||
inT16 src_fontinfo_id, // font
|
|
||||||
inT16 src_fontinfo_id2, // 2nd choice font
|
|
||||||
int script_id, // script
|
int script_id, // script
|
||||||
float min_xheight, // min xheight in image pixel units
|
float min_xheight, // min xheight in image pixel units
|
||||||
float max_xheight, // max xheight allowed by this char
|
float max_xheight, // max xheight allowed by this char
|
||||||
@ -89,6 +88,26 @@ class BLOB_CHOICE: public ELIST_LINK
|
|||||||
inT16 fontinfo_id2() const {
|
inT16 fontinfo_id2() const {
|
||||||
return fontinfo_id2_;
|
return fontinfo_id2_;
|
||||||
}
|
}
|
||||||
|
const GenericVector<tesseract::ScoredFont>& fonts() const {
|
||||||
|
return fonts_;
|
||||||
|
}
|
||||||
|
void set_fonts(const GenericVector<tesseract::ScoredFont>& fonts) {
|
||||||
|
fonts_ = fonts;
|
||||||
|
int score1 = 0, score2 = 0;
|
||||||
|
fontinfo_id_ = -1;
|
||||||
|
fontinfo_id2_ = -1;
|
||||||
|
for (int f = 0; f < fonts_.size(); ++f) {
|
||||||
|
if (fonts_[f].score > score1) {
|
||||||
|
score2 = score1;
|
||||||
|
fontinfo_id2_ = fontinfo_id_;
|
||||||
|
score1 = fonts_[f].score;
|
||||||
|
fontinfo_id_ = fonts_[f].fontinfo_id;
|
||||||
|
} else if (fonts_[f].score > score2) {
|
||||||
|
score2 = fonts_[f].score;
|
||||||
|
fontinfo_id2_ = fonts_[f].fontinfo_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
int script_id() const {
|
int script_id() const {
|
||||||
return script_id_;
|
return script_id_;
|
||||||
}
|
}
|
||||||
@ -131,12 +150,6 @@ class BLOB_CHOICE: public ELIST_LINK
|
|||||||
void set_certainty(float newrat) {
|
void set_certainty(float newrat) {
|
||||||
certainty_ = newrat;
|
certainty_ = newrat;
|
||||||
}
|
}
|
||||||
void set_fontinfo_id(inT16 newfont) {
|
|
||||||
fontinfo_id_ = newfont;
|
|
||||||
}
|
|
||||||
void set_fontinfo_id2(inT16 newfont) {
|
|
||||||
fontinfo_id2_ = newfont;
|
|
||||||
}
|
|
||||||
void set_script(int newscript_id) {
|
void set_script(int newscript_id) {
|
||||||
script_id_ = newscript_id;
|
script_id_ = newscript_id;
|
||||||
}
|
}
|
||||||
@ -186,6 +199,8 @@ class BLOB_CHOICE: public ELIST_LINK
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
UNICHAR_ID unichar_id_; // unichar id
|
UNICHAR_ID unichar_id_; // unichar id
|
||||||
|
// Fonts and scores. Allowed to be empty.
|
||||||
|
GenericVector<tesseract::ScoredFont> fonts_;
|
||||||
inT16 fontinfo_id_; // char font information
|
inT16 fontinfo_id_; // char font information
|
||||||
inT16 fontinfo_id2_; // 2nd choice font information
|
inT16 fontinfo_id2_; // 2nd choice font information
|
||||||
// Rating is the classifier distance weighted by the length of the outline
|
// Rating is the classifier distance weighted by the length of the outline
|
||||||
|
@ -27,114 +27,236 @@
|
|||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
#include "seam.h"
|
#include "seam.h"
|
||||||
#include "blobs.h"
|
#include "blobs.h"
|
||||||
#include "freelist.h"
|
|
||||||
#include "tprintf.h"
|
#include "tprintf.h"
|
||||||
|
|
||||||
#ifdef __UNIX__
|
|
||||||
#include <assert.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
|
||||||
V a r i a b l e s
|
|
||||||
----------------------------------------------------------------------*/
|
|
||||||
#define NUM_STARTING_SEAMS 20
|
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
Public Function Code
|
Public Function Code
|
||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
/**
|
|
||||||
* @name point_in_split
|
|
||||||
*
|
|
||||||
* Check to see if either of these points are present in the current
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Returns the bounding box of all the points in the seam.
|
||||||
/**
|
TBOX SEAM::bounding_box() const {
|
||||||
* @name point_in_seam
|
TBOX box(location_.x, location_.y, location_.x, location_.y);
|
||||||
*
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
* Check to see if either of these points are present in the current
|
box += splits_[s].bounding_box();
|
||||||
* seam.
|
|
||||||
* @returns TRUE if one of them is.
|
|
||||||
*/
|
|
||||||
bool point_in_seam(const 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name point_used_by_split
|
|
||||||
*
|
|
||||||
* Return whether this particular EDGEPT * is used in a given split.
|
|
||||||
* @returns TRUE if the edgept is used by the split.
|
|
||||||
*/
|
|
||||||
bool point_used_by_split(SPLIT *split, EDGEPT *point) {
|
|
||||||
if (split == NULL) return false;
|
|
||||||
return point == split->point1 || point == split->point2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name point_used_by_seam
|
|
||||||
*
|
|
||||||
* Return whether this particular EDGEPT * is used in a given seam.
|
|
||||||
* @returns TRUE if the edgept is used by the seam.
|
|
||||||
*/
|
|
||||||
bool point_used_by_seam(SEAM *seam, EDGEPT *point) {
|
|
||||||
if (seam == NULL) return false;
|
|
||||||
return point_used_by_split(seam->split1, point) ||
|
|
||||||
point_used_by_split(seam->split2, point) ||
|
|
||||||
point_used_by_split(seam->split3, point);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name combine_seam
|
|
||||||
*
|
|
||||||
* Combine two seam records into a single seam. Move the split
|
|
||||||
* references from the second seam to the first one. The argument
|
|
||||||
* convention is patterned after strcpy.
|
|
||||||
*/
|
|
||||||
void combine_seams(SEAM *dest_seam, SEAM *source_seam) {
|
|
||||||
dest_seam->priority += source_seam->priority;
|
|
||||||
dest_seam->location += source_seam->location;
|
|
||||||
dest_seam->location /= 2;
|
|
||||||
|
|
||||||
if (source_seam->split1) {
|
|
||||||
if (!dest_seam->split1)
|
|
||||||
dest_seam->split1 = source_seam->split1;
|
|
||||||
else if (!dest_seam->split2)
|
|
||||||
dest_seam->split2 = source_seam->split1;
|
|
||||||
else if (!dest_seam->split3)
|
|
||||||
dest_seam->split3 = source_seam->split1;
|
|
||||||
else
|
|
||||||
delete source_seam->split1; // Wouldn't have fitted.
|
|
||||||
source_seam->split1 = NULL;
|
|
||||||
}
|
}
|
||||||
if (source_seam->split2) {
|
return box;
|
||||||
if (!dest_seam->split2)
|
}
|
||||||
dest_seam->split2 = source_seam->split2;
|
|
||||||
else if (!dest_seam->split3)
|
// Returns true if other can be combined into *this.
|
||||||
dest_seam->split3 = source_seam->split2;
|
bool SEAM::CombineableWith(const SEAM& other, int max_x_dist,
|
||||||
else
|
float max_total_priority) const {
|
||||||
delete source_seam->split2; // Wouldn't have fitted.
|
int dist = location_.x - other.location_.x;
|
||||||
source_seam->split2 = NULL;
|
if (-max_x_dist < dist && dist < max_x_dist &&
|
||||||
|
num_splits_ + other.num_splits_ <= kMaxNumSplits &&
|
||||||
|
priority_ + other.priority_ < max_total_priority &&
|
||||||
|
!OverlappingSplits(other) && !SharesPosition(other)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (source_seam->split3) {
|
}
|
||||||
if (!dest_seam->split3)
|
|
||||||
dest_seam->split3 = source_seam->split3;
|
// Combines other into *this. Only works if CombinableWith returned true.
|
||||||
else
|
void SEAM::CombineWith(const SEAM& other) {
|
||||||
delete source_seam->split3; // Wouldn't have fitted.
|
priority_ += other.priority_;
|
||||||
source_seam->split3 = NULL;
|
location_ += other.location_;
|
||||||
|
location_ /= 2;
|
||||||
|
|
||||||
|
for (int s = 0; s < other.num_splits_ && num_splits_ < kMaxNumSplits; ++s)
|
||||||
|
splits_[num_splits_++] = other.splits_[s];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the splits in *this SEAM appear OK in the sense that they
|
||||||
|
// do not cross any outlines and do not chop off any ridiculously small
|
||||||
|
// pieces.
|
||||||
|
bool SEAM::IsHealthy(const TBLOB& blob, int min_points, int min_area) const {
|
||||||
|
// TODO(rays) Try testing all the splits. Duplicating original code for now,
|
||||||
|
// which tested only the first.
|
||||||
|
return num_splits_ == 0 || splits_[0].IsHealthy(blob, min_points, min_area);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes the widthp_/widthn_ range for all existing SEAMs and for *this
|
||||||
|
// seam, which is about to be inserted at insert_index. Returns false if
|
||||||
|
// any of the computations fails, as this indicates an invalid chop.
|
||||||
|
// widthn_/widthp_ are only changed if modify is true.
|
||||||
|
bool SEAM::PrepareToInsertSeam(const GenericVector<SEAM*>& seams,
|
||||||
|
const GenericVector<TBLOB*>& blobs,
|
||||||
|
int insert_index, bool modify) {
|
||||||
|
for (int s = 0; s < insert_index; ++s) {
|
||||||
|
if (!seams[s]->FindBlobWidth(blobs, s, modify)) return false;
|
||||||
}
|
}
|
||||||
delete source_seam;
|
if (!FindBlobWidth(blobs, insert_index, modify)) return false;
|
||||||
|
for (int s = insert_index; s < seams.size(); ++s) {
|
||||||
|
if (!seams[s]->FindBlobWidth(blobs, s + 1, modify)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes the widthp_/widthn_ range. Returns false if not all the splits
|
||||||
|
// are accounted for. widthn_/widthp_ are only changed if modify is true.
|
||||||
|
bool SEAM::FindBlobWidth(const GenericVector<TBLOB*>& blobs, int index,
|
||||||
|
bool modify) {
|
||||||
|
int num_found = 0;
|
||||||
|
if (modify) {
|
||||||
|
widthp_ = 0;
|
||||||
|
widthn_ = 0;
|
||||||
|
}
|
||||||
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
|
const SPLIT& split = splits_[s];
|
||||||
|
bool found_split = split.ContainedByBlob(*blobs[index]);
|
||||||
|
// Look right.
|
||||||
|
for (int b = index + 1; !found_split && b < blobs.size(); ++b) {
|
||||||
|
found_split = split.ContainedByBlob(*blobs[b]);
|
||||||
|
if (found_split && b - index > widthp_ && modify) widthp_ = b - index;
|
||||||
|
}
|
||||||
|
// Look left.
|
||||||
|
for (int b = index - 1; !found_split && b >= 0; --b) {
|
||||||
|
found_split = split.ContainedByBlob(*blobs[b]);
|
||||||
|
if (found_split && index - b > widthn_ && modify) widthn_ = index - b;
|
||||||
|
}
|
||||||
|
if (found_split) ++num_found;
|
||||||
|
}
|
||||||
|
return num_found == num_splits_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splits this blob into two blobs by applying the splits included in
|
||||||
|
// *this SEAM
|
||||||
|
void SEAM::ApplySeam(bool italic_blob, TBLOB* blob, TBLOB* other_blob) const {
|
||||||
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
|
splits_[s].SplitOutlineList(blob->outlines);
|
||||||
|
}
|
||||||
|
blob->ComputeBoundingBoxes();
|
||||||
|
|
||||||
|
divide_blobs(blob, other_blob, italic_blob, location_);
|
||||||
|
|
||||||
|
blob->EliminateDuplicateOutlines();
|
||||||
|
other_blob->EliminateDuplicateOutlines();
|
||||||
|
|
||||||
|
blob->CorrectBlobOrder(other_blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undoes ApplySeam by removing the seam between these two blobs.
|
||||||
|
// Produces one blob as a result, and deletes other_blob.
|
||||||
|
void SEAM::UndoSeam(TBLOB* blob, TBLOB* other_blob) const {
|
||||||
|
if (blob->outlines == NULL) {
|
||||||
|
blob->outlines = other_blob->outlines;
|
||||||
|
other_blob->outlines = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
TESSLINE* outline = blob->outlines;
|
||||||
|
while (outline->next) outline = outline->next;
|
||||||
|
outline->next = other_blob->outlines;
|
||||||
|
other_blob->outlines = NULL;
|
||||||
|
delete other_blob;
|
||||||
|
|
||||||
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
|
splits_[s].UnsplitOutlineList(blob);
|
||||||
|
}
|
||||||
|
blob->ComputeBoundingBoxes();
|
||||||
|
blob->EliminateDuplicateOutlines();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints everything in *this SEAM.
|
||||||
|
void SEAM::Print(const char* label) const {
|
||||||
|
tprintf(label);
|
||||||
|
tprintf(" %6.2f @ (%d,%d), p=%d, n=%d ", priority_, location_.x, location_.y,
|
||||||
|
widthp_, widthn_);
|
||||||
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
|
splits_[s].Print();
|
||||||
|
if (s + 1 < num_splits_) tprintf(", ");
|
||||||
|
}
|
||||||
|
tprintf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints a collection of SEAMs.
|
||||||
|
/* static */
|
||||||
|
void SEAM::PrintSeams(const char* label, const GenericVector<SEAM*>& seams) {
|
||||||
|
if (!seams.empty()) {
|
||||||
|
tprintf("%s\n", label);
|
||||||
|
for (int x = 0; x < seams.size(); ++x) {
|
||||||
|
tprintf("%2d: ", x);
|
||||||
|
seams[x]->Print("");
|
||||||
|
}
|
||||||
|
tprintf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef GRAPHICS_DISABLED
|
||||||
|
// Draws the seam in the given window.
|
||||||
|
void SEAM::Mark(ScrollView* window) const {
|
||||||
|
for (int s = 0; s < num_splits_; ++s) splits_[s].Mark(window);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Break up the blobs in this chain so that they are all independent.
|
||||||
|
// This operation should undo the affect of join_pieces.
|
||||||
|
/* static */
|
||||||
|
void SEAM::BreakPieces(const GenericVector<SEAM*>& seams,
|
||||||
|
const GenericVector<TBLOB*>& blobs, int first,
|
||||||
|
int last) {
|
||||||
|
for (int x = first; x < last; ++x) seams[x]->Reveal();
|
||||||
|
|
||||||
|
TESSLINE* outline = blobs[first]->outlines;
|
||||||
|
int next_blob = first + 1;
|
||||||
|
|
||||||
|
while (outline != NULL && next_blob <= last) {
|
||||||
|
if (outline->next == blobs[next_blob]->outlines) {
|
||||||
|
outline->next = NULL;
|
||||||
|
outline = blobs[next_blob]->outlines;
|
||||||
|
++next_blob;
|
||||||
|
} else {
|
||||||
|
outline = outline->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join a group of base level pieces into a single blob that can then
|
||||||
|
// be classified.
|
||||||
|
/* static */
|
||||||
|
void SEAM::JoinPieces(const GenericVector<SEAM*>& seams,
|
||||||
|
const GenericVector<TBLOB*>& blobs, int first, int last) {
|
||||||
|
TESSLINE* outline = blobs[first]->outlines;
|
||||||
|
if (!outline)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int x = first; x < last; ++x) {
|
||||||
|
SEAM *seam = seams[x];
|
||||||
|
if (x - seam->widthn_ >= first && x + seam->widthp_ < last) seam->Hide();
|
||||||
|
while (outline->next) outline = outline->next;
|
||||||
|
outline->next = blobs[x + 1]->outlines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hides the seam so the outlines appear not to be cut by it.
|
||||||
|
void SEAM::Hide() const {
|
||||||
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
|
splits_[s].Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undoes hide, so the outlines are cut by the seam.
|
||||||
|
void SEAM::Reveal() const {
|
||||||
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
|
splits_[s].Reveal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes and returns, but does not set, the full priority of *this SEAM.
|
||||||
|
float SEAM::FullPriority(int xmin, int xmax, double overlap_knob,
|
||||||
|
int centered_maxwidth, double center_knob,
|
||||||
|
double width_change_knob) const {
|
||||||
|
if (num_splits_ == 0) return 0.0f;
|
||||||
|
for (int s = 1; s < num_splits_; ++s) {
|
||||||
|
splits_[s].SplitOutline();
|
||||||
|
}
|
||||||
|
float full_priority =
|
||||||
|
priority_ +
|
||||||
|
splits_[0].FullPriority(xmin, xmax, overlap_knob, centered_maxwidth,
|
||||||
|
center_knob, width_change_knob);
|
||||||
|
for (int s = num_splits_ - 1; s >= 1; --s) {
|
||||||
|
splits_[s].UnsplitOutlines();
|
||||||
|
}
|
||||||
|
return full_priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,7 +266,7 @@ void combine_seams(SEAM *dest_seam, SEAM *source_seam) {
|
|||||||
* present in the starting segmentation. Each of the seams created
|
* present in the starting segmentation. Each of the seams created
|
||||||
* by this routine have location information only.
|
* by this routine have location information only.
|
||||||
*/
|
*/
|
||||||
void start_seam_list(TWERD *word, GenericVector<SEAM*>* seam_array) {
|
void start_seam_list(TWERD* word, GenericVector<SEAM*>* seam_array) {
|
||||||
seam_array->truncate(0);
|
seam_array->truncate(0);
|
||||||
TPOINT location;
|
TPOINT location;
|
||||||
|
|
||||||
@ -153,381 +275,6 @@ void start_seam_list(TWERD *word, GenericVector<SEAM*>* seam_array) {
|
|||||||
TBOX nbox = word->blobs[b]->bounding_box();
|
TBOX nbox = word->blobs[b]->bounding_box();
|
||||||
location.x = (bbox.right() + nbox.left()) / 2;
|
location.x = (bbox.right() + nbox.left()) / 2;
|
||||||
location.y = (bbox.bottom() + bbox.top() + nbox.bottom() + nbox.top()) / 4;
|
location.y = (bbox.bottom() + bbox.top() + nbox.bottom() + nbox.top()) / 4;
|
||||||
seam_array->push_back(new SEAM(0.0f, location, NULL, NULL, NULL));
|
seam_array->push_back(new SEAM(0.0f, location));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name test_insert_seam
|
|
||||||
*
|
|
||||||
* @returns true if insert_seam will succeed.
|
|
||||||
*/
|
|
||||||
bool test_insert_seam(const GenericVector<SEAM*>& seam_array,
|
|
||||||
TWERD *word, int index) {
|
|
||||||
SEAM *test_seam;
|
|
||||||
int list_length = seam_array.size();
|
|
||||||
for (int test_index = 0; test_index < index; ++test_index) {
|
|
||||||
test_seam = seam_array[test_index];
|
|
||||||
if (test_index + test_seam->widthp < index &&
|
|
||||||
test_seam->widthp + test_index == index - 1 &&
|
|
||||||
account_splits(test_seam, word, test_index + 1, 1) < 0)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (int test_index = index; test_index < list_length; test_index++) {
|
|
||||||
test_seam = seam_array[test_index];
|
|
||||||
if (test_index - test_seam->widthn >= index &&
|
|
||||||
test_index - test_seam->widthn == index &&
|
|
||||||
account_splits(test_seam, word, test_index + 1, -1) < 0)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name insert_seam
|
|
||||||
*
|
|
||||||
* Add another seam to a collection of seams at a particular location
|
|
||||||
* in the seam array.
|
|
||||||
*/
|
|
||||||
void insert_seam(const TWERD* word, int index, SEAM *seam,
|
|
||||||
GenericVector<SEAM*>* seam_array) {
|
|
||||||
SEAM *test_seam;
|
|
||||||
int list_length = seam_array->size();
|
|
||||||
for (int test_index = 0; test_index < index; ++test_index) {
|
|
||||||
test_seam = seam_array->get(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) {
|
|
||||||
test_seam->widthp = account_splits(test_seam, word, test_index + 1, 1);
|
|
||||||
if (test_seam->widthp < 0) {
|
|
||||||
tprintf("Failed to find any right blob for a split!\n");
|
|
||||||
print_seam("New dud seam", seam);
|
|
||||||
print_seam("Failed seam", test_seam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int test_index = index; test_index < list_length; test_index++) {
|
|
||||||
test_seam = seam_array->get(test_index);
|
|
||||||
if (test_index - test_seam->widthn < index) {
|
|
||||||
test_seam->widthn++; /*got in the way */
|
|
||||||
} else if (test_index - test_seam->widthn == index) {
|
|
||||||
test_seam->widthn = account_splits(test_seam, word, test_index + 1, -1);
|
|
||||||
if (test_seam->widthn < 0) {
|
|
||||||
tprintf("Failed to find any left blob for a split!\n");
|
|
||||||
print_seam("New dud seam", seam);
|
|
||||||
print_seam("Failed seam", test_seam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
seam_array->insert(seam, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name account_splits
|
|
||||||
*
|
|
||||||
* Account for all the splits by looking to the right (blob_direction == 1),
|
|
||||||
* or to the left (blob_direction == -1) in the word.
|
|
||||||
*/
|
|
||||||
int account_splits(const SEAM *seam, const TWERD *word, int blob_index,
|
|
||||||
int blob_direction) {
|
|
||||||
inT8 found_em[3];
|
|
||||||
inT8 width;
|
|
||||||
|
|
||||||
found_em[0] = seam->split1 == NULL;
|
|
||||||
found_em[1] = seam->split2 == NULL;
|
|
||||||
found_em[2] = seam->split3 == NULL;
|
|
||||||
if (found_em[0] && found_em[1] && found_em[2])
|
|
||||||
return 0;
|
|
||||||
width = 0;
|
|
||||||
do {
|
|
||||||
TBLOB* blob = word->blobs[blob_index];
|
|
||||||
if (!found_em[0])
|
|
||||||
found_em[0] = find_split_in_blob(seam->split1, blob);
|
|
||||||
if (!found_em[1])
|
|
||||||
found_em[1] = find_split_in_blob(seam->split2, blob);
|
|
||||||
if (!found_em[2])
|
|
||||||
found_em[2] = find_split_in_blob(seam->split3, blob);
|
|
||||||
if (found_em[0] && found_em[1] && found_em[2]) {
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
width++;
|
|
||||||
blob_index += blob_direction;
|
|
||||||
} while (0 <= blob_index && blob_index < word->NumBlobs());
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name find_split_in_blob
|
|
||||||
*
|
|
||||||
* @returns TRUE if the split is somewhere in this blob.
|
|
||||||
*/
|
|
||||||
bool find_split_in_blob(SPLIT *split, TBLOB *blob) {
|
|
||||||
TESSLINE *outline;
|
|
||||||
|
|
||||||
for (outline = blob->outlines; outline != NULL; outline = outline->next)
|
|
||||||
if (outline->Contains(split->point1->pos))
|
|
||||||
break;
|
|
||||||
if (outline == NULL)
|
|
||||||
return FALSE;
|
|
||||||
for (outline = blob->outlines; outline != NULL; outline = outline->next)
|
|
||||||
if (outline->Contains(split->point2->pos))
|
|
||||||
return TRUE;
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name join_two_seams
|
|
||||||
*
|
|
||||||
* Merge these two seams into a new seam. Duplicate the split records
|
|
||||||
* in both of the input seams. Return the resultant seam.
|
|
||||||
*/
|
|
||||||
SEAM *join_two_seams(const SEAM *seam1, const SEAM *seam2) {
|
|
||||||
SEAM *result = NULL;
|
|
||||||
SEAM *temp;
|
|
||||||
|
|
||||||
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))) {
|
|
||||||
result = new SEAM(*seam1);
|
|
||||||
temp = new SEAM(*seam2);
|
|
||||||
combine_seams(result, temp);
|
|
||||||
}
|
|
||||||
return (result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name print_seam
|
|
||||||
*
|
|
||||||
* Print a list of splits. Show the coordinates of both points in
|
|
||||||
* each split.
|
|
||||||
*/
|
|
||||||
void print_seam(const char *label, SEAM *seam) {
|
|
||||||
if (seam) {
|
|
||||||
tprintf(label);
|
|
||||||
tprintf(" %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) {
|
|
||||||
tprintf(", ");
|
|
||||||
print_split (seam->split2);
|
|
||||||
if (seam->split3) {
|
|
||||||
tprintf(", ");
|
|
||||||
print_split (seam->split3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tprintf("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name print_seams
|
|
||||||
*
|
|
||||||
* Print a list of splits. Show the coordinates of both points in
|
|
||||||
* each split.
|
|
||||||
*/
|
|
||||||
void print_seams(const char *label, const GenericVector<SEAM*>& seams) {
|
|
||||||
char number[CHARS_PER_LINE];
|
|
||||||
|
|
||||||
if (!seams.empty()) {
|
|
||||||
tprintf("%s\n", label);
|
|
||||||
for (int x = 0; x < seams.size(); ++x) {
|
|
||||||
sprintf(number, "%2d: ", x);
|
|
||||||
print_seam(number, seams[x]);
|
|
||||||
}
|
|
||||||
tprintf("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name shared_split_points
|
|
||||||
*
|
|
||||||
* Check these two seams to make sure that neither of them have two
|
|
||||||
* points in common. Return TRUE if any of the same points are present
|
|
||||||
* in any of the splits of both seams.
|
|
||||||
*/
|
|
||||||
int shared_split_points(const SEAM *seam1, const SEAM *seam2) {
|
|
||||||
if (seam1 == NULL || seam2 == NULL)
|
|
||||||
return (FALSE);
|
|
||||||
|
|
||||||
if (seam2->split1 == NULL)
|
|
||||||
return (FALSE);
|
|
||||||
if (point_in_seam(seam1, seam2->split1))
|
|
||||||
return (TRUE);
|
|
||||||
|
|
||||||
if (seam2->split2 == NULL)
|
|
||||||
return (FALSE);
|
|
||||||
if (point_in_seam(seam1, seam2->split2))
|
|
||||||
return (TRUE);
|
|
||||||
|
|
||||||
if (seam2->split3 == NULL)
|
|
||||||
return (FALSE);
|
|
||||||
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(const GenericVector<SEAM*>& seams, int first, int last,
|
|
||||||
TWERD *word) {
|
|
||||||
for (int x = first; x < last; ++x)
|
|
||||||
reveal_seam(seams[x]);
|
|
||||||
|
|
||||||
TESSLINE *outline = word->blobs[first]->outlines;
|
|
||||||
int next_blob = first + 1;
|
|
||||||
|
|
||||||
while (outline != NULL && next_blob <= last) {
|
|
||||||
if (outline->next == word->blobs[next_blob]->outlines) {
|
|
||||||
outline->next = NULL;
|
|
||||||
outline = word->blobs[next_blob]->outlines;
|
|
||||||
++next_blob;
|
|
||||||
} 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(const GenericVector<SEAM*>& seams, int first, int last,
|
|
||||||
TWERD *word) {
|
|
||||||
TESSLINE *outline = word->blobs[first]->outlines;
|
|
||||||
if (!outline)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (int x = first; x < last; ++x) {
|
|
||||||
SEAM *seam = seams[x];
|
|
||||||
if (x - seam->widthn >= first && x + seam->widthp < last)
|
|
||||||
hide_seam(seam);
|
|
||||||
while (outline->next)
|
|
||||||
outline = outline->next;
|
|
||||||
outline->next = word->blobs[x + 1]->outlines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* 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) {
|
|
||||||
/* tprintf("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) {
|
|
||||||
/* tprintf("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) {
|
|
||||||
/* tprintf("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) {
|
|
||||||
/* tprintf("Hid entire outline at (%d,%d)!!\n",
|
|
||||||
edgept->pos.x,edgept->pos.y); */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
230
ccstruct/seam.h
230
ccstruct/seam.h
@ -36,95 +36,163 @@
|
|||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
typedef float PRIORITY; /* PRIORITY */
|
typedef float PRIORITY; /* PRIORITY */
|
||||||
|
|
||||||
struct SEAM {
|
class SEAM {
|
||||||
// Constructor that was formerly new_seam.
|
public:
|
||||||
SEAM(PRIORITY priority0, const TPOINT& location0,
|
// A seam with no splits
|
||||||
SPLIT *splita, SPLIT *splitb, SPLIT *splitc)
|
SEAM(float priority, const TPOINT& location)
|
||||||
: priority(priority0), widthp(0), widthn(0), location(location0),
|
: priority_(priority),
|
||||||
split1(splita), split2(splitb), split3(splitc) {}
|
location_(location),
|
||||||
// Copy constructor that was formerly clone_seam.
|
widthp_(0),
|
||||||
SEAM(const SEAM& src)
|
widthn_(0),
|
||||||
: priority(src.priority), widthp(src.widthp), widthn(src.widthn),
|
num_splits_(0) {}
|
||||||
location(src.location) {
|
// A seam with a single split point.
|
||||||
clone_split(split1, src.split1);
|
SEAM(float priority, const TPOINT& location, const SPLIT& split)
|
||||||
clone_split(split2, src.split2);
|
: priority_(priority),
|
||||||
clone_split(split3, src.split3);
|
location_(location),
|
||||||
|
widthp_(0),
|
||||||
|
widthn_(0),
|
||||||
|
num_splits_(1) {
|
||||||
|
splits_[0] = split;
|
||||||
}
|
}
|
||||||
// Destructor was delete_seam.
|
// Default copy constructor, operator= and destructor are OK!
|
||||||
~SEAM() {
|
|
||||||
if (split1)
|
// Accessors.
|
||||||
delete_split(split1);
|
float priority() const { return priority_; }
|
||||||
if (split2)
|
void set_priority(float priority) { priority_ = priority; }
|
||||||
delete_split(split2);
|
bool HasAnySplits() const { return num_splits_ > 0; }
|
||||||
if (split3)
|
|
||||||
delete_split(split3);
|
// Returns the bounding box of all the points in the seam.
|
||||||
|
TBOX bounding_box() const;
|
||||||
|
|
||||||
|
// Returns true if other can be combined into *this.
|
||||||
|
bool CombineableWith(const SEAM& other, int max_x_dist,
|
||||||
|
float max_total_priority) const;
|
||||||
|
// Combines other into *this. Only works if CombinableWith returned true.
|
||||||
|
void CombineWith(const SEAM& other);
|
||||||
|
|
||||||
|
// Returns true if the given blob contains all splits of *this SEAM.
|
||||||
|
bool ContainedByBlob(const TBLOB& blob) const {
|
||||||
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
|
if (!splits_[s].ContainedByBlob(blob)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PRIORITY priority;
|
// Returns true if the given EDGEPT is used by this SEAM, checking only
|
||||||
inT8 widthp;
|
// the EDGEPT pointer, not the coordinates.
|
||||||
inT8 widthn;
|
bool UsesPoint(const EDGEPT* point) const {
|
||||||
TPOINT location;
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
SPLIT *split1;
|
if (splits_[s].UsesPoint(point)) return true;
|
||||||
SPLIT *split2;
|
}
|
||||||
SPLIT *split3;
|
return false;
|
||||||
|
}
|
||||||
|
// Returns true if *this and other share any common point, by coordinates.
|
||||||
|
bool SharesPosition(const SEAM& other) const {
|
||||||
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
|
for (int t = 0; t < other.num_splits_; ++t)
|
||||||
|
if (splits_[s].SharesPosition(other.splits_[t])) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Returns true if *this and other have any vertically overlapping splits.
|
||||||
|
bool OverlappingSplits(const SEAM& other) const {
|
||||||
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
|
TBOX split1_box = splits_[s].bounding_box();
|
||||||
|
for (int t = 0; t < other.num_splits_; ++t) {
|
||||||
|
TBOX split2_box = other.splits_[t].bounding_box();
|
||||||
|
if (split1_box.y_overlap(split2_box)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marks the edgepts used by the seam so the segments made by the cut
|
||||||
|
// never get split further by another seam in the future.
|
||||||
|
void Finalize() {
|
||||||
|
for (int s = 0; s < num_splits_; ++s) {
|
||||||
|
splits_[s].point1->MarkChop();
|
||||||
|
splits_[s].point2->MarkChop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the splits in *this SEAM appear OK in the sense that they
|
||||||
|
// do not cross any outlines and do not chop off any ridiculously small
|
||||||
|
// pieces.
|
||||||
|
bool IsHealthy(const TBLOB& blob, int min_points, int min_area) const;
|
||||||
|
|
||||||
|
// Computes the widthp_/widthn_ range for all existing SEAMs and for *this
|
||||||
|
// seam, which is about to be inserted at insert_index. Returns false if
|
||||||
|
// any of the computations fails, as this indicates an invalid chop.
|
||||||
|
// widthn_/widthp_ are only changed if modify is true.
|
||||||
|
bool PrepareToInsertSeam(const GenericVector<SEAM*>& seams,
|
||||||
|
const GenericVector<TBLOB*>& blobs, int insert_index,
|
||||||
|
bool modify);
|
||||||
|
// Computes the widthp_/widthn_ range. Returns false if not all the splits
|
||||||
|
// are accounted for. widthn_/widthp_ are only changed if modify is true.
|
||||||
|
bool FindBlobWidth(const GenericVector<TBLOB*>& blobs, int index,
|
||||||
|
bool modify);
|
||||||
|
|
||||||
|
// Splits this blob into two blobs by applying the splits included in
|
||||||
|
// *this SEAM
|
||||||
|
void ApplySeam(bool italic_blob, TBLOB* blob, TBLOB* other_blob) const;
|
||||||
|
// Undoes ApplySeam by removing the seam between these two blobs.
|
||||||
|
// Produces one blob as a result, and deletes other_blob.
|
||||||
|
void UndoSeam(TBLOB* blob, TBLOB* other_blob) const;
|
||||||
|
|
||||||
|
// Prints everything in *this SEAM.
|
||||||
|
void Print(const char* label) const;
|
||||||
|
// Prints a collection of SEAMs.
|
||||||
|
static void PrintSeams(const char* label, const GenericVector<SEAM*>& seams);
|
||||||
|
#ifndef GRAPHICS_DISABLED
|
||||||
|
// Draws the seam in the given window.
|
||||||
|
void Mark(ScrollView* window) const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Break up the blobs in this chain so that they are all independent.
|
||||||
|
// This operation should undo the affect of join_pieces.
|
||||||
|
static void BreakPieces(const GenericVector<SEAM*>& seams,
|
||||||
|
const GenericVector<TBLOB*>& blobs, int first,
|
||||||
|
int last);
|
||||||
|
// Join a group of base level pieces into a single blob that can then
|
||||||
|
// be classified.
|
||||||
|
static void JoinPieces(const GenericVector<SEAM*>& seams,
|
||||||
|
const GenericVector<TBLOB*>& blobs, int first,
|
||||||
|
int last);
|
||||||
|
|
||||||
|
// Hides the seam so the outlines appear not to be cut by it.
|
||||||
|
void Hide() const;
|
||||||
|
// Undoes hide, so the outlines are cut by the seam.
|
||||||
|
void Reveal() const;
|
||||||
|
|
||||||
|
// Computes and returns, but does not set, the full priority of *this SEAM.
|
||||||
|
// The arguments here are config parameters defined in Wordrec. Add chop_
|
||||||
|
// to the beginning of the name.
|
||||||
|
float FullPriority(int xmin, int xmax, double overlap_knob,
|
||||||
|
int centered_maxwidth, double center_knob,
|
||||||
|
double width_change_knob) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Maximum number of splits that a SEAM can hold.
|
||||||
|
static const int kMaxNumSplits = 3;
|
||||||
|
// Priority of this split. Lower is better.
|
||||||
|
float priority_;
|
||||||
|
// Position of the middle of the seam.
|
||||||
|
TPOINT location_;
|
||||||
|
// A range such that all splits in *this SEAM are contained within blobs in
|
||||||
|
// the range [index - widthn_,index + widthp_] where index is the index of
|
||||||
|
// this SEAM in the seams vector.
|
||||||
|
inT8 widthp_;
|
||||||
|
inT8 widthn_;
|
||||||
|
// Number of splits_ that are used.
|
||||||
|
inT8 num_splits_;
|
||||||
|
// Set of pairs of points that are the ends of each split in the SEAM.
|
||||||
|
SPLIT splits_[kMaxNumSplits];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* exact_point
|
|
||||||
*
|
|
||||||
* Return TRUE if the point positions are the exactly the same. The
|
|
||||||
* parameters must be of type (EDGEPT*).
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define exact_point(p1,p2) \
|
|
||||||
(! ((p1->pos.x - p2->pos.x) || (p1->pos.y - p2->pos.y)))
|
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
F u n c t i o n s
|
F u n c t i o n s
|
||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
bool point_in_split(SPLIT *split, EDGEPT *point1, EDGEPT *point2);
|
|
||||||
|
|
||||||
bool point_in_seam(const SEAM *seam, SPLIT *split);
|
void start_seam_list(TWERD* word, GenericVector<SEAM*>* seam_array);
|
||||||
|
|
||||||
bool point_used_by_split(SPLIT *split, EDGEPT *point);
|
|
||||||
|
|
||||||
bool point_used_by_seam(SEAM *seam, EDGEPT *point);
|
|
||||||
|
|
||||||
void combine_seams(SEAM *dest_seam, SEAM *source_seam);
|
|
||||||
|
|
||||||
void start_seam_list(TWERD *word, GenericVector<SEAM*>* seam_array);
|
|
||||||
|
|
||||||
bool test_insert_seam(const GenericVector<SEAM*>& seam_array,
|
|
||||||
TWERD *word, int index);
|
|
||||||
|
|
||||||
void insert_seam(const TWERD *word, int index, SEAM *seam,
|
|
||||||
GenericVector<SEAM*>* seam_array);
|
|
||||||
|
|
||||||
int account_splits(const SEAM *seam, const TWERD *word, int blob_index,
|
|
||||||
int blob_direction);
|
|
||||||
|
|
||||||
bool find_split_in_blob(SPLIT *split, TBLOB *blob);
|
|
||||||
|
|
||||||
SEAM *join_two_seams(const SEAM *seam1, const SEAM *seam2);
|
|
||||||
|
|
||||||
void print_seam(const char *label, SEAM *seam);
|
|
||||||
|
|
||||||
void print_seams(const char *label, const GenericVector<SEAM*>& seams);
|
|
||||||
|
|
||||||
int shared_split_points(const SEAM *seam1, const SEAM *seam2);
|
|
||||||
|
|
||||||
void break_pieces(const GenericVector<SEAM*>& seams,
|
|
||||||
int first, int last, TWERD *word);
|
|
||||||
|
|
||||||
void join_pieces(const GenericVector<SEAM*>& seams,
|
|
||||||
int first, int last, TWERD *word);
|
|
||||||
|
|
||||||
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
|
#endif
|
||||||
|
@ -36,23 +36,103 @@
|
|||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
V a r i a b l e s
|
V a r i a b l e s
|
||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
|
// Limit on the amount of penalty for the chop being off-center.
|
||||||
|
const int kCenterGradeCap = 25;
|
||||||
|
// Ridiculously large priority for splits that are no use.
|
||||||
|
const double kBadPriority = 999.0;
|
||||||
|
|
||||||
BOOL_VAR(wordrec_display_splits, 0, "Display splits");
|
BOOL_VAR(wordrec_display_splits, 0, "Display splits");
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
// Returns the bounding box of all the points in the split.
|
||||||
F u n c t i o n s
|
TBOX SPLIT::bounding_box() const {
|
||||||
----------------------------------------------------------------------*/
|
return TBOX(
|
||||||
|
MIN(point1->pos.x, point2->pos.x), MIN(point1->pos.y, point2->pos.y),
|
||||||
/**********************************************************************
|
MAX(point1->pos.x, point2->pos.x), MAX(point1->pos.y, point2->pos.y));
|
||||||
* delete_split
|
|
||||||
*
|
|
||||||
* Remove this split from existence.
|
|
||||||
**********************************************************************/
|
|
||||||
void delete_split(SPLIT *split) {
|
|
||||||
if (split) {
|
|
||||||
delete split;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hides the SPLIT so the outlines appear not to be cut by it.
|
||||||
|
void SPLIT::Hide() const {
|
||||||
|
EDGEPT* edgept = point1;
|
||||||
|
do {
|
||||||
|
edgept->Hide();
|
||||||
|
edgept = edgept->next;
|
||||||
|
} while (!edgept->EqualPos(*point2) && edgept != point1);
|
||||||
|
edgept = point2;
|
||||||
|
do {
|
||||||
|
edgept->Hide();
|
||||||
|
edgept = edgept->next;
|
||||||
|
} while (!edgept->EqualPos(*point1) && edgept != point2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undoes hide, so the outlines are cut by the SPLIT.
|
||||||
|
void SPLIT::Reveal() const {
|
||||||
|
EDGEPT* edgept = point1;
|
||||||
|
do {
|
||||||
|
edgept->Reveal();
|
||||||
|
edgept = edgept->next;
|
||||||
|
} while (!edgept->EqualPos(*point2) && edgept != point1);
|
||||||
|
edgept = point2;
|
||||||
|
do {
|
||||||
|
edgept->Reveal();
|
||||||
|
edgept = edgept->next;
|
||||||
|
} while (!edgept->EqualPos(*point1) && edgept != point2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute a split priority based on the bounding boxes of the parts.
|
||||||
|
// The arguments here are config parameters defined in Wordrec. Add chop_
|
||||||
|
// to the beginning of the name.
|
||||||
|
float SPLIT::FullPriority(int xmin, int xmax, double overlap_knob,
|
||||||
|
int centered_maxwidth, double center_knob,
|
||||||
|
double width_change_knob) const {
|
||||||
|
TBOX box1 = Box12();
|
||||||
|
TBOX box2 = Box21();
|
||||||
|
int min_left = MIN(box1.left(), box2.left());
|
||||||
|
int max_right = MAX(box1.right(), box2.right());
|
||||||
|
if (xmin < min_left && xmax > max_right) return kBadPriority;
|
||||||
|
|
||||||
|
float grade = 0.0f;
|
||||||
|
// grade_overlap.
|
||||||
|
int width1 = box1.width();
|
||||||
|
int width2 = box2.width();
|
||||||
|
int min_width = MIN(width1, width2);
|
||||||
|
int overlap = -box1.x_gap(box2);
|
||||||
|
if (overlap == min_width) {
|
||||||
|
grade += 100.0f; // Total overlap.
|
||||||
|
} else {
|
||||||
|
if (2 * overlap > min_width) overlap += 2 * overlap - min_width;
|
||||||
|
if (overlap > 0) grade += overlap_knob * overlap;
|
||||||
|
}
|
||||||
|
// grade_center_of_blob.
|
||||||
|
if (width1 <= centered_maxwidth || width2 <= centered_maxwidth) {
|
||||||
|
grade += MIN(kCenterGradeCap, center_knob * abs(width1 - width2));
|
||||||
|
}
|
||||||
|
// grade_width_change.
|
||||||
|
float width_change_grade = 20 - (max_right - min_left - MAX(width1, width2));
|
||||||
|
if (width_change_grade > 0.0f)
|
||||||
|
grade += width_change_grade * width_change_knob;
|
||||||
|
return grade;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if *this SPLIT appears OK in the sense that it does not cross
|
||||||
|
// any outlines and does not chop off any ridiculously small pieces.
|
||||||
|
bool SPLIT::IsHealthy(const TBLOB& blob, int min_points, int min_area) const {
|
||||||
|
return !IsLittleChunk(min_points, min_area) &&
|
||||||
|
!blob.SegmentCrossesOutline(point1->pos, point2->pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the split generates a small chunk in terms of either area
|
||||||
|
// or number of points.
|
||||||
|
bool SPLIT::IsLittleChunk(int min_points, int min_area) const {
|
||||||
|
if (point1->ShortNonCircularSegment(min_points, point2) &&
|
||||||
|
point1->SegmentArea(point2) < min_area) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (point2->ShortNonCircularSegment(min_points, point1) &&
|
||||||
|
point2->SegmentArea(point1) < min_area) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* make_edgept
|
* make_edgept
|
||||||
@ -135,102 +215,113 @@ void remove_edgept(EDGEPT *point) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* new_split
|
* Print
|
||||||
*
|
*
|
||||||
* Create a new split record and initialize it. Put it on the display
|
* Shows the coordinates of both points in a split.
|
||||||
* list.
|
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
SPLIT *new_split(EDGEPT *point1, EDGEPT *point2) {
|
void SPLIT::Print() const {
|
||||||
SPLIT *s = new SPLIT;
|
if (this != NULL) {
|
||||||
s->point1 = point1;
|
tprintf("(%d,%d)--(%d,%d)", point1->pos.x, point1->pos.y, point2->pos.x,
|
||||||
s->point2 = point2;
|
point2->pos.y);
|
||||||
return (s);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* print_split
|
|
||||||
*
|
|
||||||
* Print a list of splits. Show the coordinates of both points in
|
|
||||||
* each split.
|
|
||||||
**********************************************************************/
|
|
||||||
void print_split(SPLIT *split) {
|
|
||||||
if (split) {
|
|
||||||
tprintf("(%d,%d)--(%d,%d)",
|
|
||||||
split->point1->pos.x, split->point1->pos.y,
|
|
||||||
split->point2->pos.x, split->point2->pos.y);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef GRAPHICS_DISABLED
|
||||||
|
// Draws the split in the given window.
|
||||||
|
void SPLIT::Mark(ScrollView* window) const {
|
||||||
|
window->Pen(ScrollView::GREEN);
|
||||||
|
window->Line(point1->pos.x, point1->pos.y, point2->pos.x, point2->pos.y);
|
||||||
|
window->UpdateWindow();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/**********************************************************************
|
// Creates two outlines out of one by splitting the original one in half.
|
||||||
* split_outline
|
// Inserts the resulting outlines into the given list.
|
||||||
*
|
void SPLIT::SplitOutlineList(TESSLINE* outlines) const {
|
||||||
* Split between these two edge points.
|
SplitOutline();
|
||||||
**********************************************************************/
|
while (outlines->next != NULL) outlines = outlines->next;
|
||||||
void split_outline(EDGEPT *join_point1, EDGEPT *join_point2) {
|
|
||||||
assert(join_point1 != join_point2);
|
|
||||||
|
|
||||||
EDGEPT* temp2 = join_point2->next;
|
outlines->next = new TESSLINE;
|
||||||
EDGEPT* temp1 = join_point1->next;
|
outlines->next->loop = point1;
|
||||||
/* Create two new points */
|
outlines->next->ComputeBoundingBox();
|
||||||
EDGEPT* new_point1 = make_edgept(join_point1->pos.x, join_point1->pos.y,
|
|
||||||
temp1, join_point2);
|
outlines = outlines->next;
|
||||||
EDGEPT* new_point2 = make_edgept(join_point2->pos.x, join_point2->pos.y,
|
|
||||||
temp2, join_point1);
|
outlines->next = new TESSLINE;
|
||||||
// Join_point1 and 2 are now cross-over points, so they must have NULL
|
outlines->next->loop = point2;
|
||||||
// src_outlines and give their src_outline information their new
|
outlines->next->ComputeBoundingBox();
|
||||||
// replacements.
|
|
||||||
new_point1->src_outline = join_point1->src_outline;
|
outlines->next->next = NULL;
|
||||||
new_point1->start_step = join_point1->start_step;
|
|
||||||
new_point1->step_count = join_point1->step_count;
|
|
||||||
new_point2->src_outline = join_point2->src_outline;
|
|
||||||
new_point2->start_step = join_point2->start_step;
|
|
||||||
new_point2->step_count = join_point2->step_count;
|
|
||||||
join_point1->src_outline = NULL;
|
|
||||||
join_point1->start_step = 0;
|
|
||||||
join_point1->step_count = 0;
|
|
||||||
join_point2->src_outline = NULL;
|
|
||||||
join_point2->start_step = 0;
|
|
||||||
join_point2->step_count = 0;
|
|
||||||
join_point1->MarkChop();
|
|
||||||
join_point2->MarkChop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Makes a split between these two edge points, but does not affect the
|
||||||
|
// outlines to which they belong.
|
||||||
|
void SPLIT::SplitOutline() const {
|
||||||
|
EDGEPT* temp2 = point2->next;
|
||||||
|
EDGEPT* temp1 = point1->next;
|
||||||
|
/* Create two new points */
|
||||||
|
EDGEPT* new_point1 = make_edgept(point1->pos.x, point1->pos.y, temp1, point2);
|
||||||
|
EDGEPT* new_point2 = make_edgept(point2->pos.x, point2->pos.y, temp2, point1);
|
||||||
|
// point1 and 2 are now cross-over points, so they must have NULL
|
||||||
|
// src_outlines and give their src_outline information their new
|
||||||
|
// replacements.
|
||||||
|
new_point1->src_outline = point1->src_outline;
|
||||||
|
new_point1->start_step = point1->start_step;
|
||||||
|
new_point1->step_count = point1->step_count;
|
||||||
|
new_point2->src_outline = point2->src_outline;
|
||||||
|
new_point2->start_step = point2->start_step;
|
||||||
|
new_point2->step_count = point2->step_count;
|
||||||
|
point1->src_outline = NULL;
|
||||||
|
point1->start_step = 0;
|
||||||
|
point1->step_count = 0;
|
||||||
|
point2->src_outline = NULL;
|
||||||
|
point2->start_step = 0;
|
||||||
|
point2->step_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
// Undoes the effect of SplitOutlineList, correcting the outlines for undoing
|
||||||
* unsplit_outlines
|
// the split, but possibly leaving some duplicate outlines.
|
||||||
*
|
void SPLIT::UnsplitOutlineList(TBLOB* blob) const {
|
||||||
* Remove the split that was put between these two points.
|
/* Modify edge points */
|
||||||
**********************************************************************/
|
UnsplitOutlines();
|
||||||
void unsplit_outlines(EDGEPT *p1, EDGEPT *p2) {
|
|
||||||
EDGEPT *tmp1 = p1->next;
|
|
||||||
EDGEPT *tmp2 = p2->next;
|
|
||||||
|
|
||||||
assert (p1 != p2);
|
TESSLINE* outline1 = new TESSLINE;
|
||||||
|
outline1->next = blob->outlines;
|
||||||
|
blob->outlines = outline1;
|
||||||
|
outline1->loop = point1;
|
||||||
|
|
||||||
tmp1->next->prev = p2;
|
TESSLINE* outline2 = new TESSLINE;
|
||||||
tmp2->next->prev = p1;
|
outline2->next = blob->outlines;
|
||||||
|
blob->outlines = outline2;
|
||||||
|
outline2->loop = point2;
|
||||||
|
}
|
||||||
|
|
||||||
// tmp2 is coincident with p1. p1 takes tmp2's place as tmp2 is deleted.
|
// Removes the split that was put between these two points.
|
||||||
p1->next = tmp2->next;
|
void SPLIT::UnsplitOutlines() const {
|
||||||
p1->src_outline = tmp2->src_outline;
|
EDGEPT* tmp1 = point1->next;
|
||||||
p1->start_step = tmp2->start_step;
|
EDGEPT* tmp2 = point2->next;
|
||||||
p1->step_count = tmp2->step_count;
|
|
||||||
// Likewise p2 takes tmp1's place.
|
tmp1->next->prev = point2;
|
||||||
p2->next = tmp1->next;
|
tmp2->next->prev = point1;
|
||||||
p2->src_outline = tmp1->src_outline;
|
|
||||||
p2->start_step = tmp1->start_step;
|
// tmp2 is coincident with point1. point1 takes tmp2's place as tmp2 is
|
||||||
p2->step_count = tmp1->step_count;
|
// deleted.
|
||||||
p1->UnmarkChop();
|
point1->next = tmp2->next;
|
||||||
p2->UnmarkChop();
|
point1->src_outline = tmp2->src_outline;
|
||||||
|
point1->start_step = tmp2->start_step;
|
||||||
|
point1->step_count = tmp2->step_count;
|
||||||
|
// Likewise point2 takes tmp1's place.
|
||||||
|
point2->next = tmp1->next;
|
||||||
|
point2->src_outline = tmp1->src_outline;
|
||||||
|
point2->start_step = tmp1->start_step;
|
||||||
|
point2->step_count = tmp1->step_count;
|
||||||
|
|
||||||
delete tmp1;
|
delete tmp1;
|
||||||
delete tmp2;
|
delete tmp2;
|
||||||
|
|
||||||
p1->vec.x = p1->next->pos.x - p1->pos.x;
|
point1->vec.x = point1->next->pos.x - point1->pos.x;
|
||||||
p1->vec.y = p1->next->pos.y - p1->pos.y;
|
point1->vec.y = point1->next->pos.y - point1->pos.y;
|
||||||
|
|
||||||
p2->vec.x = p2->next->pos.x - p2->pos.x;
|
point2->vec.x = point2->next->pos.x - point2->pos.x;
|
||||||
p2->vec.y = p2->next->pos.y - p2->pos.y;
|
point2->vec.y = point2->next->pos.y - point2->pos.y;
|
||||||
}
|
}
|
||||||
|
101
ccstruct/split.h
101
ccstruct/split.h
@ -29,18 +29,80 @@
|
|||||||
I n c l u d e s
|
I n c l u d e s
|
||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
#include "blobs.h"
|
#include "blobs.h"
|
||||||
#include "oldlist.h"
|
#include "scrollview.h"
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
T y p e s
|
T y p e s
|
||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
typedef struct split_record
|
struct SPLIT {
|
||||||
{ /* SPLIT */
|
SPLIT() : point1(NULL), point2(NULL) {}
|
||||||
|
SPLIT(EDGEPT* pt1, EDGEPT* pt2) : point1(pt1), point2(pt2) {}
|
||||||
|
|
||||||
|
// Returns the bounding box of all the points in the split.
|
||||||
|
TBOX bounding_box() const;
|
||||||
|
// Returns the bounding box of the outline from point1 to point2.
|
||||||
|
TBOX Box12() const { return point1->SegmentBox(point2); }
|
||||||
|
// Returns the bounding box of the outline from point1 to point1.
|
||||||
|
TBOX Box21() const { return point2->SegmentBox(point1); }
|
||||||
|
// Returns the bounding box of the out
|
||||||
|
|
||||||
|
// Hides the SPLIT so the outlines appear not to be cut by it.
|
||||||
|
void Hide() const;
|
||||||
|
// Undoes hide, so the outlines are cut by the SPLIT.
|
||||||
|
void Reveal() const;
|
||||||
|
|
||||||
|
// Returns true if the given EDGEPT is used by this SPLIT, checking only
|
||||||
|
// the EDGEPT pointer, not the coordinates.
|
||||||
|
bool UsesPoint(const EDGEPT* point) const {
|
||||||
|
return point1 == point || point2 == point;
|
||||||
|
}
|
||||||
|
// Returns true if the other SPLIT has any position shared with *this.
|
||||||
|
bool SharesPosition(const SPLIT& other) const {
|
||||||
|
return point1->EqualPos(*other.point1) || point1->EqualPos(*other.point2) ||
|
||||||
|
point2->EqualPos(*other.point1) || point2->EqualPos(*other.point2);
|
||||||
|
}
|
||||||
|
// Returns true if both points are contained within the blob.
|
||||||
|
bool ContainedByBlob(const TBLOB& blob) const {
|
||||||
|
return blob.Contains(point1->pos) && blob.Contains(point2->pos);
|
||||||
|
}
|
||||||
|
// Returns true if both points are contained within the outline.
|
||||||
|
bool ContainedByOutline(const TESSLINE& outline) const {
|
||||||
|
return outline.Contains(point1->pos) && outline.Contains(point2->pos);
|
||||||
|
}
|
||||||
|
// Compute a split priority based on the bounding boxes of the parts.
|
||||||
|
// The arguments here are config parameters defined in Wordrec. Add chop_
|
||||||
|
// to the beginning of the name.
|
||||||
|
float FullPriority(int xmin, int xmax, double overlap_knob,
|
||||||
|
int centered_maxwidth, double center_knob,
|
||||||
|
double width_change_knob) const;
|
||||||
|
// Returns true if *this SPLIT appears OK in the sense that it does not cross
|
||||||
|
// any outlines and does not chop off any ridiculously small pieces.
|
||||||
|
bool IsHealthy(const TBLOB& blob, int min_points, int min_area) const;
|
||||||
|
// Returns true if the split generates a small chunk in terms of either area
|
||||||
|
// or number of points.
|
||||||
|
bool IsLittleChunk(int min_points, int min_area) const;
|
||||||
|
|
||||||
|
void Print() const;
|
||||||
|
#ifndef GRAPHICS_DISABLED
|
||||||
|
// Draws the split in the given window.
|
||||||
|
void Mark(ScrollView* window) const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Creates two outlines out of one by splitting the original one in half.
|
||||||
|
// Inserts the resulting outlines into the given list.
|
||||||
|
void SplitOutlineList(TESSLINE* outlines) const;
|
||||||
|
// Makes a split between these two edge points, but does not affect the
|
||||||
|
// outlines to which they belong.
|
||||||
|
void SplitOutline() const;
|
||||||
|
// Undoes the effect of SplitOutlineList, correcting the outlines for undoing
|
||||||
|
// the split, but possibly leaving some duplicate outlines.
|
||||||
|
void UnsplitOutlineList(TBLOB* blob) const;
|
||||||
|
// Removes the split that was put between these two points.
|
||||||
|
void UnsplitOutlines() const;
|
||||||
|
|
||||||
EDGEPT *point1;
|
EDGEPT *point1;
|
||||||
EDGEPT *point2;
|
EDGEPT *point2;
|
||||||
} SPLIT;
|
};
|
||||||
|
|
||||||
typedef LIST SPLITS; /* SPLITS */
|
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
V a r i a b l e s
|
V a r i a b l e s
|
||||||
@ -48,38 +110,11 @@ typedef LIST SPLITS; /* SPLITS */
|
|||||||
|
|
||||||
extern BOOL_VAR_H(wordrec_display_splits, 0, "Display splits");
|
extern BOOL_VAR_H(wordrec_display_splits, 0, "Display splits");
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
|
||||||
M a c r o s
|
|
||||||
----------------------------------------------------------------------*/
|
|
||||||
/**********************************************************************
|
|
||||||
* clone_split
|
|
||||||
*
|
|
||||||
* Create a new split record and set the contents equal to the contents
|
|
||||||
* of this record.
|
|
||||||
**********************************************************************/
|
|
||||||
|
|
||||||
#define clone_split(dest,source) \
|
|
||||||
if (source) \
|
|
||||||
(dest) = new_split ((source)->point1, (source)->point2); \
|
|
||||||
else \
|
|
||||||
(dest) = (SPLIT*) NULL \
|
|
||||||
|
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
F u n c t i o n s
|
F u n c t i o n s
|
||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
void delete_split(SPLIT *split);
|
|
||||||
|
|
||||||
EDGEPT *make_edgept(int x, int y, EDGEPT *next, EDGEPT *prev);
|
EDGEPT *make_edgept(int x, int y, EDGEPT *next, EDGEPT *prev);
|
||||||
|
|
||||||
void remove_edgept(EDGEPT *point);
|
void remove_edgept(EDGEPT *point);
|
||||||
|
|
||||||
SPLIT *new_split(EDGEPT *point1, EDGEPT *point2);
|
|
||||||
|
|
||||||
void print_split(SPLIT *split);
|
|
||||||
|
|
||||||
void split_outline(EDGEPT *join_point1, EDGEPT *join_point2);
|
|
||||||
|
|
||||||
void unsplit_outlines(EDGEPT *p1, EDGEPT *p2);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
I n c l u d e s
|
I n c l u d e s
|
||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
#include "vecfuncs.h"
|
#include "vecfuncs.h"
|
||||||
|
#include "blobs.h"
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
F u n c t i o n s
|
F u n c t i o n s
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
#define VECFUNCS_H
|
#define VECFUNCS_H
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include "blobs.h"
|
|
||||||
|
|
||||||
struct EDGEPT;
|
struct EDGEPT;
|
||||||
|
|
||||||
|
@ -160,23 +160,37 @@ WERD* WERD::ConstructFromSingleBlob(bool bol, bool eol, C_BLOB* blob) {
|
|||||||
* row being marked as FUZZY space.
|
* row being marked as FUZZY space.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
TBOX WERD::bounding_box() {
|
TBOX WERD::bounding_box() const { return restricted_bounding_box(true, true); }
|
||||||
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();
|
// Returns the bounding box including the desired combination of upper and
|
||||||
rej_cblob_it.forward()) {
|
// lower noise/diacritic elements.
|
||||||
box += rej_cblob_it.data()->bounding_box();
|
TBOX WERD::restricted_bounding_box(bool upper_dots, bool lower_dots) const {
|
||||||
|
TBOX box = true_bounding_box();
|
||||||
|
int bottom = box.bottom();
|
||||||
|
int top = box.top();
|
||||||
|
// This is a read-only iteration of the rejected blobs.
|
||||||
|
C_BLOB_IT it(const_cast<C_BLOB_LIST*>(&rej_cblobs));
|
||||||
|
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
|
||||||
|
TBOX dot_box = it.data()->bounding_box();
|
||||||
|
if ((upper_dots || dot_box.bottom() <= top) &&
|
||||||
|
(lower_dots || dot_box.top() >= bottom)) {
|
||||||
|
box += dot_box;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
C_BLOB_IT it = &cblobs; // blobs of WERD
|
// Returns the bounding box of only the good blobs.
|
||||||
|
TBOX WERD::true_bounding_box() const {
|
||||||
|
TBOX box; // box being built
|
||||||
|
// This is a read-only iteration of the good blobs.
|
||||||
|
C_BLOB_IT it(const_cast<C_BLOB_LIST*>(&cblobs));
|
||||||
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
|
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
|
||||||
box += it.data()->bounding_box();
|
box += it.data()->bounding_box();
|
||||||
}
|
}
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WERD::move
|
* WERD::move
|
||||||
*
|
*
|
||||||
@ -489,3 +503,101 @@ WERD* WERD::ConstructWerdWithNewBlobs(C_BLOB_LIST* all_blobs,
|
|||||||
}
|
}
|
||||||
return new_werd;
|
return new_werd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes noise from the word by moving small outlines to the rej_cblobs
|
||||||
|
// list, based on the size_threshold.
|
||||||
|
void WERD::CleanNoise(float size_threshold) {
|
||||||
|
C_BLOB_IT blob_it(&cblobs);
|
||||||
|
C_BLOB_IT rej_it(&rej_cblobs);
|
||||||
|
for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
|
||||||
|
C_BLOB* blob = blob_it.data();
|
||||||
|
C_OUTLINE_IT ol_it(blob->out_list());
|
||||||
|
for (ol_it.mark_cycle_pt(); !ol_it.cycled_list(); ol_it.forward()) {
|
||||||
|
C_OUTLINE* outline = ol_it.data();
|
||||||
|
TBOX ol_box = outline->bounding_box();
|
||||||
|
int ol_size =
|
||||||
|
ol_box.width() > ol_box.height() ? ol_box.width() : ol_box.height();
|
||||||
|
if (ol_size < size_threshold) {
|
||||||
|
// This outline is too small. Move it to a separate blob in the
|
||||||
|
// reject blobs list.
|
||||||
|
C_BLOB* rej_blob = new C_BLOB(ol_it.extract());
|
||||||
|
rej_it.add_after_then_move(rej_blob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blob->out_list()->empty()) delete blob_it.extract();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracts all the noise outlines and stuffs the pointers into the given
|
||||||
|
// vector of outlines. Afterwards, the outlines vector owns the pointers.
|
||||||
|
void WERD::GetNoiseOutlines(GenericVector<C_OUTLINE*>* outlines) {
|
||||||
|
C_BLOB_IT rej_it(&rej_cblobs);
|
||||||
|
for (rej_it.mark_cycle_pt(); !rej_it.empty(); rej_it.forward()) {
|
||||||
|
C_BLOB* blob = rej_it.extract();
|
||||||
|
C_OUTLINE_IT ol_it(blob->out_list());
|
||||||
|
outlines->push_back(ol_it.extract());
|
||||||
|
delete blob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds the selected outlines to the indcated real blobs, and puts the rest
|
||||||
|
// back in rej_cblobs where they came from. Where the target_blobs entry is
|
||||||
|
// NULL, a run of wanted outlines is put into a single new blob.
|
||||||
|
// Ownership of the outlines is transferred back to the word. (Hence
|
||||||
|
// GenericVector and not PointerVector.)
|
||||||
|
// Returns true if any new blob was added to the start of the word, which
|
||||||
|
// suggests that it might need joining to the word before it, and likewise
|
||||||
|
// sets make_next_word_fuzzy true if any new blob was added to the end.
|
||||||
|
bool WERD::AddSelectedOutlines(const GenericVector<bool>& wanted,
|
||||||
|
const GenericVector<C_BLOB*>& target_blobs,
|
||||||
|
const GenericVector<C_OUTLINE*>& outlines,
|
||||||
|
bool* make_next_word_fuzzy) {
|
||||||
|
bool outline_added_to_start = false;
|
||||||
|
if (make_next_word_fuzzy != NULL) *make_next_word_fuzzy = false;
|
||||||
|
C_BLOB_IT rej_it(&rej_cblobs);
|
||||||
|
for (int i = 0; i < outlines.size(); ++i) {
|
||||||
|
C_OUTLINE* outline = outlines[i];
|
||||||
|
if (outline == NULL) continue; // Already used it.
|
||||||
|
if (wanted[i]) {
|
||||||
|
C_BLOB* target_blob = target_blobs[i];
|
||||||
|
TBOX noise_box = outline->bounding_box();
|
||||||
|
if (target_blob == NULL) {
|
||||||
|
target_blob = new C_BLOB(outline);
|
||||||
|
// Need to find the insertion point.
|
||||||
|
C_BLOB_IT blob_it(&cblobs);
|
||||||
|
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.left() > noise_box.left()) {
|
||||||
|
if (blob_it.at_first() && !flag(W_FUZZY_SP) && !flag(W_FUZZY_NON)) {
|
||||||
|
// We might want to join this word to its predecessor.
|
||||||
|
outline_added_to_start = true;
|
||||||
|
}
|
||||||
|
blob_it.add_before_stay_put(target_blob);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blob_it.cycled_list()) {
|
||||||
|
blob_it.add_to_end(target_blob);
|
||||||
|
if (make_next_word_fuzzy != NULL) *make_next_word_fuzzy = true;
|
||||||
|
}
|
||||||
|
// Add all consecutive wanted, but null-blob outlines to same blob.
|
||||||
|
C_OUTLINE_IT ol_it(target_blob->out_list());
|
||||||
|
while (i + 1 < outlines.size() && wanted[i + 1] &&
|
||||||
|
target_blobs[i + 1] == NULL) {
|
||||||
|
++i;
|
||||||
|
ol_it.add_to_end(outlines[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Insert outline into this blob.
|
||||||
|
C_OUTLINE_IT ol_it(target_blob->out_list());
|
||||||
|
ol_it.add_to_end(outline);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Put back on noise list.
|
||||||
|
rej_it.add_to_end(new C_BLOB(outline));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outline_added_to_start;
|
||||||
|
}
|
||||||
|
@ -114,7 +114,13 @@ class WERD : public ELIST2_LINK {
|
|||||||
script_id_ = id;
|
script_id_ = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
TBOX bounding_box(); // compute bounding box
|
// Returns the (default) bounding box including all the dots.
|
||||||
|
TBOX bounding_box() const; // compute bounding box
|
||||||
|
// Returns the bounding box including the desired combination of upper and
|
||||||
|
// lower noise/diacritic elements.
|
||||||
|
TBOX restricted_bounding_box(bool upper_dots, bool lower_dots) const;
|
||||||
|
// Returns the bounding box of only the good blobs.
|
||||||
|
TBOX true_bounding_box() const;
|
||||||
|
|
||||||
const char *text() const { return correct.string(); }
|
const char *text() const { return correct.string(); }
|
||||||
void set_text(const char *new_text) { correct = new_text; }
|
void set_text(const char *new_text) { correct = new_text; }
|
||||||
@ -155,6 +161,26 @@ class WERD : public ELIST2_LINK {
|
|||||||
void plot_rej_blobs(ScrollView *window);
|
void plot_rej_blobs(ScrollView *window);
|
||||||
#endif // GRAPHICS_DISABLED
|
#endif // GRAPHICS_DISABLED
|
||||||
|
|
||||||
|
// Removes noise from the word by moving small outlines to the rej_cblobs
|
||||||
|
// list, based on the size_threshold.
|
||||||
|
void CleanNoise(float size_threshold);
|
||||||
|
|
||||||
|
// Extracts all the noise outlines and stuffs the pointers into the given
|
||||||
|
// vector of outlines. Afterwards, the outlines vector owns the pointers.
|
||||||
|
void GetNoiseOutlines(GenericVector<C_OUTLINE *> *outlines);
|
||||||
|
// Adds the selected outlines to the indcated real blobs, and puts the rest
|
||||||
|
// back in rej_cblobs where they came from. Where the target_blobs entry is
|
||||||
|
// NULL, a run of wanted outlines is put into a single new blob.
|
||||||
|
// Ownership of the outlines is transferred back to the word. (Hence
|
||||||
|
// GenericVector and not PointerVector.)
|
||||||
|
// Returns true if any new blob was added to the start of the word, which
|
||||||
|
// suggests that it might need joining to the word before it, and likewise
|
||||||
|
// sets make_next_word_fuzzy true if any new blob was added to the end.
|
||||||
|
bool AddSelectedOutlines(const GenericVector<bool> &wanted,
|
||||||
|
const GenericVector<C_BLOB *> &target_blobs,
|
||||||
|
const GenericVector<C_OUTLINE *> &outlines,
|
||||||
|
bool *make_next_word_fuzzy);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uinT8 blanks; // no of blanks
|
uinT8 blanks; // no of blanks
|
||||||
uinT8 dummy; // padding
|
uinT8 dummy; // padding
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
AUTOMAKE_OPTIONS = subdir-objects
|
||||||
SUBDIRS =
|
SUBDIRS =
|
||||||
AM_CXXFLAGS =
|
AM_CXXFLAGS =
|
||||||
|
|
||||||
@ -40,8 +41,7 @@ libtesseract_ccutil_la_SOURCES = \
|
|||||||
unichar.cpp unicharmap.cpp unicharset.cpp unicodes.cpp \
|
unichar.cpp unicharmap.cpp unicharset.cpp unicodes.cpp \
|
||||||
params.cpp universalambigs.cpp
|
params.cpp universalambigs.cpp
|
||||||
|
|
||||||
|
if T_WIN
|
||||||
if MINGW
|
|
||||||
AM_CPPFLAGS += -I$(top_srcdir)/vs2008/port -DWINDLLNAME=\"lib@GENERIC_LIBRARY_NAME@\"
|
AM_CPPFLAGS += -I$(top_srcdir)/vs2008/port -DWINDLLNAME=\"lib@GENERIC_LIBRARY_NAME@\"
|
||||||
noinst_HEADERS += ../vs2010/port/strtok_r.h
|
noinst_HEADERS += ../vs2010/port/strtok_r.h
|
||||||
libtesseract_ccutil_la_SOURCES += ../vs2010/port/strtok_r.cpp
|
libtesseract_ccutil_la_SOURCES += ../vs2010/port/strtok_r.cpp
|
||||||
|
@ -24,13 +24,13 @@
|
|||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
#include "universalambigs.h"
|
#include "universalambigs.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if defined _WIN32 || defined(__CYGWIN__)
|
||||||
#ifndef __GNUC__
|
#ifndef __GNUC__
|
||||||
#define strtok_r strtok_s
|
#define strtok_r strtok_s
|
||||||
#else
|
#else
|
||||||
#include "strtok_r.h"
|
#include "strtok_r.h"
|
||||||
#endif /* __GNUC__ */
|
#endif /* __GNUC__ */
|
||||||
#endif /* _WIN32 */
|
#endif /* _WIN32 __CYGWIN__*/
|
||||||
|
|
||||||
namespace tesseract {
|
namespace tesseract {
|
||||||
|
|
||||||
|
@ -445,8 +445,10 @@ class PointerVector : public GenericVector<T*> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PointerVector<T>& operator=(const PointerVector& other) {
|
PointerVector<T>& operator=(const PointerVector& other) {
|
||||||
this->truncate(0);
|
if (&other != this) {
|
||||||
this->operator+=(other);
|
this->truncate(0);
|
||||||
|
this->operator+=(other);
|
||||||
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -777,8 +779,10 @@ GenericVector<T> &GenericVector<T>::operator+=(const GenericVector& other) {
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
GenericVector<T> &GenericVector<T>::operator=(const GenericVector& other) {
|
GenericVector<T> &GenericVector<T>::operator=(const GenericVector& other) {
|
||||||
this->truncate(0);
|
if (&other != this) {
|
||||||
this->operator+=(other);
|
this->truncate(0);
|
||||||
|
this->operator+=(other);
|
||||||
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,10 +28,12 @@
|
|||||||
#define ultoa _ultoa
|
#define ultoa _ultoa
|
||||||
#endif /* __GNUC__ */
|
#endif /* __GNUC__ */
|
||||||
#define SIGNED
|
#define SIGNED
|
||||||
|
#if defined(_MSC_VER)
|
||||||
#define snprintf _snprintf
|
#define snprintf _snprintf
|
||||||
#if (_MSC_VER <= 1400)
|
#if (_MSC_VER <= 1400)
|
||||||
#define vsnprintf _vsnprintf
|
#define vsnprintf _vsnprintf
|
||||||
#endif /* _WIN32 */
|
#endif /* (_MSC_VER <= 1400) */
|
||||||
|
#endif /* defined(_MSC_VER) */
|
||||||
#else
|
#else
|
||||||
#define __UNIX__
|
#define __UNIX__
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
#include "tprintf.h"
|
#include "tprintf.h"
|
||||||
|
|
||||||
// workaround for "'off_t' was not declared in this scope" with -std=c++11
|
// workaround for "'off_t' was not declared in this scope" with -std=c++11
|
||||||
#if !defined(off_t) && !defined(__APPLE__)
|
#if !defined(off_t) && !defined(__APPLE__) && !defined(__CYGWIN__)
|
||||||
typedef long off_t;
|
typedef long off_t;
|
||||||
#endif // off_t
|
#endif // off_t
|
||||||
|
|
||||||
|
@ -61,9 +61,11 @@ bool TFile::Open(FILE* fp, inT64 end_offset) {
|
|||||||
offset_ = 0;
|
offset_ = 0;
|
||||||
inT64 current_pos = ftell(fp);
|
inT64 current_pos = ftell(fp);
|
||||||
if (end_offset < 0) {
|
if (end_offset < 0) {
|
||||||
fseek(fp, 0, SEEK_END);
|
if (fseek(fp, 0, SEEK_END))
|
||||||
|
return false;
|
||||||
end_offset = ftell(fp);
|
end_offset = ftell(fp);
|
||||||
fseek(fp, current_pos, SEEK_SET);
|
if (fseek(fp, current_pos, SEEK_SET))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
int size = end_offset - current_pos;
|
int size = end_offset - current_pos;
|
||||||
is_writing_ = false;
|
is_writing_ = false;
|
||||||
|
@ -95,21 +95,30 @@ void TessdataManager::CopyFile(FILE *input_file, FILE *output_file,
|
|||||||
delete[] chunk;
|
delete[] chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TessdataManager::WriteMetadata(inT64 *offset_table,
|
bool TessdataManager::WriteMetadata(inT64 *offset_table,
|
||||||
const char * language_data_path_prefix,
|
const char * language_data_path_prefix,
|
||||||
FILE *output_file) {
|
FILE *output_file) {
|
||||||
fseek(output_file, 0, SEEK_SET);
|
|
||||||
inT32 num_entries = TESSDATA_NUM_ENTRIES;
|
inT32 num_entries = TESSDATA_NUM_ENTRIES;
|
||||||
fwrite(&num_entries, sizeof(inT32), 1, output_file);
|
bool result = true;
|
||||||
fwrite(offset_table, sizeof(inT64), TESSDATA_NUM_ENTRIES, output_file);
|
if (fseek(output_file, 0, SEEK_SET) != 0 ||
|
||||||
fclose(output_file);
|
fwrite(&num_entries, sizeof(inT32), 1, output_file) != 1 ||
|
||||||
|
fwrite(offset_table, sizeof(inT64), TESSDATA_NUM_ENTRIES,
|
||||||
tprintf("TessdataManager combined tesseract data files.\n");
|
output_file) != TESSDATA_NUM_ENTRIES) {
|
||||||
for (int i = 0; i < TESSDATA_NUM_ENTRIES; ++i) {
|
fclose(output_file);
|
||||||
tprintf("Offset for type %2d (%s%-22s) is %lld\n", i,
|
result = false;
|
||||||
language_data_path_prefix, kTessdataFileSuffixes[i],
|
tprintf("WriteMetadata failed in TessdataManager!\n");
|
||||||
offset_table[i]);
|
} else if (fclose(output_file)) {
|
||||||
|
result = false;
|
||||||
|
tprintf("WriteMetadata failed to close file!\n");
|
||||||
|
} else {
|
||||||
|
tprintf("TessdataManager combined tesseract data files.\n");
|
||||||
|
for (int i = 0; i < TESSDATA_NUM_ENTRIES; ++i) {
|
||||||
|
tprintf("Offset for type %2d (%s%-22s) is %lld\n", i,
|
||||||
|
language_data_path_prefix, kTessdataFileSuffixes[i],
|
||||||
|
offset_table[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TessdataManager::CombineDataFiles(
|
bool TessdataManager::CombineDataFiles(
|
||||||
@ -124,8 +133,11 @@ bool TessdataManager::CombineDataFiles(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Leave some space for recording the offset_table.
|
// Leave some space for recording the offset_table.
|
||||||
fseek(output_file,
|
if (fseek(output_file,
|
||||||
sizeof(inT32) + sizeof(inT64) * TESSDATA_NUM_ENTRIES, SEEK_SET);
|
sizeof(inT32) + sizeof(inT64) * TESSDATA_NUM_ENTRIES, SEEK_SET)) {
|
||||||
|
tprintf("Error seeking %s\n", output_filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
TessdataType type = TESSDATA_NUM_ENTRIES;
|
TessdataType type = TESSDATA_NUM_ENTRIES;
|
||||||
bool text_file = false;
|
bool text_file = false;
|
||||||
@ -161,8 +173,7 @@ bool TessdataManager::CombineDataFiles(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteMetadata(offset_table, language_data_path_prefix, output_file);
|
return WriteMetadata(offset_table, language_data_path_prefix, output_file);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TessdataManager::OverwriteComponents(
|
bool TessdataManager::OverwriteComponents(
|
||||||
@ -185,8 +196,12 @@ bool TessdataManager::OverwriteComponents(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Leave some space for recording the offset_table.
|
// Leave some space for recording the offset_table.
|
||||||
fseek(output_file,
|
if (fseek(output_file,
|
||||||
sizeof(inT32) + sizeof(inT64) * TESSDATA_NUM_ENTRIES, SEEK_SET);
|
sizeof(inT32) + sizeof(inT64) * TESSDATA_NUM_ENTRIES, SEEK_SET)) {
|
||||||
|
fclose(output_file);
|
||||||
|
tprintf("Error seeking %s\n", new_traineddata_filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Open the files with the new components.
|
// Open the files with the new components.
|
||||||
for (i = 0; i < num_new_components; ++i) {
|
for (i = 0; i < num_new_components; ++i) {
|
||||||
@ -212,8 +227,7 @@ bool TessdataManager::OverwriteComponents(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const char *language_data_path_prefix = strchr(new_traineddata_filename, '.');
|
const char *language_data_path_prefix = strchr(new_traineddata_filename, '.');
|
||||||
WriteMetadata(offset_table, language_data_path_prefix, output_file);
|
return WriteMetadata(offset_table, language_data_path_prefix, output_file);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TessdataManager::TessdataTypeFromFileSuffix(
|
bool TessdataManager::TessdataTypeFromFileSuffix(
|
||||||
|
@ -199,8 +199,10 @@ class TessdataManager {
|
|||||||
return swap_;
|
return swap_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Writes the number of entries and the given offset table to output_file. */
|
/** Writes the number of entries and the given offset table to output_file.
|
||||||
static void WriteMetadata(inT64 *offset_table,
|
* Returns false on error.
|
||||||
|
*/
|
||||||
|
static bool WriteMetadata(inT64 *offset_table,
|
||||||
const char *language_data_path_prefix,
|
const char *language_data_path_prefix,
|
||||||
FILE *output_file);
|
FILE *output_file);
|
||||||
|
|
||||||
|
@ -206,12 +206,20 @@ UNICHAR::const_iterator UNICHAR::end(const char* utf8_str, const int len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Converts a utf-8 string to a vector of unicodes.
|
// Converts a utf-8 string to a vector of unicodes.
|
||||||
void UNICHAR::UTF8ToUnicode(const char* utf8_str,
|
// Returns false if the input contains invalid UTF-8, and replaces
|
||||||
|
// the rest of the string with a single space.
|
||||||
|
bool UNICHAR::UTF8ToUnicode(const char* utf8_str,
|
||||||
GenericVector<int>* unicodes) {
|
GenericVector<int>* unicodes) {
|
||||||
const int utf8_length = strlen(utf8_str);
|
const int utf8_length = strlen(utf8_str);
|
||||||
const_iterator end_it(end(utf8_str, utf8_length));
|
const_iterator end_it(end(utf8_str, utf8_length));
|
||||||
for (const_iterator it(begin(utf8_str, utf8_length)); it != end_it; ++it) {
|
for (const_iterator it(begin(utf8_str, utf8_length)); it != end_it; ++it) {
|
||||||
unicodes->push_back(*it);
|
if (it.is_legal()) {
|
||||||
|
unicodes->push_back(*it);
|
||||||
|
} else {
|
||||||
|
unicodes->push_back(' ');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +151,9 @@ class UNICHAR {
|
|||||||
static const_iterator end(const char* utf8_str, const int byte_length);
|
static const_iterator end(const char* utf8_str, const int byte_length);
|
||||||
|
|
||||||
// Converts a utf-8 string to a vector of unicodes.
|
// Converts a utf-8 string to a vector of unicodes.
|
||||||
static void UTF8ToUnicode(const char* utf8_str, GenericVector<int>* unicodes);
|
// Returns false if the input contains invalid UTF-8, and replaces
|
||||||
|
// the rest of the string with a single space.
|
||||||
|
static bool UTF8ToUnicode(const char* utf8_str, GenericVector<int>* unicodes);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// A UTF-8 representation of 1 or more Unicode characters.
|
// A UTF-8 representation of 1 or more Unicode characters.
|
||||||
|
@ -867,8 +867,11 @@ bool UNICHARSET::load_via_fgets(
|
|||||||
// Skip fragments if needed.
|
// Skip fragments if needed.
|
||||||
CHAR_FRAGMENT *frag = NULL;
|
CHAR_FRAGMENT *frag = NULL;
|
||||||
if (skip_fragments && (frag = CHAR_FRAGMENT::parse_from_string(unichar))) {
|
if (skip_fragments && (frag = CHAR_FRAGMENT::parse_from_string(unichar))) {
|
||||||
|
int num_pieces = frag->get_total();
|
||||||
delete frag;
|
delete frag;
|
||||||
continue;
|
// Skip multi-element fragments, but keep singles like UNICHAR_BROKEN in.
|
||||||
|
if (num_pieces > 1)
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
// Insert unichar into unicharset and set its properties.
|
// Insert unichar into unicharset and set its properties.
|
||||||
if (strcmp(unichar, "NULL") == 0)
|
if (strcmp(unichar, "NULL") == 0)
|
||||||
@ -982,8 +985,10 @@ bool UNICHARSET::major_right_to_left() const {
|
|||||||
// Set a whitelist and/or blacklist of characters to recognize.
|
// Set a whitelist and/or blacklist of characters to recognize.
|
||||||
// An empty or NULL whitelist enables everything (minus any blacklist).
|
// An empty or NULL whitelist enables everything (minus any blacklist).
|
||||||
// An empty or NULL blacklist disables nothing.
|
// An empty or NULL blacklist disables nothing.
|
||||||
|
// An empty or NULL blacklist has no effect.
|
||||||
void UNICHARSET::set_black_and_whitelist(const char* blacklist,
|
void UNICHARSET::set_black_and_whitelist(const char* blacklist,
|
||||||
const char* whitelist) {
|
const char* whitelist,
|
||||||
|
const char* unblacklist) {
|
||||||
bool def_enabled = whitelist == NULL || whitelist[0] == '\0';
|
bool def_enabled = whitelist == NULL || whitelist[0] == '\0';
|
||||||
// Set everything to default
|
// Set everything to default
|
||||||
for (int ch = 0; ch < size_used; ++ch)
|
for (int ch = 0; ch < size_used; ++ch)
|
||||||
@ -1006,6 +1011,15 @@ void UNICHARSET::set_black_and_whitelist(const char* blacklist,
|
|||||||
unichars[encoding[i]].properties.enabled = false;
|
unichars[encoding[i]].properties.enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (unblacklist != NULL && unblacklist[0] != '\0') {
|
||||||
|
// Re-enable the unblacklist.
|
||||||
|
GenericVector<UNICHAR_ID> encoding;
|
||||||
|
encode_string(unblacklist, false, &encoding, NULL, NULL);
|
||||||
|
for (int i = 0; i < encoding.size(); ++i) {
|
||||||
|
if (encoding[i] != INVALID_UNICHAR_ID)
|
||||||
|
unichars[encoding[i]].properties.enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int UNICHARSET::add_script(const char* script) {
|
int UNICHARSET::add_script(const char* script) {
|
||||||
|
@ -381,11 +381,14 @@ class UNICHARSET {
|
|||||||
// Set a whitelist and/or blacklist of characters to recognize.
|
// Set a whitelist and/or blacklist of characters to recognize.
|
||||||
// An empty or NULL whitelist enables everything (minus any blacklist).
|
// An empty or NULL whitelist enables everything (minus any blacklist).
|
||||||
// An empty or NULL blacklist disables nothing.
|
// An empty or NULL blacklist disables nothing.
|
||||||
|
// An empty or NULL unblacklist has no effect.
|
||||||
// The blacklist overrides the whitelist.
|
// The blacklist overrides the whitelist.
|
||||||
|
// The unblacklist overrides the blacklist.
|
||||||
// Each list is a string of utf8 character strings. Boundaries between
|
// Each list is a string of utf8 character strings. Boundaries between
|
||||||
// unicharset units are worked out automatically, and characters not in
|
// unicharset units are worked out automatically, and characters not in
|
||||||
// the unicharset are silently ignored.
|
// the unicharset are silently ignored.
|
||||||
void set_black_and_whitelist(const char* blacklist, const char* whitelist);
|
void set_black_and_whitelist(const char* blacklist, const char* whitelist,
|
||||||
|
const char* unblacklist);
|
||||||
|
|
||||||
// Set the isalpha property of the given unichar to the given value.
|
// Set the isalpha property of the given unichar to the given value.
|
||||||
void set_isalpha(UNICHAR_ID unichar_id, bool value) {
|
void set_isalpha(UNICHAR_ID unichar_id, bool value) {
|
||||||
@ -614,6 +617,10 @@ class UNICHARSET {
|
|||||||
unichars[unichar_id].properties.max_advance =
|
unichars[unichar_id].properties.max_advance =
|
||||||
static_cast<inT16>(ClipToRange(max_advance, 0, MAX_INT16));
|
static_cast<inT16>(ClipToRange(max_advance, 0, MAX_INT16));
|
||||||
}
|
}
|
||||||
|
// Returns true if the font metrics properties are empty.
|
||||||
|
bool PropertiesIncomplete(UNICHAR_ID unichar_id) const {
|
||||||
|
return unichars[unichar_id].properties.AnyRangeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
// Return the script name of the given unichar.
|
// Return the script name of the given unichar.
|
||||||
// The returned pointer will always be the same for the same script, it's
|
// The returned pointer will always be the same for the same script, it's
|
||||||
|
@ -11,15 +11,15 @@ endif
|
|||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
adaptive.h blobclass.h \
|
adaptive.h blobclass.h \
|
||||||
classify.h cluster.h clusttool.h cutoffs.h \
|
classify.h cluster.h clusttool.h cutoffs.h \
|
||||||
errorcounter.h extern.h extract.h \
|
errorcounter.h \
|
||||||
featdefs.h flexfx.h float2int.h fpoint.h fxdefs.h \
|
featdefs.h float2int.h fpoint.h \
|
||||||
intfeaturedist.h intfeaturemap.h intfeaturespace.h \
|
intfeaturedist.h intfeaturemap.h intfeaturespace.h \
|
||||||
intfx.h intmatcher.h intproto.h kdtree.h \
|
intfx.h intmatcher.h intproto.h kdtree.h \
|
||||||
mastertrainer.h mf.h mfdefs.h mfoutline.h mfx.h \
|
mastertrainer.h mf.h mfdefs.h mfoutline.h mfx.h \
|
||||||
normfeat.h normmatch.h \
|
normfeat.h normmatch.h \
|
||||||
ocrfeatures.h outfeat.h picofeat.h protos.h \
|
ocrfeatures.h outfeat.h picofeat.h protos.h \
|
||||||
sampleiterator.h shapeclassifier.h shapetable.h \
|
sampleiterator.h shapeclassifier.h shapetable.h \
|
||||||
tessclassifier.h trainingsample.h trainingsampleset.h xform2d.h
|
tessclassifier.h trainingsample.h trainingsampleset.h
|
||||||
|
|
||||||
if !USING_MULTIPLELIBS
|
if !USING_MULTIPLELIBS
|
||||||
noinst_LTLIBRARIES = libtesseract_classify.la
|
noinst_LTLIBRARIES = libtesseract_classify.la
|
||||||
@ -37,14 +37,14 @@ endif
|
|||||||
libtesseract_classify_la_SOURCES = \
|
libtesseract_classify_la_SOURCES = \
|
||||||
adaptive.cpp adaptmatch.cpp blobclass.cpp \
|
adaptive.cpp adaptmatch.cpp blobclass.cpp \
|
||||||
classify.cpp cluster.cpp clusttool.cpp cutoffs.cpp \
|
classify.cpp cluster.cpp clusttool.cpp cutoffs.cpp \
|
||||||
errorcounter.cpp extract.cpp \
|
errorcounter.cpp \
|
||||||
featdefs.cpp flexfx.cpp float2int.cpp fpoint.cpp fxdefs.cpp \
|
featdefs.cpp float2int.cpp fpoint.cpp \
|
||||||
intfeaturedist.cpp intfeaturemap.cpp intfeaturespace.cpp \
|
intfeaturedist.cpp intfeaturemap.cpp intfeaturespace.cpp \
|
||||||
intfx.cpp intmatcher.cpp intproto.cpp kdtree.cpp \
|
intfx.cpp intmatcher.cpp intproto.cpp kdtree.cpp \
|
||||||
mastertrainer.cpp mf.cpp mfdefs.cpp mfoutline.cpp mfx.cpp \
|
mastertrainer.cpp mf.cpp mfdefs.cpp mfoutline.cpp mfx.cpp \
|
||||||
normfeat.cpp normmatch.cpp \
|
normfeat.cpp normmatch.cpp \
|
||||||
ocrfeatures.cpp outfeat.cpp picofeat.cpp protos.cpp \
|
ocrfeatures.cpp outfeat.cpp picofeat.cpp protos.cpp \
|
||||||
sampleiterator.cpp shapeclassifier.cpp shapetable.cpp \
|
sampleiterator.cpp shapeclassifier.cpp shapetable.cpp \
|
||||||
tessclassifier.cpp trainingsample.cpp trainingsampleset.cpp xform2d.cpp
|
tessclassifier.cpp trainingsample.cpp trainingsampleset.cpp
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include "shapeclassifier.h"
|
||||||
#include "ambigs.h"
|
#include "ambigs.h"
|
||||||
#include "blobclass.h"
|
#include "blobclass.h"
|
||||||
#include "blobs.h"
|
#include "blobs.h"
|
||||||
@ -73,37 +74,39 @@
|
|||||||
|
|
||||||
#define Y_DIM_OFFSET (Y_SHIFT - BASELINE_Y_SHIFT)
|
#define Y_DIM_OFFSET (Y_SHIFT - BASELINE_Y_SHIFT)
|
||||||
|
|
||||||
#define WORST_POSSIBLE_RATING (1.0)
|
#define WORST_POSSIBLE_RATING (0.0f)
|
||||||
|
|
||||||
struct ScoredClass {
|
using tesseract::UnicharRating;
|
||||||
CLASS_ID unichar_id;
|
using tesseract::ScoredFont;
|
||||||
int shape_id;
|
|
||||||
FLOAT32 rating;
|
|
||||||
bool adapted;
|
|
||||||
inT16 config;
|
|
||||||
inT16 fontinfo_id;
|
|
||||||
inT16 fontinfo_id2;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ADAPT_RESULTS {
|
struct ADAPT_RESULTS {
|
||||||
inT32 BlobLength;
|
inT32 BlobLength;
|
||||||
bool HasNonfragment;
|
bool HasNonfragment;
|
||||||
GenericVector<ScoredClass> match;
|
UNICHAR_ID best_unichar_id;
|
||||||
ScoredClass best_match;
|
int best_match_index;
|
||||||
|
FLOAT32 best_rating;
|
||||||
|
GenericVector<UnicharRating> match;
|
||||||
GenericVector<CP_RESULT_STRUCT> CPResults;
|
GenericVector<CP_RESULT_STRUCT> CPResults;
|
||||||
|
|
||||||
/// Initializes data members to the default values. Sets the initial
|
/// Initializes data members to the default values. Sets the initial
|
||||||
/// rating of each class to be the worst possible rating (1.0).
|
/// rating of each class to be the worst possible rating (1.0).
|
||||||
inline void Initialize() {
|
inline void Initialize() {
|
||||||
BlobLength = MAX_INT32;
|
BlobLength = MAX_INT32;
|
||||||
HasNonfragment = false;
|
HasNonfragment = false;
|
||||||
best_match.unichar_id = NO_CLASS;
|
ComputeBest();
|
||||||
best_match.shape_id = -1;
|
}
|
||||||
best_match.rating = WORST_POSSIBLE_RATING;
|
// Computes best_unichar_id, best_match_index and best_rating.
|
||||||
best_match.adapted = false;
|
void ComputeBest() {
|
||||||
best_match.config = 0;
|
best_unichar_id = INVALID_UNICHAR_ID;
|
||||||
best_match.fontinfo_id = kBlankFontinfoId;
|
best_match_index = -1;
|
||||||
best_match.fontinfo_id2 = kBlankFontinfoId;
|
best_rating = WORST_POSSIBLE_RATING;
|
||||||
|
for (int i = 0; i < match.size(); ++i) {
|
||||||
|
if (match[i].rating > best_rating) {
|
||||||
|
best_rating = match[i].rating;
|
||||||
|
best_unichar_id = match[i].unichar_id;
|
||||||
|
best_match_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -116,17 +119,30 @@ struct PROTO_KEY {
|
|||||||
/*-----------------------------------------------------------------------------
|
/*-----------------------------------------------------------------------------
|
||||||
Private Macros
|
Private Macros
|
||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
#define MarginalMatch(Rating) \
|
inline bool MarginalMatch(float confidence, float matcher_great_threshold) {
|
||||||
((Rating) > matcher_great_threshold)
|
return (1.0f - confidence) > matcher_great_threshold;
|
||||||
|
}
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------------
|
/*-----------------------------------------------------------------------------
|
||||||
Private Function Prototypes
|
Private Function Prototypes
|
||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
int CompareByRating(const void *arg1, const void *arg2);
|
// Returns the index of the given id in results, if present, or the size of the
|
||||||
|
// vector (index it will go at) if not present.
|
||||||
|
static int FindScoredUnichar(UNICHAR_ID id, const ADAPT_RESULTS& results) {
|
||||||
|
for (int i = 0; i < results.match.size(); i++) {
|
||||||
|
if (results.match[i].unichar_id == id)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return results.match.size();
|
||||||
|
}
|
||||||
|
|
||||||
ScoredClass *FindScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id);
|
// Returns the current rating for a unichar id if we have rated it, defaulting
|
||||||
|
// to WORST_POSSIBLE_RATING.
|
||||||
ScoredClass ScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id);
|
static float ScoredUnichar(UNICHAR_ID id, const ADAPT_RESULTS& results) {
|
||||||
|
int index = FindScoredUnichar(id, results);
|
||||||
|
if (index >= results.match.size()) return WORST_POSSIBLE_RATING;
|
||||||
|
return results.match[index].rating;
|
||||||
|
}
|
||||||
|
|
||||||
void InitMatcherRatings(register FLOAT32 *Rating);
|
void InitMatcherRatings(register FLOAT32 *Rating);
|
||||||
|
|
||||||
@ -176,19 +192,21 @@ void Classify::AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices) {
|
|||||||
DoAdaptiveMatch(Blob, Results);
|
DoAdaptiveMatch(Blob, Results);
|
||||||
|
|
||||||
RemoveBadMatches(Results);
|
RemoveBadMatches(Results);
|
||||||
Results->match.sort(CompareByRating);
|
Results->match.sort(&UnicharRating::SortDescendingRating);
|
||||||
RemoveExtraPuncs(Results);
|
RemoveExtraPuncs(Results);
|
||||||
|
Results->ComputeBest();
|
||||||
ConvertMatchesToChoices(Blob->denorm(), Blob->bounding_box(), Results,
|
ConvertMatchesToChoices(Blob->denorm(), Blob->bounding_box(), Results,
|
||||||
Choices);
|
Choices);
|
||||||
|
|
||||||
if (matcher_debug_level >= 1) {
|
// TODO(rays) Move to before ConvertMatchesToChoices!
|
||||||
cprintf ("AD Matches = ");
|
|
||||||
PrintAdaptiveMatchResults(stdout, Results);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LargeSpeckle(*Blob) || Choices->length() == 0)
|
if (LargeSpeckle(*Blob) || Choices->length() == 0)
|
||||||
AddLargeSpeckleTo(Results->BlobLength, Choices);
|
AddLargeSpeckleTo(Results->BlobLength, Choices);
|
||||||
|
|
||||||
|
if (matcher_debug_level >= 1) {
|
||||||
|
tprintf("AD Matches = ");
|
||||||
|
PrintAdaptiveMatchResults(*Results);
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef GRAPHICS_DISABLED
|
#ifndef GRAPHICS_DISABLED
|
||||||
if (classify_enable_adaptive_debugger)
|
if (classify_enable_adaptive_debugger)
|
||||||
DebugAdaptiveClassifier(Blob, Results);
|
DebugAdaptiveClassifier(Blob, Results);
|
||||||
@ -220,17 +238,15 @@ void Classify::RefreshDebugWindow(ScrollView **win, const char *msg,
|
|||||||
|
|
||||||
// Learns the given word using its chopped_word, seam_array, denorm,
|
// Learns the given word using its chopped_word, seam_array, denorm,
|
||||||
// box_word, best_state, and correct_text to learn both correctly and
|
// box_word, best_state, and correct_text to learn both correctly and
|
||||||
// incorrectly segmented blobs. If filename is not NULL, then LearnBlob
|
// incorrectly segmented blobs. If fontname is not NULL, then LearnBlob
|
||||||
// is called and the data will be written to a file for static training.
|
// is called and the data will be saved in an internal buffer.
|
||||||
// Otherwise AdaptToBlob is called for adaption within a document.
|
// Otherwise AdaptToBlob is called for adaption within a document.
|
||||||
// If rejmap is not NULL, then only chars with a rejmap entry of '1' will
|
void Classify::LearnWord(const char* fontname, WERD_RES* word) {
|
||||||
// be learned, otherwise all chars with good correct_text are learned.
|
|
||||||
void Classify::LearnWord(const char* filename, WERD_RES *word) {
|
|
||||||
int word_len = word->correct_text.size();
|
int word_len = word->correct_text.size();
|
||||||
if (word_len == 0) return;
|
if (word_len == 0) return;
|
||||||
|
|
||||||
float* thresholds = NULL;
|
float* thresholds = NULL;
|
||||||
if (filename == NULL) {
|
if (fontname == NULL) {
|
||||||
// Adaption mode.
|
// Adaption mode.
|
||||||
if (!EnableLearning || word->best_choice == NULL)
|
if (!EnableLearning || word->best_choice == NULL)
|
||||||
return; // Can't or won't adapt.
|
return; // Can't or won't adapt.
|
||||||
@ -267,8 +283,8 @@ void Classify::LearnWord(const char* filename, WERD_RES *word) {
|
|||||||
if (word->correct_text[ch].length() > 0) {
|
if (word->correct_text[ch].length() > 0) {
|
||||||
float threshold = thresholds != NULL ? thresholds[ch] : 0.0f;
|
float threshold = thresholds != NULL ? thresholds[ch] : 0.0f;
|
||||||
|
|
||||||
LearnPieces(filename, start_blob, word->best_state[ch],
|
LearnPieces(fontname, start_blob, word->best_state[ch], threshold,
|
||||||
threshold, CST_WHOLE, word->correct_text[ch].string(), word);
|
CST_WHOLE, word->correct_text[ch].string(), word);
|
||||||
|
|
||||||
if (word->best_state[ch] > 1 && !disable_character_fragments) {
|
if (word->best_state[ch] > 1 && !disable_character_fragments) {
|
||||||
// Check that the character breaks into meaningful fragments
|
// Check that the character breaks into meaningful fragments
|
||||||
@ -301,8 +317,8 @@ void Classify::LearnWord(const char* filename, WERD_RES *word) {
|
|||||||
if (i != tokens.size() - 1)
|
if (i != tokens.size() - 1)
|
||||||
full_string += ' ';
|
full_string += ' ';
|
||||||
}
|
}
|
||||||
LearnPieces(filename, start_blob + frag, 1,
|
LearnPieces(fontname, start_blob + frag, 1, threshold,
|
||||||
threshold, CST_FRAGMENT, full_string.string(), word);
|
CST_FRAGMENT, full_string.string(), word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -314,13 +330,13 @@ void Classify::LearnWord(const char* filename, WERD_RES *word) {
|
|||||||
if (word->best_state[ch] > 1) {
|
if (word->best_state[ch] > 1) {
|
||||||
// If the next blob is good, make junk with the rightmost fragment.
|
// If the next blob is good, make junk with the rightmost fragment.
|
||||||
if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
|
if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
|
||||||
LearnPieces(filename, start_blob + word->best_state[ch] - 1,
|
LearnPieces(fontname, start_blob + word->best_state[ch] - 1,
|
||||||
word->best_state[ch + 1] + 1,
|
word->best_state[ch + 1] + 1,
|
||||||
threshold, CST_IMPROPER, INVALID_UNICHAR, word);
|
threshold, CST_IMPROPER, INVALID_UNICHAR, word);
|
||||||
}
|
}
|
||||||
// If the previous blob is good, make junk with the leftmost fragment.
|
// If the previous blob is good, make junk with the leftmost fragment.
|
||||||
if (ch > 0 && word->correct_text[ch - 1].length() > 0) {
|
if (ch > 0 && word->correct_text[ch - 1].length() > 0) {
|
||||||
LearnPieces(filename, start_blob - word->best_state[ch - 1],
|
LearnPieces(fontname, start_blob - word->best_state[ch - 1],
|
||||||
word->best_state[ch - 1] + 1,
|
word->best_state[ch - 1] + 1,
|
||||||
threshold, CST_IMPROPER, INVALID_UNICHAR, word);
|
threshold, CST_IMPROPER, INVALID_UNICHAR, word);
|
||||||
}
|
}
|
||||||
@ -329,7 +345,7 @@ void Classify::LearnWord(const char* filename, WERD_RES *word) {
|
|||||||
if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
|
if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
|
||||||
STRING joined_text = word->correct_text[ch];
|
STRING joined_text = word->correct_text[ch];
|
||||||
joined_text += word->correct_text[ch + 1];
|
joined_text += word->correct_text[ch + 1];
|
||||||
LearnPieces(filename, start_blob,
|
LearnPieces(fontname, start_blob,
|
||||||
word->best_state[ch] + word->best_state[ch + 1],
|
word->best_state[ch] + word->best_state[ch + 1],
|
||||||
threshold, CST_NGRAM, joined_text.string(), word);
|
threshold, CST_NGRAM, joined_text.string(), word);
|
||||||
}
|
}
|
||||||
@ -342,16 +358,16 @@ void Classify::LearnWord(const char* filename, WERD_RES *word) {
|
|||||||
|
|
||||||
// Builds a blob of length fragments, from the word, starting at start,
|
// Builds a blob of length fragments, from the word, starting at start,
|
||||||
// and then learns it, as having the given correct_text.
|
// and then learns it, as having the given correct_text.
|
||||||
// If filename is not NULL, then LearnBlob
|
// If fontname is not NULL, then LearnBlob is called and the data will be
|
||||||
// is called and the data will be written to a file for static training.
|
// saved in an internal buffer for static training.
|
||||||
// Otherwise AdaptToBlob is called for adaption within a document.
|
// Otherwise AdaptToBlob is called for adaption within a document.
|
||||||
// threshold is a magic number required by AdaptToChar and generated by
|
// threshold is a magic number required by AdaptToChar and generated by
|
||||||
// ComputeAdaptionThresholds.
|
// ComputeAdaptionThresholds.
|
||||||
// Although it can be partly inferred from the string, segmentation is
|
// Although it can be partly inferred from the string, segmentation is
|
||||||
// provided to explicitly clarify the character segmentation.
|
// provided to explicitly clarify the character segmentation.
|
||||||
void Classify::LearnPieces(const char* filename, int start, int length,
|
void Classify::LearnPieces(const char* fontname, int start, int length,
|
||||||
float threshold, CharSegmentationType segmentation,
|
float threshold, CharSegmentationType segmentation,
|
||||||
const char* correct_text, WERD_RES *word) {
|
const char* correct_text, WERD_RES* word) {
|
||||||
// TODO(daria) Remove/modify this if/when we want
|
// TODO(daria) Remove/modify this if/when we want
|
||||||
// to train and/or adapt to n-grams.
|
// to train and/or adapt to n-grams.
|
||||||
if (segmentation != CST_WHOLE &&
|
if (segmentation != CST_WHOLE &&
|
||||||
@ -359,8 +375,8 @@ void Classify::LearnPieces(const char* filename, int start, int length,
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (length > 1) {
|
if (length > 1) {
|
||||||
join_pieces(word->seam_array, start, start + length - 1,
|
SEAM::JoinPieces(word->seam_array, word->chopped_word->blobs, start,
|
||||||
word->chopped_word);
|
start + length - 1);
|
||||||
}
|
}
|
||||||
TBLOB* blob = word->chopped_word->blobs[start];
|
TBLOB* blob = word->chopped_word->blobs[start];
|
||||||
// Rotate the blob if needed for classification.
|
// Rotate the blob if needed for classification.
|
||||||
@ -385,7 +401,7 @@ void Classify::LearnPieces(const char* filename, int start, int length,
|
|||||||
}
|
}
|
||||||
#endif // GRAPHICS_DISABLED
|
#endif // GRAPHICS_DISABLED
|
||||||
|
|
||||||
if (filename != NULL) {
|
if (fontname != NULL) {
|
||||||
classify_norm_method.set_value(character); // force char norm spc 30/11/93
|
classify_norm_method.set_value(character); // force char norm spc 30/11/93
|
||||||
tess_bn_matching.set_value(false); // turn it off
|
tess_bn_matching.set_value(false); // turn it off
|
||||||
tess_cn_matching.set_value(false);
|
tess_cn_matching.set_value(false);
|
||||||
@ -393,8 +409,7 @@ void Classify::LearnPieces(const char* filename, int start, int length,
|
|||||||
INT_FX_RESULT_STRUCT fx_info;
|
INT_FX_RESULT_STRUCT fx_info;
|
||||||
SetupBLCNDenorms(*rotated_blob, classify_nonlinear_norm,
|
SetupBLCNDenorms(*rotated_blob, classify_nonlinear_norm,
|
||||||
&bl_denorm, &cn_denorm, &fx_info);
|
&bl_denorm, &cn_denorm, &fx_info);
|
||||||
LearnBlob(feature_defs_, filename, rotated_blob, bl_denorm, cn_denorm,
|
LearnBlob(fontname, rotated_blob, cn_denorm, fx_info, correct_text);
|
||||||
fx_info, correct_text);
|
|
||||||
} else if (unicharset.contains_unichar(correct_text)) {
|
} else if (unicharset.contains_unichar(correct_text)) {
|
||||||
UNICHAR_ID class_id = unicharset.unichar_to_id(correct_text);
|
UNICHAR_ID class_id = unicharset.unichar_to_id(correct_text);
|
||||||
int font_id = word->fontinfo != NULL
|
int font_id = word->fontinfo != NULL
|
||||||
@ -413,7 +428,8 @@ void Classify::LearnPieces(const char* filename, int start, int length,
|
|||||||
delete rotated_blob;
|
delete rotated_blob;
|
||||||
}
|
}
|
||||||
|
|
||||||
break_pieces(word->seam_array, start, start + length - 1, word->chopped_word);
|
SEAM::BreakPieces(word->seam_array, word->chopped_word->blobs, start,
|
||||||
|
start + length - 1);
|
||||||
} // LearnPieces.
|
} // LearnPieces.
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
@ -726,8 +742,8 @@ void Classify::InitAdaptedClass(TBLOB *Blob,
|
|||||||
ConvertConfig (AllProtosOn, 0, IClass);
|
ConvertConfig (AllProtosOn, 0, IClass);
|
||||||
|
|
||||||
if (classify_learning_debug_level >= 1) {
|
if (classify_learning_debug_level >= 1) {
|
||||||
cprintf ("Added new class '%s' with class id %d and %d protos.\n",
|
tprintf("Added new class '%s' with class id %d and %d protos.\n",
|
||||||
unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
|
unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
|
||||||
if (classify_learning_debug_level > 1)
|
if (classify_learning_debug_level > 1)
|
||||||
DisplayAdaptedChar(Blob, IClass);
|
DisplayAdaptedChar(Blob, IClass);
|
||||||
}
|
}
|
||||||
@ -839,7 +855,7 @@ void Classify::AdaptToChar(TBLOB *Blob,
|
|||||||
FLOAT32 Threshold) {
|
FLOAT32 Threshold) {
|
||||||
int NumFeatures;
|
int NumFeatures;
|
||||||
INT_FEATURE_ARRAY IntFeatures;
|
INT_FEATURE_ARRAY IntFeatures;
|
||||||
INT_RESULT_STRUCT IntResult;
|
UnicharRating int_result;
|
||||||
INT_CLASS IClass;
|
INT_CLASS IClass;
|
||||||
ADAPT_CLASS Class;
|
ADAPT_CLASS Class;
|
||||||
TEMP_CONFIG TempConfig;
|
TEMP_CONFIG TempConfig;
|
||||||
@ -849,13 +865,13 @@ void Classify::AdaptToChar(TBLOB *Blob,
|
|||||||
if (!LegalClassId (ClassId))
|
if (!LegalClassId (ClassId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
int_result.unichar_id = ClassId;
|
||||||
Class = AdaptedTemplates->Class[ClassId];
|
Class = AdaptedTemplates->Class[ClassId];
|
||||||
assert(Class != NULL);
|
assert(Class != NULL);
|
||||||
if (IsEmptyAdaptedClass(Class)) {
|
if (IsEmptyAdaptedClass(Class)) {
|
||||||
InitAdaptedClass(Blob, ClassId, FontinfoId, Class, AdaptedTemplates);
|
InitAdaptedClass(Blob, ClassId, FontinfoId, Class, AdaptedTemplates);
|
||||||
}
|
} else {
|
||||||
else {
|
IClass = ClassForClassId(AdaptedTemplates->Templates, ClassId);
|
||||||
IClass = ClassForClassId (AdaptedTemplates->Templates, ClassId);
|
|
||||||
|
|
||||||
NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
|
NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
|
||||||
if (NumFeatures <= 0)
|
if (NumFeatures <= 0)
|
||||||
@ -872,39 +888,38 @@ void Classify::AdaptToChar(TBLOB *Blob,
|
|||||||
}
|
}
|
||||||
im_.Match(IClass, AllProtosOn, MatchingFontConfigs,
|
im_.Match(IClass, AllProtosOn, MatchingFontConfigs,
|
||||||
NumFeatures, IntFeatures,
|
NumFeatures, IntFeatures,
|
||||||
&IntResult, classify_adapt_feature_threshold,
|
&int_result, classify_adapt_feature_threshold,
|
||||||
NO_DEBUG, matcher_debug_separate_windows);
|
NO_DEBUG, matcher_debug_separate_windows);
|
||||||
FreeBitVector(MatchingFontConfigs);
|
FreeBitVector(MatchingFontConfigs);
|
||||||
|
|
||||||
SetAdaptiveThreshold(Threshold);
|
SetAdaptiveThreshold(Threshold);
|
||||||
|
|
||||||
if (IntResult.Rating <= Threshold) {
|
if (1.0f - int_result.rating <= Threshold) {
|
||||||
if (ConfigIsPermanent (Class, IntResult.Config)) {
|
if (ConfigIsPermanent(Class, int_result.config)) {
|
||||||
if (classify_learning_debug_level >= 1)
|
if (classify_learning_debug_level >= 1)
|
||||||
cprintf ("Found good match to perm config %d = %4.1f%%.\n",
|
tprintf("Found good match to perm config %d = %4.1f%%.\n",
|
||||||
IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
|
int_result.config, int_result.rating * 100.0);
|
||||||
FreeFeatureSet(FloatFeatures);
|
FreeFeatureSet(FloatFeatures);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TempConfig = TempConfigFor (Class, IntResult.Config);
|
TempConfig = TempConfigFor(Class, int_result.config);
|
||||||
IncreaseConfidence(TempConfig);
|
IncreaseConfidence(TempConfig);
|
||||||
if (TempConfig->NumTimesSeen > Class->MaxNumTimesSeen) {
|
if (TempConfig->NumTimesSeen > Class->MaxNumTimesSeen) {
|
||||||
Class->MaxNumTimesSeen = TempConfig->NumTimesSeen;
|
Class->MaxNumTimesSeen = TempConfig->NumTimesSeen;
|
||||||
}
|
}
|
||||||
if (classify_learning_debug_level >= 1)
|
if (classify_learning_debug_level >= 1)
|
||||||
cprintf ("Increasing reliability of temp config %d to %d.\n",
|
tprintf("Increasing reliability of temp config %d to %d.\n",
|
||||||
IntResult.Config, TempConfig->NumTimesSeen);
|
int_result.config, TempConfig->NumTimesSeen);
|
||||||
|
|
||||||
if (TempConfigReliable(ClassId, TempConfig)) {
|
if (TempConfigReliable(ClassId, TempConfig)) {
|
||||||
MakePermanent(AdaptedTemplates, ClassId, IntResult.Config, Blob);
|
MakePermanent(AdaptedTemplates, ClassId, int_result.config, Blob);
|
||||||
UpdateAmbigsGroup(ClassId, Blob);
|
UpdateAmbigsGroup(ClassId, Blob);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (classify_learning_debug_level >= 1) {
|
if (classify_learning_debug_level >= 1) {
|
||||||
cprintf ("Found poor match to temp config %d = %4.1f%%.\n",
|
tprintf("Found poor match to temp config %d = %4.1f%%.\n",
|
||||||
IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
|
int_result.config, int_result.rating * 100.0);
|
||||||
if (classify_learning_debug_level > 2)
|
if (classify_learning_debug_level > 2)
|
||||||
DisplayAdaptedChar(Blob, IClass);
|
DisplayAdaptedChar(Blob, IClass);
|
||||||
}
|
}
|
||||||
@ -939,20 +954,20 @@ void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) {
|
|||||||
&bl_features);
|
&bl_features);
|
||||||
if (sample == NULL) return;
|
if (sample == NULL) return;
|
||||||
|
|
||||||
INT_RESULT_STRUCT IntResult;
|
UnicharRating int_result;
|
||||||
im_.Match(int_class, AllProtosOn, AllConfigsOn,
|
im_.Match(int_class, AllProtosOn, AllConfigsOn,
|
||||||
bl_features.size(), &bl_features[0],
|
bl_features.size(), &bl_features[0],
|
||||||
&IntResult, classify_adapt_feature_threshold,
|
&int_result, classify_adapt_feature_threshold,
|
||||||
NO_DEBUG, matcher_debug_separate_windows);
|
NO_DEBUG, matcher_debug_separate_windows);
|
||||||
cprintf ("Best match to temp config %d = %4.1f%%.\n",
|
tprintf("Best match to temp config %d = %4.1f%%.\n",
|
||||||
IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
|
int_result.config, int_result.rating * 100.0);
|
||||||
if (classify_learning_debug_level >= 2) {
|
if (classify_learning_debug_level >= 2) {
|
||||||
uinT32 ConfigMask;
|
uinT32 ConfigMask;
|
||||||
ConfigMask = 1 << IntResult.Config;
|
ConfigMask = 1 << int_result.config;
|
||||||
ShowMatchDisplay();
|
ShowMatchDisplay();
|
||||||
im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask,
|
im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask,
|
||||||
bl_features.size(), &bl_features[0],
|
bl_features.size(), &bl_features[0],
|
||||||
&IntResult, classify_adapt_feature_threshold,
|
&int_result, classify_adapt_feature_threshold,
|
||||||
6 | 0x19, matcher_debug_separate_windows);
|
6 | 0x19, matcher_debug_separate_windows);
|
||||||
UpdateMatchDisplay();
|
UpdateMatchDisplay();
|
||||||
}
|
}
|
||||||
@ -988,44 +1003,34 @@ void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) {
|
|||||||
* @note Exceptions: none
|
* @note Exceptions: none
|
||||||
* @note History: Tue Mar 12 18:19:29 1991, DSJ, Created.
|
* @note History: Tue Mar 12 18:19:29 1991, DSJ, Created.
|
||||||
*/
|
*/
|
||||||
void Classify::AddNewResult(ADAPT_RESULTS *results,
|
void Classify::AddNewResult(const UnicharRating& new_result,
|
||||||
CLASS_ID class_id,
|
ADAPT_RESULTS *results) {
|
||||||
int shape_id,
|
int old_match = FindScoredUnichar(new_result.unichar_id, *results);
|
||||||
FLOAT32 rating,
|
|
||||||
bool adapted,
|
|
||||||
int config,
|
|
||||||
int fontinfo_id,
|
|
||||||
int fontinfo_id2) {
|
|
||||||
ScoredClass *old_match = FindScoredUnichar(results, class_id);
|
|
||||||
ScoredClass match =
|
|
||||||
{ class_id,
|
|
||||||
shape_id,
|
|
||||||
rating,
|
|
||||||
adapted,
|
|
||||||
static_cast<inT16>(config),
|
|
||||||
static_cast<inT16>(fontinfo_id),
|
|
||||||
static_cast<inT16>(fontinfo_id2) };
|
|
||||||
|
|
||||||
if (rating > results->best_match.rating + matcher_bad_match_pad ||
|
if (new_result.rating + matcher_bad_match_pad < results->best_rating ||
|
||||||
(old_match && rating >= old_match->rating))
|
(old_match < results->match.size() &&
|
||||||
return;
|
new_result.rating <= results->match[old_match].rating))
|
||||||
|
return; // New one not good enough.
|
||||||
|
|
||||||
if (!unicharset.get_fragment(class_id))
|
if (!unicharset.get_fragment(new_result.unichar_id))
|
||||||
results->HasNonfragment = true;
|
results->HasNonfragment = true;
|
||||||
|
|
||||||
if (old_match)
|
if (old_match < results->match.size()) {
|
||||||
old_match->rating = rating;
|
results->match[old_match].rating = new_result.rating;
|
||||||
else
|
} else {
|
||||||
results->match.push_back(match);
|
results->match.push_back(new_result);
|
||||||
|
}
|
||||||
|
|
||||||
if (rating < results->best_match.rating &&
|
if (new_result.rating > results->best_rating &&
|
||||||
// Ensure that fragments do not affect best rating, class and config.
|
// Ensure that fragments do not affect best rating, class and config.
|
||||||
// This is needed so that at least one non-fragmented character is
|
// This is needed so that at least one non-fragmented character is
|
||||||
// always present in the results.
|
// always present in the results.
|
||||||
// TODO(daria): verify that this helps accuracy and does not
|
// TODO(daria): verify that this helps accuracy and does not
|
||||||
// hurt performance.
|
// hurt performance.
|
||||||
!unicharset.get_fragment(class_id)) {
|
!unicharset.get_fragment(new_result.unichar_id)) {
|
||||||
results->best_match = match;
|
results->best_match_index = old_match;
|
||||||
|
results->best_rating = new_result.rating;
|
||||||
|
results->best_unichar_id = new_result.unichar_id;
|
||||||
}
|
}
|
||||||
} /* AddNewResult */
|
} /* AddNewResult */
|
||||||
|
|
||||||
@ -1060,7 +1065,7 @@ void Classify::AmbigClassifier(
|
|||||||
ADAPT_RESULTS *results) {
|
ADAPT_RESULTS *results) {
|
||||||
if (int_features.empty()) return;
|
if (int_features.empty()) return;
|
||||||
uinT8* CharNormArray = new uinT8[unicharset.size()];
|
uinT8* CharNormArray = new uinT8[unicharset.size()];
|
||||||
INT_RESULT_STRUCT IntResult;
|
UnicharRating int_result;
|
||||||
|
|
||||||
results->BlobLength = GetCharNormFeature(fx_info, templates, NULL,
|
results->BlobLength = GetCharNormFeature(fx_info, templates, NULL,
|
||||||
CharNormArray);
|
CharNormArray);
|
||||||
@ -1073,17 +1078,18 @@ void Classify::AmbigClassifier(
|
|||||||
while (*ambiguities >= 0) {
|
while (*ambiguities >= 0) {
|
||||||
CLASS_ID class_id = *ambiguities;
|
CLASS_ID class_id = *ambiguities;
|
||||||
|
|
||||||
|
int_result.unichar_id = class_id;
|
||||||
im_.Match(ClassForClassId(templates, class_id),
|
im_.Match(ClassForClassId(templates, class_id),
|
||||||
AllProtosOn, AllConfigsOn,
|
AllProtosOn, AllConfigsOn,
|
||||||
int_features.size(), &int_features[0],
|
int_features.size(), &int_features[0],
|
||||||
&IntResult,
|
&int_result,
|
||||||
classify_adapt_feature_threshold, NO_DEBUG,
|
classify_adapt_feature_threshold, NO_DEBUG,
|
||||||
matcher_debug_separate_windows);
|
matcher_debug_separate_windows);
|
||||||
|
|
||||||
ExpandShapesAndApplyCorrections(NULL, debug, class_id, bottom, top, 0,
|
ExpandShapesAndApplyCorrections(NULL, debug, class_id, bottom, top, 0,
|
||||||
results->BlobLength,
|
results->BlobLength,
|
||||||
classify_integer_matcher_multiplier,
|
classify_integer_matcher_multiplier,
|
||||||
CharNormArray, IntResult, results);
|
CharNormArray, &int_result, results);
|
||||||
ambiguities++;
|
ambiguities++;
|
||||||
}
|
}
|
||||||
delete [] CharNormArray;
|
delete [] CharNormArray;
|
||||||
@ -1104,14 +1110,15 @@ void Classify::MasterMatcher(INT_TEMPLATES templates,
|
|||||||
ADAPT_RESULTS* final_results) {
|
ADAPT_RESULTS* final_results) {
|
||||||
int top = blob_box.top();
|
int top = blob_box.top();
|
||||||
int bottom = blob_box.bottom();
|
int bottom = blob_box.bottom();
|
||||||
|
UnicharRating int_result;
|
||||||
for (int c = 0; c < results.size(); c++) {
|
for (int c = 0; c < results.size(); c++) {
|
||||||
CLASS_ID class_id = results[c].Class;
|
CLASS_ID class_id = results[c].Class;
|
||||||
INT_RESULT_STRUCT& int_result = results[c].IMResult;
|
|
||||||
BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos
|
BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos
|
||||||
: AllProtosOn;
|
: AllProtosOn;
|
||||||
BIT_VECTOR configs = classes != NULL ? classes[class_id]->PermConfigs
|
BIT_VECTOR configs = classes != NULL ? classes[class_id]->PermConfigs
|
||||||
: AllConfigsOn;
|
: AllConfigsOn;
|
||||||
|
|
||||||
|
int_result.unichar_id = class_id;
|
||||||
im_.Match(ClassForClassId(templates, class_id),
|
im_.Match(ClassForClassId(templates, class_id),
|
||||||
protos, configs,
|
protos, configs,
|
||||||
num_features, features,
|
num_features, features,
|
||||||
@ -1122,7 +1129,7 @@ void Classify::MasterMatcher(INT_TEMPLATES templates,
|
|||||||
results[c].Rating,
|
results[c].Rating,
|
||||||
final_results->BlobLength,
|
final_results->BlobLength,
|
||||||
matcher_multiplier, norm_factors,
|
matcher_multiplier, norm_factors,
|
||||||
int_result, final_results);
|
&int_result, final_results);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1135,65 +1142,76 @@ void Classify::ExpandShapesAndApplyCorrections(
|
|||||||
ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top,
|
ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top,
|
||||||
float cp_rating, int blob_length, int matcher_multiplier,
|
float cp_rating, int blob_length, int matcher_multiplier,
|
||||||
const uinT8* cn_factors,
|
const uinT8* cn_factors,
|
||||||
INT_RESULT_STRUCT& int_result, ADAPT_RESULTS* final_results) {
|
UnicharRating* int_result, ADAPT_RESULTS* final_results) {
|
||||||
// Compute the fontinfo_ids.
|
|
||||||
int fontinfo_id = kBlankFontinfoId;
|
|
||||||
int fontinfo_id2 = kBlankFontinfoId;
|
|
||||||
if (classes != NULL) {
|
if (classes != NULL) {
|
||||||
// Adapted result.
|
// Adapted result. Convert configs to fontinfo_ids.
|
||||||
fontinfo_id = GetFontinfoId(classes[class_id], int_result.Config);
|
int_result->adapted = true;
|
||||||
fontinfo_id2 = GetFontinfoId(classes[class_id], int_result.Config2);
|
for (int f = 0; f < int_result->fonts.size(); ++f) {
|
||||||
|
int_result->fonts[f].fontinfo_id =
|
||||||
|
GetFontinfoId(classes[class_id], int_result->fonts[f].fontinfo_id);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Pre-trained result.
|
// Pre-trained result. Map fonts using font_sets_.
|
||||||
fontinfo_id = ClassAndConfigIDToFontOrShapeID(class_id, int_result.Config);
|
int_result->adapted = false;
|
||||||
fontinfo_id2 = ClassAndConfigIDToFontOrShapeID(class_id,
|
for (int f = 0; f < int_result->fonts.size(); ++f) {
|
||||||
int_result.Config2);
|
int_result->fonts[f].fontinfo_id =
|
||||||
|
ClassAndConfigIDToFontOrShapeID(class_id,
|
||||||
|
int_result->fonts[f].fontinfo_id);
|
||||||
|
}
|
||||||
if (shape_table_ != NULL) {
|
if (shape_table_ != NULL) {
|
||||||
// Actually fontinfo_id is an index into the shape_table_ and it
|
// Two possible cases:
|
||||||
// contains a list of unchar_id/font_id pairs.
|
// 1. Flat shapetable. All unichar-ids of the shapes referenced by
|
||||||
int shape_id = fontinfo_id;
|
// int_result->fonts are the same. In this case build a new vector of
|
||||||
const Shape& shape = shape_table_->GetShape(fontinfo_id);
|
// mapped fonts and replace the fonts in int_result.
|
||||||
double min_rating = 0.0;
|
// 2. Multi-unichar shapetable. Variable unichars in the shapes referenced
|
||||||
for (int c = 0; c < shape.size(); ++c) {
|
// by int_result. In this case, build a vector of UnicharRating to
|
||||||
int unichar_id = shape[c].unichar_id;
|
// gather together different font-ids for each unichar. Also covers case1.
|
||||||
fontinfo_id = shape[c].font_ids[0];
|
GenericVector<UnicharRating> mapped_results;
|
||||||
if (shape[c].font_ids.size() > 1)
|
for (int f = 0; f < int_result->fonts.size(); ++f) {
|
||||||
fontinfo_id2 = shape[c].font_ids[1];
|
int shape_id = int_result->fonts[f].fontinfo_id;
|
||||||
else if (fontinfo_id2 != kBlankFontinfoId)
|
const Shape& shape = shape_table_->GetShape(shape_id);
|
||||||
fontinfo_id2 = shape_table_->GetShape(fontinfo_id2)[0].font_ids[0];
|
for (int c = 0; c < shape.size(); ++c) {
|
||||||
double rating = ComputeCorrectedRating(debug, unichar_id, cp_rating,
|
int unichar_id = shape[c].unichar_id;
|
||||||
int_result.Rating,
|
if (!unicharset.get_enabled(unichar_id)) continue;
|
||||||
int_result.FeatureMisses,
|
// Find the mapped_result for unichar_id.
|
||||||
bottom, top, blob_length,
|
int r = 0;
|
||||||
matcher_multiplier, cn_factors);
|
for (r = 0; r < mapped_results.size() &&
|
||||||
if (c == 0 || rating < min_rating)
|
mapped_results[r].unichar_id != unichar_id; ++r) {}
|
||||||
min_rating = rating;
|
if (r == mapped_results.size()) {
|
||||||
if (unicharset.get_enabled(unichar_id)) {
|
mapped_results.push_back(*int_result);
|
||||||
AddNewResult(final_results, unichar_id, shape_id, rating,
|
mapped_results[r].unichar_id = unichar_id;
|
||||||
classes != NULL, int_result.Config,
|
mapped_results[r].fonts.truncate(0);
|
||||||
fontinfo_id, fontinfo_id2);
|
}
|
||||||
|
for (int i = 0; i < shape[c].font_ids.size(); ++i) {
|
||||||
|
mapped_results[r].fonts.push_back(
|
||||||
|
ScoredFont(shape[c].font_ids[i], int_result->fonts[f].score));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int_result.Rating = min_rating;
|
for (int m = 0; m < mapped_results.size(); ++m) {
|
||||||
|
mapped_results[m].rating =
|
||||||
|
ComputeCorrectedRating(debug, mapped_results[m].unichar_id,
|
||||||
|
cp_rating, int_result->rating,
|
||||||
|
int_result->feature_misses, bottom, top,
|
||||||
|
blob_length, matcher_multiplier, cn_factors);
|
||||||
|
AddNewResult(mapped_results[m], final_results);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
double rating = ComputeCorrectedRating(debug, class_id, cp_rating,
|
|
||||||
int_result.Rating,
|
|
||||||
int_result.FeatureMisses,
|
|
||||||
bottom, top, blob_length,
|
|
||||||
matcher_multiplier, cn_factors);
|
|
||||||
if (unicharset.get_enabled(class_id)) {
|
if (unicharset.get_enabled(class_id)) {
|
||||||
AddNewResult(final_results, class_id, -1, rating,
|
int_result->rating = ComputeCorrectedRating(debug, class_id, cp_rating,
|
||||||
classes != NULL, int_result.Config,
|
int_result->rating,
|
||||||
fontinfo_id, fontinfo_id2);
|
int_result->feature_misses,
|
||||||
|
bottom, top, blob_length,
|
||||||
|
matcher_multiplier, cn_factors);
|
||||||
|
AddNewResult(*int_result, final_results);
|
||||||
}
|
}
|
||||||
int_result.Rating = rating;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applies a set of corrections to the distance im_rating,
|
// Applies a set of corrections to the confidence im_rating,
|
||||||
// including the cn_correction, miss penalty and additional penalty
|
// including the cn_correction, miss penalty and additional penalty
|
||||||
// for non-alnums being vertical misfits. Returns the corrected distance.
|
// for non-alnums being vertical misfits. Returns the corrected confidence.
|
||||||
double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
|
double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
|
||||||
double cp_rating, double im_rating,
|
double cp_rating, double im_rating,
|
||||||
int feature_misses,
|
int feature_misses,
|
||||||
@ -1201,7 +1219,7 @@ double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
|
|||||||
int blob_length, int matcher_multiplier,
|
int blob_length, int matcher_multiplier,
|
||||||
const uinT8* cn_factors) {
|
const uinT8* cn_factors) {
|
||||||
// Compute class feature corrections.
|
// Compute class feature corrections.
|
||||||
double cn_corrected = im_.ApplyCNCorrection(im_rating, blob_length,
|
double cn_corrected = im_.ApplyCNCorrection(1.0 - im_rating, blob_length,
|
||||||
cn_factors[unichar_id],
|
cn_factors[unichar_id],
|
||||||
matcher_multiplier);
|
matcher_multiplier);
|
||||||
double miss_penalty = tessedit_class_miss_scale * feature_misses;
|
double miss_penalty = tessedit_class_miss_scale * feature_misses;
|
||||||
@ -1222,16 +1240,16 @@ double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
|
|||||||
vertical_penalty = classify_misfit_junk_penalty;
|
vertical_penalty = classify_misfit_junk_penalty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
double result =cn_corrected + miss_penalty + vertical_penalty;
|
double result = 1.0 - (cn_corrected + miss_penalty + vertical_penalty);
|
||||||
if (result > WORST_POSSIBLE_RATING)
|
if (result < WORST_POSSIBLE_RATING)
|
||||||
result = WORST_POSSIBLE_RATING;
|
result = WORST_POSSIBLE_RATING;
|
||||||
if (debug) {
|
if (debug) {
|
||||||
tprintf("%s: %2.1f(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n",
|
tprintf("%s: %2.1f%%(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n",
|
||||||
unicharset.id_to_unichar(unichar_id),
|
unicharset.id_to_unichar(unichar_id),
|
||||||
result * 100.0,
|
result * 100.0,
|
||||||
cp_rating * 100.0,
|
cp_rating * 100.0,
|
||||||
im_rating * 100.0,
|
(1.0 - im_rating) * 100.0,
|
||||||
(cn_corrected - im_rating) * 100.0,
|
(cn_corrected - (1.0 - im_rating)) * 100.0,
|
||||||
cn_factors[unichar_id],
|
cn_factors[unichar_id],
|
||||||
miss_penalty * 100.0,
|
miss_penalty * 100.0,
|
||||||
vertical_penalty * 100.0);
|
vertical_penalty * 100.0);
|
||||||
@ -1266,11 +1284,11 @@ UNICHAR_ID *Classify::BaselineClassifier(
|
|||||||
ClearCharNormArray(CharNormArray);
|
ClearCharNormArray(CharNormArray);
|
||||||
|
|
||||||
Results->BlobLength = IntCastRounded(fx_info.Length / kStandardFeatureLength);
|
Results->BlobLength = IntCastRounded(fx_info.Length / kStandardFeatureLength);
|
||||||
PruneClasses(Templates->Templates, int_features.size(), &int_features[0],
|
PruneClasses(Templates->Templates, int_features.size(), -1, &int_features[0],
|
||||||
CharNormArray, BaselineCutoffs, &Results->CPResults);
|
CharNormArray, BaselineCutoffs, &Results->CPResults);
|
||||||
|
|
||||||
if (matcher_debug_level >= 2 || classify_debug_level > 1)
|
if (matcher_debug_level >= 2 || classify_debug_level > 1)
|
||||||
cprintf ("BL Matches = ");
|
tprintf("BL Matches = ");
|
||||||
|
|
||||||
MasterMatcher(Templates->Templates, int_features.size(), &int_features[0],
|
MasterMatcher(Templates->Templates, int_features.size(), &int_features[0],
|
||||||
CharNormArray,
|
CharNormArray,
|
||||||
@ -1278,13 +1296,12 @@ UNICHAR_ID *Classify::BaselineClassifier(
|
|||||||
Blob->bounding_box(), Results->CPResults, Results);
|
Blob->bounding_box(), Results->CPResults, Results);
|
||||||
|
|
||||||
delete [] CharNormArray;
|
delete [] CharNormArray;
|
||||||
CLASS_ID ClassId = Results->best_match.unichar_id;
|
CLASS_ID ClassId = Results->best_unichar_id;
|
||||||
if (ClassId == NO_CLASS)
|
if (ClassId == INVALID_UNICHAR_ID || Results->best_match_index < 0)
|
||||||
return (NULL);
|
return NULL;
|
||||||
/* this is a bug - maybe should return "" */
|
|
||||||
|
|
||||||
return Templates->Class[ClassId]->
|
return Templates->Class[ClassId]->
|
||||||
Config[Results->best_match.config].Perm->Ambigs;
|
Config[Results->match[Results->best_match_index].config].Perm->Ambigs;
|
||||||
} /* BaselineClassifier */
|
} /* BaselineClassifier */
|
||||||
|
|
||||||
|
|
||||||
@ -1318,14 +1335,7 @@ int Classify::CharNormClassifier(TBLOB *blob,
|
|||||||
-1, &unichar_results);
|
-1, &unichar_results);
|
||||||
// Convert results to the format used internally by AdaptiveClassifier.
|
// Convert results to the format used internally by AdaptiveClassifier.
|
||||||
for (int r = 0; r < unichar_results.size(); ++r) {
|
for (int r = 0; r < unichar_results.size(); ++r) {
|
||||||
int unichar_id = unichar_results[r].unichar_id;
|
AddNewResult(unichar_results[r], adapt_results);
|
||||||
// Fonts are listed in order of preference.
|
|
||||||
int font1 = unichar_results[r].fonts.size() >= 1
|
|
||||||
? unichar_results[r].fonts[0] : kBlankFontinfoId;
|
|
||||||
int font2 = unichar_results[r].fonts.size() >= 2
|
|
||||||
? unichar_results[r].fonts[1] : kBlankFontinfoId;
|
|
||||||
float rating = 1.0f - unichar_results[r].rating;
|
|
||||||
AddNewResult(adapt_results, unichar_id, -1, rating, false, 0, font1, font2);
|
|
||||||
}
|
}
|
||||||
return sample.num_features();
|
return sample.num_features();
|
||||||
} /* CharNormClassifier */
|
} /* CharNormClassifier */
|
||||||
@ -1356,7 +1366,7 @@ int Classify::CharNormTrainingSample(bool pruner_only,
|
|||||||
ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array,
|
ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array,
|
||||||
pruner_norm_array);
|
pruner_norm_array);
|
||||||
|
|
||||||
PruneClasses(PreTrainedTemplates, num_features, sample.features(),
|
PruneClasses(PreTrainedTemplates, num_features, keep_this, sample.features(),
|
||||||
pruner_norm_array,
|
pruner_norm_array,
|
||||||
shape_table_ != NULL ? &shapetable_cutoffs_[0] : CharNormCutoffs,
|
shape_table_ != NULL ? &shapetable_cutoffs_[0] : CharNormCutoffs,
|
||||||
&adapt_results->CPResults);
|
&adapt_results->CPResults);
|
||||||
@ -1380,14 +1390,7 @@ int Classify::CharNormTrainingSample(bool pruner_only,
|
|||||||
blob_box, adapt_results->CPResults, adapt_results);
|
blob_box, adapt_results->CPResults, adapt_results);
|
||||||
// Convert master matcher results to output format.
|
// Convert master matcher results to output format.
|
||||||
for (int i = 0; i < adapt_results->match.size(); i++) {
|
for (int i = 0; i < adapt_results->match.size(); i++) {
|
||||||
ScoredClass next = adapt_results->match[i];
|
results->push_back(adapt_results->match[i]);
|
||||||
UnicharRating rating(next.unichar_id, 1.0f - next.rating);
|
|
||||||
if (next.fontinfo_id >= 0) {
|
|
||||||
rating.fonts.push_back(next.fontinfo_id);
|
|
||||||
if (next.fontinfo_id2 >= 0)
|
|
||||||
rating.fonts.push_back(next.fontinfo_id2);
|
|
||||||
}
|
|
||||||
results->push_back(rating);
|
|
||||||
}
|
}
|
||||||
results->sort(&UnicharRating::SortDescendingRating);
|
results->sort(&UnicharRating::SortDescendingRating);
|
||||||
}
|
}
|
||||||
@ -1412,60 +1415,14 @@ int Classify::CharNormTrainingSample(bool pruner_only,
|
|||||||
* @note Exceptions: none
|
* @note Exceptions: none
|
||||||
* @note History: Tue Mar 12 18:36:52 1991, DSJ, Created.
|
* @note History: Tue Mar 12 18:36:52 1991, DSJ, Created.
|
||||||
*/
|
*/
|
||||||
void Classify::ClassifyAsNoise(ADAPT_RESULTS *Results) {
|
void Classify::ClassifyAsNoise(ADAPT_RESULTS *results) {
|
||||||
register FLOAT32 Rating;
|
float rating = results->BlobLength / matcher_avg_noise_size;
|
||||||
|
rating *= rating;
|
||||||
|
rating /= 1.0 + rating;
|
||||||
|
|
||||||
Rating = Results->BlobLength / matcher_avg_noise_size;
|
AddNewResult(UnicharRating(UNICHAR_SPACE, 1.0f - rating), results);
|
||||||
Rating *= Rating;
|
|
||||||
Rating /= 1.0 + Rating;
|
|
||||||
|
|
||||||
AddNewResult(Results, NO_CLASS, -1, Rating, false, -1,
|
|
||||||
kBlankFontinfoId, kBlankFontinfoId);
|
|
||||||
} /* ClassifyAsNoise */
|
} /* ClassifyAsNoise */
|
||||||
} // namespace tesseract
|
|
||||||
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
|
||||||
// Return a pointer to the scored unichar in results, or NULL if not present.
|
|
||||||
ScoredClass *FindScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id) {
|
|
||||||
for (int i = 0; i < results->match.size(); i++) {
|
|
||||||
if (results->match[i].unichar_id == id)
|
|
||||||
return &results->match[i];
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the current rating for a unichar id if we have rated it, defaulting
|
|
||||||
// to WORST_POSSIBLE_RATING.
|
|
||||||
ScoredClass ScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id) {
|
|
||||||
ScoredClass poor_result =
|
|
||||||
{id, -1, WORST_POSSIBLE_RATING, false, -1,
|
|
||||||
kBlankFontinfoId, kBlankFontinfoId};
|
|
||||||
ScoredClass *entry = FindScoredUnichar(results, id);
|
|
||||||
return (entry == NULL) ? poor_result : *entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare character classes by rating as for qsort(3).
|
|
||||||
// For repeatability, use character class id as a tie-breaker.
|
|
||||||
int CompareByRating(const void *arg1, // ScoredClass *class1
|
|
||||||
const void *arg2) { // ScoredClass *class2
|
|
||||||
const ScoredClass *class1 = (const ScoredClass *)arg1;
|
|
||||||
const ScoredClass *class2 = (const ScoredClass *)arg2;
|
|
||||||
|
|
||||||
if (class1->rating < class2->rating)
|
|
||||||
return -1;
|
|
||||||
else if (class1->rating > class2->rating)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (class1->unichar_id < class2->unichar_id)
|
|
||||||
return -1;
|
|
||||||
else if (class1->unichar_id > class2->unichar_id)
|
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
|
||||||
namespace tesseract {
|
|
||||||
/// The function converts the given match ratings to the list of blob
|
/// The function converts the given match ratings to the list of blob
|
||||||
/// choices with ratings and certainties (used by the context checkers).
|
/// choices with ratings and certainties (used by the context checkers).
|
||||||
/// If character fragments are present in the results, this function also makes
|
/// If character fragments are present in the results, this function also makes
|
||||||
@ -1496,11 +1453,9 @@ void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
|
|||||||
|
|
||||||
float best_certainty = -MAX_FLOAT32;
|
float best_certainty = -MAX_FLOAT32;
|
||||||
for (int i = 0; i < Results->match.size(); i++) {
|
for (int i = 0; i < Results->match.size(); i++) {
|
||||||
ScoredClass next = Results->match[i];
|
const UnicharRating& result = Results->match[i];
|
||||||
int fontinfo_id = next.fontinfo_id;
|
bool adapted = result.adapted;
|
||||||
int fontinfo_id2 = next.fontinfo_id2;
|
bool current_is_frag = (unicharset.get_fragment(result.unichar_id) != NULL);
|
||||||
bool adapted = next.adapted;
|
|
||||||
bool current_is_frag = (unicharset.get_fragment(next.unichar_id) != NULL);
|
|
||||||
if (temp_it.length()+1 == max_matches &&
|
if (temp_it.length()+1 == max_matches &&
|
||||||
!contains_nonfrag && current_is_frag) {
|
!contains_nonfrag && current_is_frag) {
|
||||||
continue; // look for a non-fragmented character to fill the
|
continue; // look for a non-fragmented character to fill the
|
||||||
@ -1514,7 +1469,7 @@ void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
|
|||||||
Certainty = -20;
|
Certainty = -20;
|
||||||
Rating = 100; // should be -certainty * real_blob_length
|
Rating = 100; // should be -certainty * real_blob_length
|
||||||
} else {
|
} else {
|
||||||
Rating = Certainty = next.rating;
|
Rating = Certainty = (1.0f - result.rating);
|
||||||
Rating *= rating_scale * Results->BlobLength;
|
Rating *= rating_scale * Results->BlobLength;
|
||||||
Certainty *= -(getDict().certainty_scale);
|
Certainty *= -(getDict().certainty_scale);
|
||||||
}
|
}
|
||||||
@ -1531,14 +1486,16 @@ void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
|
|||||||
}
|
}
|
||||||
|
|
||||||
float min_xheight, max_xheight, yshift;
|
float min_xheight, max_xheight, yshift;
|
||||||
denorm.XHeightRange(next.unichar_id, unicharset, box,
|
denorm.XHeightRange(result.unichar_id, unicharset, box,
|
||||||
&min_xheight, &max_xheight, &yshift);
|
&min_xheight, &max_xheight, &yshift);
|
||||||
temp_it.add_to_end(new BLOB_CHOICE(next.unichar_id, Rating, Certainty,
|
BLOB_CHOICE* choice =
|
||||||
fontinfo_id, fontinfo_id2,
|
new BLOB_CHOICE(result.unichar_id, Rating, Certainty,
|
||||||
unicharset.get_script(next.unichar_id),
|
unicharset.get_script(result.unichar_id),
|
||||||
min_xheight, max_xheight, yshift,
|
min_xheight, max_xheight, yshift,
|
||||||
adapted ? BCC_ADAPTED_CLASSIFIER
|
adapted ? BCC_ADAPTED_CLASSIFIER
|
||||||
: BCC_STATIC_CLASSIFIER));
|
: BCC_STATIC_CLASSIFIER);
|
||||||
|
choice->set_fonts(result.fonts);
|
||||||
|
temp_it.add_to_end(choice);
|
||||||
contains_nonfrag |= !current_is_frag; // update contains_nonfrag
|
contains_nonfrag |= !current_is_frag; // update contains_nonfrag
|
||||||
choices_length++;
|
choices_length++;
|
||||||
if (choices_length >= max_matches) break;
|
if (choices_length >= max_matches) break;
|
||||||
@ -1562,17 +1519,13 @@ void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
|
|||||||
void Classify::DebugAdaptiveClassifier(TBLOB *blob,
|
void Classify::DebugAdaptiveClassifier(TBLOB *blob,
|
||||||
ADAPT_RESULTS *Results) {
|
ADAPT_RESULTS *Results) {
|
||||||
if (static_classifier_ == NULL) return;
|
if (static_classifier_ == NULL) return;
|
||||||
for (int i = 0; i < Results->match.size(); i++) {
|
|
||||||
if (i == 0 || Results->match[i].rating < Results->best_match.rating)
|
|
||||||
Results->best_match = Results->match[i];
|
|
||||||
}
|
|
||||||
INT_FX_RESULT_STRUCT fx_info;
|
INT_FX_RESULT_STRUCT fx_info;
|
||||||
GenericVector<INT_FEATURE_STRUCT> bl_features;
|
GenericVector<INT_FEATURE_STRUCT> bl_features;
|
||||||
TrainingSample* sample =
|
TrainingSample* sample =
|
||||||
BlobToTrainingSample(*blob, false, &fx_info, &bl_features);
|
BlobToTrainingSample(*blob, false, &fx_info, &bl_features);
|
||||||
if (sample == NULL) return;
|
if (sample == NULL) return;
|
||||||
static_classifier_->DebugDisplay(*sample, blob->denorm().pix(),
|
static_classifier_->DebugDisplay(*sample, blob->denorm().pix(),
|
||||||
Results->best_match.unichar_id);
|
Results->best_unichar_id);
|
||||||
} /* DebugAdaptiveClassifier */
|
} /* DebugAdaptiveClassifier */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -1615,7 +1568,8 @@ void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) {
|
|||||||
} else {
|
} else {
|
||||||
Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
|
Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
|
||||||
AdaptedTemplates, Results);
|
AdaptedTemplates, Results);
|
||||||
if ((!Results->match.empty() && MarginalMatch(Results->best_match.rating) &&
|
if ((!Results->match.empty() &&
|
||||||
|
MarginalMatch(Results->best_rating, matcher_great_threshold) &&
|
||||||
!tess_bn_matching) ||
|
!tess_bn_matching) ||
|
||||||
Results->match.empty()) {
|
Results->match.empty()) {
|
||||||
CharNormClassifier(Blob, *sample, Results);
|
CharNormClassifier(Blob, *sample, Results);
|
||||||
@ -1674,7 +1628,7 @@ UNICHAR_ID *Classify::GetAmbiguities(TBLOB *Blob,
|
|||||||
CharNormClassifier(Blob, *sample, Results);
|
CharNormClassifier(Blob, *sample, Results);
|
||||||
delete sample;
|
delete sample;
|
||||||
RemoveBadMatches(Results);
|
RemoveBadMatches(Results);
|
||||||
Results->match.sort(CompareByRating);
|
Results->match.sort(&UnicharRating::SortDescendingRating);
|
||||||
|
|
||||||
/* copy the class id's into an string of ambiguities - don't copy if
|
/* copy the class id's into an string of ambiguities - don't copy if
|
||||||
the correct class is the only class id matched */
|
the correct class is the only class id matched */
|
||||||
@ -2094,14 +2048,11 @@ namespace tesseract {
|
|||||||
* @note Exceptions: none
|
* @note Exceptions: none
|
||||||
* @note History: Mon Mar 18 09:24:53 1991, DSJ, Created.
|
* @note History: Mon Mar 18 09:24:53 1991, DSJ, Created.
|
||||||
*/
|
*/
|
||||||
void Classify::PrintAdaptiveMatchResults(FILE *File, ADAPT_RESULTS *Results) {
|
void Classify::PrintAdaptiveMatchResults(const ADAPT_RESULTS& results) {
|
||||||
for (int i = 0; i < Results->match.size(); ++i) {
|
for (int i = 0; i < results.match.size(); ++i) {
|
||||||
tprintf("%s(%d), shape %d, %.2f ",
|
tprintf("%s ", unicharset.debug_str(results.match[i].unichar_id).string());
|
||||||
unicharset.debug_str(Results->match[i].unichar_id).string(),
|
results.match[i].Print();
|
||||||
Results->match[i].unichar_id, Results->match[i].shape_id,
|
|
||||||
Results->match[i].rating * 100.0);
|
|
||||||
}
|
}
|
||||||
tprintf("\n");
|
|
||||||
} /* PrintAdaptiveMatchResults */
|
} /* PrintAdaptiveMatchResults */
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
@ -2124,40 +2075,49 @@ void Classify::RemoveBadMatches(ADAPT_RESULTS *Results) {
|
|||||||
int Next, NextGood;
|
int Next, NextGood;
|
||||||
FLOAT32 BadMatchThreshold;
|
FLOAT32 BadMatchThreshold;
|
||||||
static const char* romans = "i v x I V X";
|
static const char* romans = "i v x I V X";
|
||||||
BadMatchThreshold = Results->best_match.rating + matcher_bad_match_pad;
|
BadMatchThreshold = Results->best_rating - matcher_bad_match_pad;
|
||||||
|
|
||||||
if (classify_bln_numeric_mode) {
|
if (classify_bln_numeric_mode) {
|
||||||
UNICHAR_ID unichar_id_one = unicharset.contains_unichar("1") ?
|
UNICHAR_ID unichar_id_one = unicharset.contains_unichar("1") ?
|
||||||
unicharset.unichar_to_id("1") : -1;
|
unicharset.unichar_to_id("1") : -1;
|
||||||
UNICHAR_ID unichar_id_zero = unicharset.contains_unichar("0") ?
|
UNICHAR_ID unichar_id_zero = unicharset.contains_unichar("0") ?
|
||||||
unicharset.unichar_to_id("0") : -1;
|
unicharset.unichar_to_id("0") : -1;
|
||||||
ScoredClass scored_one = ScoredUnichar(Results, unichar_id_one);
|
float scored_one = ScoredUnichar(unichar_id_one, *Results);
|
||||||
ScoredClass scored_zero = ScoredUnichar(Results, unichar_id_zero);
|
float scored_zero = ScoredUnichar(unichar_id_zero, *Results);
|
||||||
|
|
||||||
for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
|
for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
|
||||||
if (Results->match[Next].rating <= BadMatchThreshold) {
|
const UnicharRating& match = Results->match[Next];
|
||||||
ScoredClass match = Results->match[Next];
|
if (match.rating >= BadMatchThreshold) {
|
||||||
if (!unicharset.get_isalpha(match.unichar_id) ||
|
if (!unicharset.get_isalpha(match.unichar_id) ||
|
||||||
strstr(romans,
|
strstr(romans,
|
||||||
unicharset.id_to_unichar(match.unichar_id)) != NULL) {
|
unicharset.id_to_unichar(match.unichar_id)) != NULL) {
|
||||||
Results->match[NextGood++] = Results->match[Next];
|
|
||||||
} else if (unicharset.eq(match.unichar_id, "l") &&
|
} else if (unicharset.eq(match.unichar_id, "l") &&
|
||||||
scored_one.rating >= BadMatchThreshold) {
|
scored_one < BadMatchThreshold) {
|
||||||
Results->match[NextGood] = scored_one;
|
Results->match[Next].unichar_id = unichar_id_one;
|
||||||
Results->match[NextGood].rating = match.rating;
|
|
||||||
NextGood++;
|
|
||||||
} else if (unicharset.eq(match.unichar_id, "O") &&
|
} else if (unicharset.eq(match.unichar_id, "O") &&
|
||||||
scored_zero.rating >= BadMatchThreshold) {
|
scored_zero < BadMatchThreshold) {
|
||||||
Results->match[NextGood] = scored_zero;
|
Results->match[Next].unichar_id = unichar_id_zero;
|
||||||
Results->match[NextGood].rating = match.rating;
|
} else {
|
||||||
NextGood++;
|
Results->match[Next].unichar_id = INVALID_UNICHAR_ID; // Don't copy.
|
||||||
|
}
|
||||||
|
if (Results->match[Next].unichar_id != INVALID_UNICHAR_ID) {
|
||||||
|
if (NextGood == Next) {
|
||||||
|
++NextGood;
|
||||||
|
} else {
|
||||||
|
Results->match[NextGood++] = Results->match[Next];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
|
for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
|
||||||
if (Results->match[Next].rating <= BadMatchThreshold)
|
if (Results->match[Next].rating >= BadMatchThreshold) {
|
||||||
Results->match[NextGood++] = Results->match[Next];
|
if (NextGood == Next) {
|
||||||
|
++NextGood;
|
||||||
|
} else {
|
||||||
|
Results->match[NextGood++] = Results->match[Next];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Results->match.truncate(NextGood);
|
Results->match.truncate(NextGood);
|
||||||
@ -2184,18 +2144,24 @@ void Classify::RemoveExtraPuncs(ADAPT_RESULTS *Results) {
|
|||||||
punc_count = 0;
|
punc_count = 0;
|
||||||
digit_count = 0;
|
digit_count = 0;
|
||||||
for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
|
for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
|
||||||
ScoredClass match = Results->match[Next];
|
const UnicharRating& match = Results->match[Next];
|
||||||
|
bool keep = true;
|
||||||
if (strstr(punc_chars,
|
if (strstr(punc_chars,
|
||||||
unicharset.id_to_unichar(match.unichar_id)) != NULL) {
|
unicharset.id_to_unichar(match.unichar_id)) != NULL) {
|
||||||
if (punc_count < 2)
|
if (punc_count >= 2)
|
||||||
Results->match[NextGood++] = match;
|
keep = false;
|
||||||
punc_count++;
|
punc_count++;
|
||||||
} else {
|
} else {
|
||||||
if (strstr(digit_chars,
|
if (strstr(digit_chars,
|
||||||
unicharset.id_to_unichar(match.unichar_id)) != NULL) {
|
unicharset.id_to_unichar(match.unichar_id)) != NULL) {
|
||||||
if (digit_count < 1)
|
if (digit_count >= 1)
|
||||||
Results->match[NextGood++] = match;
|
keep = false;
|
||||||
digit_count++;
|
digit_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keep) {
|
||||||
|
if (NextGood == Next) {
|
||||||
|
++NextGood;
|
||||||
} else {
|
} else {
|
||||||
Results->match[NextGood++] = match;
|
Results->match[NextGood++] = match;
|
||||||
}
|
}
|
||||||
@ -2252,7 +2218,7 @@ void Classify::ShowBestMatchFor(int shape_id,
|
|||||||
tprintf("Illegal blob (char norm features)!\n");
|
tprintf("Illegal blob (char norm features)!\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
INT_RESULT_STRUCT cn_result;
|
UnicharRating cn_result;
|
||||||
classify_norm_method.set_value(character);
|
classify_norm_method.set_value(character);
|
||||||
im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
|
im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
|
||||||
AllProtosOn, AllConfigsOn,
|
AllProtosOn, AllConfigsOn,
|
||||||
@ -2260,7 +2226,7 @@ void Classify::ShowBestMatchFor(int shape_id,
|
|||||||
classify_adapt_feature_threshold, NO_DEBUG,
|
classify_adapt_feature_threshold, NO_DEBUG,
|
||||||
matcher_debug_separate_windows);
|
matcher_debug_separate_windows);
|
||||||
tprintf("\n");
|
tprintf("\n");
|
||||||
config_mask = 1 << cn_result.Config;
|
config_mask = 1 << cn_result.config;
|
||||||
|
|
||||||
tprintf("Static Shape ID: %d\n", shape_id);
|
tprintf("Static Shape ID: %d\n", shape_id);
|
||||||
ShowMatchDisplay();
|
ShowMatchDisplay();
|
||||||
|
@ -20,63 +20,32 @@
|
|||||||
Include Files and Type Defines
|
Include Files and Type Defines
|
||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
#include "blobclass.h"
|
#include "blobclass.h"
|
||||||
#include "extract.h"
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "classify.h"
|
||||||
#include "efio.h"
|
#include "efio.h"
|
||||||
#include "featdefs.h"
|
#include "featdefs.h"
|
||||||
#include "callcpp.h"
|
#include "mf.h"
|
||||||
|
#include "normfeat.h"
|
||||||
#include <math.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <signal.h>
|
|
||||||
|
|
||||||
#define MAXFILENAME 80
|
|
||||||
#define MAXMATCHES 10
|
|
||||||
|
|
||||||
static const char kUnknownFontName[] = "UnknownFont";
|
static const char kUnknownFontName[] = "UnknownFont";
|
||||||
|
|
||||||
STRING_VAR(classify_font_name, kUnknownFontName,
|
STRING_VAR(classify_font_name, kUnknownFontName,
|
||||||
"Default font name to be used in training");
|
"Default font name to be used in training");
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------
|
namespace tesseract {
|
||||||
Global Data Definitions and Declarations
|
|
||||||
----------------------------------------------------------------------------**/
|
|
||||||
/* name of current image file being processed */
|
|
||||||
extern char imagefile[];
|
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------
|
/**----------------------------------------------------------------------------
|
||||||
Public Code
|
Public Code
|
||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
|
// Finds the name of the training font and returns it in fontname, by cutting
|
||||||
/*---------------------------------------------------------------------------*/
|
// it out based on the expectation that the filename is of the form:
|
||||||
// As all TBLOBs, Blob is in baseline normalized coords.
|
// /path/to/dir/[lang].[fontname].exp[num]
|
||||||
// See SetupBLCNDenorms in intfx.cpp for other args.
|
// The [lang], [fontname] and [num] fields should not have '.' characters.
|
||||||
void LearnBlob(const FEATURE_DEFS_STRUCT &FeatureDefs, const STRING& filename,
|
// If the global parameter classify_font_name is set, its value is used instead.
|
||||||
TBLOB * Blob, const DENORM& bl_denorm, const DENORM& cn_denorm,
|
void ExtractFontName(const STRING& filename, STRING* fontname) {
|
||||||
const INT_FX_RESULT_STRUCT& fx_info, const char* BlobText) {
|
*fontname = classify_font_name;
|
||||||
/*
|
if (*fontname == kUnknownFontName) {
|
||||||
** Parameters:
|
|
||||||
** Blob blob whose micro-features are to be learned
|
|
||||||
** Row row of text that blob came from
|
|
||||||
** BlobText text that corresponds to blob
|
|
||||||
** TextLength number of characters in blob
|
|
||||||
** Globals:
|
|
||||||
** imagefile base filename of the page being learned
|
|
||||||
** classify_font_name
|
|
||||||
** name of font currently being trained on
|
|
||||||
** Operation:
|
|
||||||
** Extract micro-features from the specified blob and append
|
|
||||||
** them to the appropriate file.
|
|
||||||
** Return: none
|
|
||||||
** Exceptions: none
|
|
||||||
** History: 7/28/89, DSJ, Created.
|
|
||||||
*/
|
|
||||||
#define TRAIN_SUFFIX ".tr"
|
|
||||||
static FILE *FeatureFile = NULL;
|
|
||||||
STRING Filename(filename);
|
|
||||||
|
|
||||||
// If no fontname was set, try to extract it from the filename
|
|
||||||
STRING CurrFontName = classify_font_name;
|
|
||||||
if (CurrFontName == kUnknownFontName) {
|
|
||||||
// filename is expected to be of the form [lang].[fontname].exp[num]
|
// filename is expected to be of the form [lang].[fontname].exp[num]
|
||||||
// The [lang], [fontname] and [num] fields should not have '.' characters.
|
// The [lang], [fontname] and [num] fields should not have '.' characters.
|
||||||
const char *basename = strrchr(filename.string(), '/');
|
const char *basename = strrchr(filename.string(), '/');
|
||||||
@ -84,47 +53,56 @@ void LearnBlob(const FEATURE_DEFS_STRUCT &FeatureDefs, const STRING& filename,
|
|||||||
const char *lastdot = strrchr(filename.string(), '.');
|
const char *lastdot = strrchr(filename.string(), '.');
|
||||||
if (firstdot != lastdot && firstdot != NULL && lastdot != NULL) {
|
if (firstdot != lastdot && firstdot != NULL && lastdot != NULL) {
|
||||||
++firstdot;
|
++firstdot;
|
||||||
CurrFontName = firstdot;
|
*fontname = firstdot;
|
||||||
CurrFontName[lastdot - firstdot] = '\0';
|
fontname->truncate_at(lastdot - firstdot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if a feature file is not yet open, open it
|
/*---------------------------------------------------------------------------*/
|
||||||
// the name of the file is the name of the image plus TRAIN_SUFFIX
|
// Extracts features from the given blob and saves them in the tr_file_data_
|
||||||
if (FeatureFile == NULL) {
|
// member variable.
|
||||||
Filename += TRAIN_SUFFIX;
|
// fontname: Name of font that this blob was printed in.
|
||||||
FeatureFile = Efopen(Filename.string(), "wb");
|
// cn_denorm: Character normalization transformation to apply to the blob.
|
||||||
cprintf("TRAINING ... Font name = %s\n", CurrFontName.string());
|
// fx_info: Character normalization parameters computed with cn_denorm.
|
||||||
}
|
// blob_text: Ground truth text for the blob.
|
||||||
|
void Classify::LearnBlob(const STRING& fontname, TBLOB* blob,
|
||||||
|
const DENORM& cn_denorm,
|
||||||
|
const INT_FX_RESULT_STRUCT& fx_info,
|
||||||
|
const char* blob_text) {
|
||||||
|
CHAR_DESC CharDesc = NewCharDescription(feature_defs_);
|
||||||
|
CharDesc->FeatureSets[0] = ExtractMicros(blob, cn_denorm);
|
||||||
|
CharDesc->FeatureSets[1] = ExtractCharNormFeatures(fx_info);
|
||||||
|
CharDesc->FeatureSets[2] = ExtractIntCNFeatures(*blob, fx_info);
|
||||||
|
CharDesc->FeatureSets[3] = ExtractIntGeoFeatures(*blob, fx_info);
|
||||||
|
|
||||||
LearnBlob(FeatureDefs, FeatureFile, Blob, bl_denorm, cn_denorm, fx_info,
|
if (ValidCharDescription(feature_defs_, CharDesc)) {
|
||||||
BlobText, CurrFontName.string());
|
// Label the features with a class name and font name.
|
||||||
} // LearnBlob
|
tr_file_data_ += "\n";
|
||||||
|
tr_file_data_ += fontname;
|
||||||
void LearnBlob(const FEATURE_DEFS_STRUCT &FeatureDefs, FILE* FeatureFile,
|
tr_file_data_ += " ";
|
||||||
TBLOB* Blob, const DENORM& bl_denorm, const DENORM& cn_denorm,
|
tr_file_data_ += blob_text;
|
||||||
const INT_FX_RESULT_STRUCT& fx_info,
|
tr_file_data_ += "\n";
|
||||||
const char* BlobText, const char* FontName) {
|
|
||||||
CHAR_DESC CharDesc;
|
|
||||||
|
|
||||||
ASSERT_HOST(FeatureFile != NULL);
|
|
||||||
|
|
||||||
CharDesc = ExtractBlobFeatures(FeatureDefs, bl_denorm, cn_denorm, fx_info,
|
|
||||||
Blob);
|
|
||||||
if (CharDesc == NULL) {
|
|
||||||
cprintf("LearnBLob: CharDesc was NULL. Aborting.\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ValidCharDescription(FeatureDefs, CharDesc)) {
|
|
||||||
// label the features with a class name and font name
|
|
||||||
fprintf(FeatureFile, "\n%s %s\n", FontName, BlobText);
|
|
||||||
|
|
||||||
// write micro-features to file and clean up
|
// write micro-features to file and clean up
|
||||||
WriteCharDescription(FeatureDefs, FeatureFile, CharDesc);
|
WriteCharDescription(feature_defs_, CharDesc, &tr_file_data_);
|
||||||
} else {
|
} else {
|
||||||
tprintf("Blob learned was invalid!\n");
|
tprintf("Blob learned was invalid!\n");
|
||||||
}
|
}
|
||||||
FreeCharDescription(CharDesc);
|
FreeCharDescription(CharDesc);
|
||||||
|
|
||||||
} // LearnBlob
|
} // LearnBlob
|
||||||
|
|
||||||
|
// Writes stored training data to a .tr file based on the given filename.
|
||||||
|
// Returns false on error.
|
||||||
|
bool Classify::WriteTRFile(const STRING& filename) {
|
||||||
|
STRING tr_filename = filename + ".tr";
|
||||||
|
FILE* fp = Efopen(tr_filename.string(), "wb");
|
||||||
|
int len = tr_file_data_.length();
|
||||||
|
bool result =
|
||||||
|
fwrite(&tr_file_data_[0], sizeof(tr_file_data_[0]), len, fp) == len;
|
||||||
|
fclose(fp);
|
||||||
|
tr_file_data_.truncate_at(0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tesseract.
|
||||||
|
@ -21,9 +21,7 @@
|
|||||||
/**----------------------------------------------------------------------------
|
/**----------------------------------------------------------------------------
|
||||||
Include Files and Type Defines
|
Include Files and Type Defines
|
||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
#include "featdefs.h"
|
#include "strngs.h"
|
||||||
#include "oldlist.h"
|
|
||||||
#include "blobs.h"
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------
|
/*---------------------------------------------------------------------------
|
||||||
Macros
|
Macros
|
||||||
@ -39,18 +37,14 @@
|
|||||||
/**----------------------------------------------------------------------------
|
/**----------------------------------------------------------------------------
|
||||||
Public Function Prototypes
|
Public Function Prototypes
|
||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
void LearnBlob(const FEATURE_DEFS_STRUCT &FeatureDefs, const STRING& filename,
|
namespace tesseract {
|
||||||
TBLOB * Blob, const DENORM& bl_denorm, const DENORM& cn_denorm,
|
// Finds the name of the training font and returns it in fontname, by cutting
|
||||||
const INT_FX_RESULT_STRUCT& fx_info,
|
// it out based on the expectation that the filename is of the form:
|
||||||
const char* BlobText);
|
// /path/to/dir/[lang].[fontname].exp[num]
|
||||||
|
// The [lang], [fontname] and [num] fields should not have '.' characters.
|
||||||
|
// If the global parameter classify_font_name is set, its value is used instead.
|
||||||
|
void ExtractFontName(const STRING& filename, STRING* fontname);
|
||||||
|
|
||||||
void LearnBlob(const FEATURE_DEFS_STRUCT &FeatureDefs, FILE* File, TBLOB* Blob,
|
} // namespace tesseract.
|
||||||
const DENORM& bl_denorm, const DENORM& cn_denorm,
|
|
||||||
const INT_FX_RESULT_STRUCT& fx_info,
|
|
||||||
const char* BlobText, const char* FontName);
|
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------
|
|
||||||
Global Data Definitions and Declarations
|
|
||||||
----------------------------------------------------------------------------**/
|
|
||||||
/*parameter used to turn on/off output of recognized chars to the screen */
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -217,7 +217,7 @@ void Classify::AddLargeSpeckleTo(int blob_length, BLOB_CHOICE_LIST *choices) {
|
|||||||
(rating_scale * blob_length);
|
(rating_scale * blob_length);
|
||||||
}
|
}
|
||||||
BLOB_CHOICE* blob_choice = new BLOB_CHOICE(UNICHAR_SPACE, rating, certainty,
|
BLOB_CHOICE* blob_choice = new BLOB_CHOICE(UNICHAR_SPACE, rating, certainty,
|
||||||
-1, -1, 0, 0, MAX_FLOAT32, 0,
|
-1, 0.0f, MAX_FLOAT32, 0,
|
||||||
BCC_SPECKLE_CLASSIFIER);
|
BCC_SPECKLE_CLASSIFIER);
|
||||||
bc_it.add_to_end(blob_choice);
|
bc_it.add_to_end(blob_choice);
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "dict.h"
|
#include "dict.h"
|
||||||
#include "featdefs.h"
|
#include "featdefs.h"
|
||||||
#include "fontinfo.h"
|
#include "fontinfo.h"
|
||||||
|
#include "imagedata.h"
|
||||||
#include "intfx.h"
|
#include "intfx.h"
|
||||||
#include "intmatcher.h"
|
#include "intmatcher.h"
|
||||||
#include "normalis.h"
|
#include "normalis.h"
|
||||||
@ -97,9 +98,8 @@ class Classify : public CCStruct {
|
|||||||
// results (output) Sorted Array of pruned classes.
|
// results (output) Sorted Array of pruned classes.
|
||||||
// Array must be sized to take the maximum possible
|
// Array must be sized to take the maximum possible
|
||||||
// number of outputs : int_templates->NumClasses.
|
// number of outputs : int_templates->NumClasses.
|
||||||
int PruneClasses(const INT_TEMPLATES_STRUCT* int_templates,
|
int PruneClasses(const INT_TEMPLATES_STRUCT* int_templates, int num_features,
|
||||||
int num_features,
|
int keep_this, const INT_FEATURE_STRUCT* features,
|
||||||
const INT_FEATURE_STRUCT* features,
|
|
||||||
const uinT8* normalization_factors,
|
const uinT8* normalization_factors,
|
||||||
const uinT16* expected_num_features,
|
const uinT16* expected_num_features,
|
||||||
GenericVector<CP_RESULT_STRUCT>* results);
|
GenericVector<CP_RESULT_STRUCT>* results);
|
||||||
@ -119,25 +119,25 @@ class Classify : public CCStruct {
|
|||||||
const UNICHARSET& target_unicharset);
|
const UNICHARSET& target_unicharset);
|
||||||
/* adaptmatch.cpp ***********************************************************/
|
/* adaptmatch.cpp ***********************************************************/
|
||||||
|
|
||||||
// Learn the given word using its chopped_word, seam_array, denorm,
|
// Learns the given word using its chopped_word, seam_array, denorm,
|
||||||
// box_word, best_state, and correct_text to learn both correctly and
|
// box_word, best_state, and correct_text to learn both correctly and
|
||||||
// incorrectly segmented blobs. If filename is not NULL, then LearnBlob
|
// incorrectly segmented blobs. If fontname is not NULL, then LearnBlob
|
||||||
// is called and the data will be written to a file for static training.
|
// is called and the data will be saved in an internal buffer.
|
||||||
// Otherwise AdaptToBlob is called for adaption within a document.
|
// Otherwise AdaptToBlob is called for adaption within a document.
|
||||||
void LearnWord(const char* filename, WERD_RES *word);
|
void LearnWord(const char* fontname, WERD_RES* word);
|
||||||
|
|
||||||
// Builds a blob of length fragments, from the word, starting at start,
|
// Builds a blob of length fragments, from the word, starting at start,
|
||||||
// and then learn it, as having the given correct_text.
|
// and then learns it, as having the given correct_text.
|
||||||
// If filename is not NULL, then LearnBlob
|
// If fontname is not NULL, then LearnBlob is called and the data will be
|
||||||
// is called and the data will be written to a file for static training.
|
// saved in an internal buffer for static training.
|
||||||
// Otherwise AdaptToBlob is called for adaption within a document.
|
// Otherwise AdaptToBlob is called for adaption within a document.
|
||||||
// threshold is a magic number required by AdaptToChar and generated by
|
// threshold is a magic number required by AdaptToChar and generated by
|
||||||
// GetAdaptThresholds.
|
// ComputeAdaptionThresholds.
|
||||||
// Although it can be partly inferred from the string, segmentation is
|
// Although it can be partly inferred from the string, segmentation is
|
||||||
// provided to explicitly clarify the character segmentation.
|
// provided to explicitly clarify the character segmentation.
|
||||||
void LearnPieces(const char* filename, int start, int length,
|
void LearnPieces(const char* fontname, int start, int length, float threshold,
|
||||||
float threshold, CharSegmentationType segmentation,
|
CharSegmentationType segmentation, const char* correct_text,
|
||||||
const char* correct_text, WERD_RES *word);
|
WERD_RES* word);
|
||||||
void InitAdaptiveClassifier(bool load_pre_trained_templates);
|
void InitAdaptiveClassifier(bool load_pre_trained_templates);
|
||||||
void InitAdaptedClass(TBLOB *Blob,
|
void InitAdaptedClass(TBLOB *Blob,
|
||||||
CLASS_ID ClassId,
|
CLASS_ID ClassId,
|
||||||
@ -174,7 +174,7 @@ class Classify : public CCStruct {
|
|||||||
int blob_length,
|
int blob_length,
|
||||||
int matcher_multiplier,
|
int matcher_multiplier,
|
||||||
const uinT8* cn_factors,
|
const uinT8* cn_factors,
|
||||||
INT_RESULT_STRUCT& int_result,
|
UnicharRating* int_result,
|
||||||
ADAPT_RESULTS* final_results);
|
ADAPT_RESULTS* final_results);
|
||||||
// Applies a set of corrections to the distance im_rating,
|
// Applies a set of corrections to the distance im_rating,
|
||||||
// including the cn_correction, miss penalty and additional penalty
|
// including the cn_correction, miss penalty and additional penalty
|
||||||
@ -187,14 +187,7 @@ class Classify : public CCStruct {
|
|||||||
void ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
|
void ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
|
||||||
ADAPT_RESULTS *Results,
|
ADAPT_RESULTS *Results,
|
||||||
BLOB_CHOICE_LIST *Choices);
|
BLOB_CHOICE_LIST *Choices);
|
||||||
void AddNewResult(ADAPT_RESULTS *results,
|
void AddNewResult(const UnicharRating& new_result, ADAPT_RESULTS *results);
|
||||||
CLASS_ID class_id,
|
|
||||||
int shape_id,
|
|
||||||
FLOAT32 rating,
|
|
||||||
bool adapted,
|
|
||||||
int config,
|
|
||||||
int fontinfo_id,
|
|
||||||
int fontinfo_id2);
|
|
||||||
int GetAdaptiveFeatures(TBLOB *Blob,
|
int GetAdaptiveFeatures(TBLOB *Blob,
|
||||||
INT_FEATURE_ARRAY IntFeatures,
|
INT_FEATURE_ARRAY IntFeatures,
|
||||||
FEATURE_SET *FloatFeatures);
|
FEATURE_SET *FloatFeatures);
|
||||||
@ -219,7 +212,7 @@ class Classify : public CCStruct {
|
|||||||
CLASS_ID ClassId,
|
CLASS_ID ClassId,
|
||||||
int ConfigId,
|
int ConfigId,
|
||||||
TBLOB *Blob);
|
TBLOB *Blob);
|
||||||
void PrintAdaptiveMatchResults(FILE *File, ADAPT_RESULTS *Results);
|
void PrintAdaptiveMatchResults(const ADAPT_RESULTS& results);
|
||||||
void RemoveExtraPuncs(ADAPT_RESULTS *Results);
|
void RemoveExtraPuncs(ADAPT_RESULTS *Results);
|
||||||
void RemoveBadMatches(ADAPT_RESULTS *Results);
|
void RemoveBadMatches(ADAPT_RESULTS *Results);
|
||||||
void SetAdaptiveThreshold(FLOAT32 Threshold);
|
void SetAdaptiveThreshold(FLOAT32 Threshold);
|
||||||
@ -361,7 +354,22 @@ class Classify : public CCStruct {
|
|||||||
FEATURE_SET ExtractOutlineFeatures(TBLOB *Blob);
|
FEATURE_SET ExtractOutlineFeatures(TBLOB *Blob);
|
||||||
/* picofeat.cpp ***********************************************************/
|
/* picofeat.cpp ***********************************************************/
|
||||||
FEATURE_SET ExtractPicoFeatures(TBLOB *Blob);
|
FEATURE_SET ExtractPicoFeatures(TBLOB *Blob);
|
||||||
|
FEATURE_SET ExtractIntCNFeatures(const TBLOB& blob,
|
||||||
|
const INT_FX_RESULT_STRUCT& fx_info);
|
||||||
|
FEATURE_SET ExtractIntGeoFeatures(const TBLOB& blob,
|
||||||
|
const INT_FX_RESULT_STRUCT& fx_info);
|
||||||
|
/* blobclass.cpp ***********************************************************/
|
||||||
|
// Extracts features from the given blob and saves them in the tr_file_data_
|
||||||
|
// member variable.
|
||||||
|
// fontname: Name of font that this blob was printed in.
|
||||||
|
// cn_denorm: Character normalization transformation to apply to the blob.
|
||||||
|
// fx_info: Character normalization parameters computed with cn_denorm.
|
||||||
|
// blob_text: Ground truth text for the blob.
|
||||||
|
void LearnBlob(const STRING& fontname, TBLOB* Blob, const DENORM& cn_denorm,
|
||||||
|
const INT_FX_RESULT_STRUCT& fx_info, const char* blob_text);
|
||||||
|
// Writes stored training data to a .tr file based on the given filename.
|
||||||
|
// Returns false on error.
|
||||||
|
bool WriteTRFile(const STRING& filename);
|
||||||
|
|
||||||
// Member variables.
|
// Member variables.
|
||||||
|
|
||||||
@ -498,6 +506,9 @@ class Classify : public CCStruct {
|
|||||||
/* variables used to hold performance statistics */
|
/* variables used to hold performance statistics */
|
||||||
int NumAdaptationsFailed;
|
int NumAdaptationsFailed;
|
||||||
|
|
||||||
|
// Training data gathered here for all the images in a document.
|
||||||
|
STRING tr_file_data_;
|
||||||
|
|
||||||
// Expected number of features in the class pruner, used to penalize
|
// Expected number of features in the class pruner, used to penalize
|
||||||
// unknowns that have too few features (like a c being classified as e) so
|
// unknowns that have too few features (like a c being classified as e) so
|
||||||
// it doesn't recognize everything as '@' or '#'.
|
// it doesn't recognize everything as '@' or '#'.
|
||||||
|
1535
classify/cluster.cpp
1535
classify/cluster.cpp
File diff suppressed because it is too large
Load Diff
@ -1,32 +0,0 @@
|
|||||||
#ifndef EXTERN_H
|
|
||||||
#define EXTERN_H
|
|
||||||
|
|
||||||
/* -*-C-*-
|
|
||||||
********************************************************************************
|
|
||||||
*
|
|
||||||
* File: extern.h (Formerly extern.h)
|
|
||||||
* Description: External definitions for C or C++
|
|
||||||
* Author: Mark Seaman, OCR Technology
|
|
||||||
* Created: Tue Mar 20 14:01:22 1990
|
|
||||||
* Modified: Tue Mar 20 14:02:09 1990 (Mark Seaman) marks@hpgrlt
|
|
||||||
* Language: C
|
|
||||||
* Package: N/A
|
|
||||||
* Status: Experimental (Do Not Distribute)
|
|
||||||
*
|
|
||||||
* (c) Copyright 1990, Hewlett-Packard Company.
|
|
||||||
** 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.
|
|
||||||
*
|
|
||||||
********************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define EXTERN extern
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,74 +0,0 @@
|
|||||||
/******************************************************************************
|
|
||||||
** Filename: extract.c
|
|
||||||
** Purpose: Generic high level feature extractor routines.
|
|
||||||
** Author: Dan Johnson
|
|
||||||
** History: Sun Jan 21 09:44:08 1990, DSJ, Created.
|
|
||||||
**
|
|
||||||
** (c) Copyright Hewlett-Packard Company, 1988.
|
|
||||||
** 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 Files and Type Defines
|
|
||||||
-----------------------------------------------------------------------------*/
|
|
||||||
#include "extract.h"
|
|
||||||
#include "flexfx.h"
|
|
||||||
#include "danerror.h"
|
|
||||||
|
|
||||||
typedef CHAR_FEATURES (*CF_FUNC) ();
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------------
|
|
||||||
Private Function Prototypes
|
|
||||||
-----------------------------------------------------------------------------*/
|
|
||||||
void ExtractorStub();
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------------
|
|
||||||
Public Code
|
|
||||||
-----------------------------------------------------------------------------*/
|
|
||||||
/*---------------------------------------------------------------------------*/
|
|
||||||
/**
|
|
||||||
* Extract features from Blob by calling the feature
|
|
||||||
* extractor which is currently being used. This routine
|
|
||||||
* simply provides a high level interface to feature
|
|
||||||
* extraction. The caller can extract any type of features
|
|
||||||
* from a blob without understanding any lower level details.
|
|
||||||
*
|
|
||||||
* @param FeatureDefs definitions of feature types/extractors
|
|
||||||
* @param denorm Normalize/denormalize to access original image
|
|
||||||
* @param Blob blob to extract features from
|
|
||||||
*
|
|
||||||
* @return The character features extracted from Blob.
|
|
||||||
* @note Exceptions: none
|
|
||||||
* @note History: Sun Jan 21 10:07:28 1990, DSJ, Created.
|
|
||||||
*/
|
|
||||||
CHAR_DESC ExtractBlobFeatures(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
|
||||||
const DENORM& bl_denorm, const DENORM& cn_denorm,
|
|
||||||
const INT_FX_RESULT_STRUCT& fx_info,
|
|
||||||
TBLOB *Blob) {
|
|
||||||
return ExtractFlexFeatures(FeatureDefs, Blob, bl_denorm, cn_denorm, fx_info);
|
|
||||||
} /* ExtractBlobFeatures */
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------------
|
|
||||||
Private Code
|
|
||||||
-----------------------------------------------------------------------------*/
|
|
||||||
/*---------------------------------------------------------------------------*/
|
|
||||||
void
|
|
||||||
ExtractorStub ()
|
|
||||||
/**
|
|
||||||
* This routine is used to stub out feature extractors
|
|
||||||
* that are no longer used. It simply calls DoError.
|
|
||||||
*
|
|
||||||
* @note Exceptions: none
|
|
||||||
* @note History: Wed Jan 2 14:16:49 1991, DSJ, Created.
|
|
||||||
*/
|
|
||||||
#define DUMMY_ERROR 1
|
|
||||||
{
|
|
||||||
DoError (DUMMY_ERROR, "Selected feature extractor has been stubbed out!");
|
|
||||||
} /* ExtractorStub */
|
|
@ -1,40 +0,0 @@
|
|||||||
/******************************************************************************
|
|
||||||
** Filename: extract.h
|
|
||||||
** Purpose: Interface to high level generic feature extraction.
|
|
||||||
** Author: Dan Johnson
|
|
||||||
** History: 1/21/90, DSJ, Created.
|
|
||||||
**
|
|
||||||
** (c) Copyright Hewlett-Packard Company, 1988.
|
|
||||||
** 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 EXTRACT_H
|
|
||||||
#define EXTRACT_H
|
|
||||||
|
|
||||||
#include "featdefs.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
class DENORM;
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------------
|
|
||||||
Public Function Prototypes
|
|
||||||
-----------------------------------------------------------------------------*/
|
|
||||||
// Deprecated! Will be deleted soon!
|
|
||||||
// In the meantime, as all TBLOBs, Blob is in baseline normalized coords.
|
|
||||||
// See SetupBLCNDenorms in intfx.cpp for other args.
|
|
||||||
CHAR_DESC ExtractBlobFeatures(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
|
||||||
const DENORM& bl_denorm, const DENORM& cn_denorm,
|
|
||||||
const INT_FX_RESULT_STRUCT& fx_info, TBLOB *Blob);
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------
|
|
||||||
Private Function Prototypes
|
|
||||||
----------------------------------------------------------------------------*/
|
|
||||||
void ExtractorStub();
|
|
||||||
#endif
|
|
@ -178,7 +178,7 @@ CHAR_DESC NewCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs) {
|
|||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
/**
|
/**
|
||||||
* Write a textual representation of CharDesc to File.
|
* Appends a textual representation of CharDesc to str.
|
||||||
* The format used is to write out the number of feature
|
* The format used is to write out the number of feature
|
||||||
* sets which will be written followed by a representation of
|
* sets which will be written followed by a representation of
|
||||||
* each feature set.
|
* each feature set.
|
||||||
@ -187,18 +187,15 @@ CHAR_DESC NewCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs) {
|
|||||||
* by a description of the feature set. Feature sets which are
|
* by a description of the feature set. Feature sets which are
|
||||||
* not present are not written.
|
* not present are not written.
|
||||||
*
|
*
|
||||||
* Globals:
|
|
||||||
* - none
|
|
||||||
*
|
|
||||||
* @param FeatureDefs definitions of feature types/extractors
|
* @param FeatureDefs definitions of feature types/extractors
|
||||||
* @param File open text file to write CharDesc to
|
* @param str string to append CharDesc to
|
||||||
* @param CharDesc character description to write to File
|
* @param CharDesc character description to write to File
|
||||||
*
|
*
|
||||||
* @note Exceptions: none
|
* @note Exceptions: none
|
||||||
* @note History: Wed May 23 17:21:18 1990, DSJ, Created.
|
* @note History: Wed May 23 17:21:18 1990, DSJ, Created.
|
||||||
*/
|
*/
|
||||||
void WriteCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
void WriteCharDescription(const FEATURE_DEFS_STRUCT& FeatureDefs,
|
||||||
FILE *File, CHAR_DESC CharDesc) {
|
CHAR_DESC CharDesc, STRING* str) {
|
||||||
int Type;
|
int Type;
|
||||||
int NumSetsToWrite = 0;
|
int NumSetsToWrite = 0;
|
||||||
|
|
||||||
@ -206,11 +203,14 @@ void WriteCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
|||||||
if (CharDesc->FeatureSets[Type])
|
if (CharDesc->FeatureSets[Type])
|
||||||
NumSetsToWrite++;
|
NumSetsToWrite++;
|
||||||
|
|
||||||
fprintf (File, " %d\n", NumSetsToWrite);
|
str->add_str_int(" ", NumSetsToWrite);
|
||||||
for (Type = 0; Type < CharDesc->NumFeatureSets; Type++)
|
*str += "\n";
|
||||||
if (CharDesc->FeatureSets[Type]) {
|
for (Type = 0; Type < CharDesc->NumFeatureSets; Type++) {
|
||||||
fprintf (File, "%s ", (FeatureDefs.FeatureDesc[Type])->ShortName);
|
if (CharDesc->FeatureSets[Type]) {
|
||||||
WriteFeatureSet (File, CharDesc->FeatureSets[Type]);
|
*str += FeatureDefs.FeatureDesc[Type]->ShortName;
|
||||||
|
*str += " ";
|
||||||
|
WriteFeatureSet(CharDesc->FeatureSets[Type], str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} /* WriteCharDescription */
|
} /* WriteCharDescription */
|
||||||
|
|
||||||
@ -231,6 +231,8 @@ bool ValidCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
|||||||
anything_written = true;
|
anything_written = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return anything_written && well_formed;
|
return anything_written && well_formed;
|
||||||
|
@ -48,7 +48,6 @@ typedef CHAR_DESC_STRUCT *CHAR_DESC;
|
|||||||
struct FEATURE_DEFS_STRUCT {
|
struct FEATURE_DEFS_STRUCT {
|
||||||
inT32 NumFeatureTypes;
|
inT32 NumFeatureTypes;
|
||||||
const FEATURE_DESC_STRUCT* FeatureDesc[NUM_FEATURE_TYPES];
|
const FEATURE_DESC_STRUCT* FeatureDesc[NUM_FEATURE_TYPES];
|
||||||
const FEATURE_EXT_STRUCT* FeatureExtractors[NUM_FEATURE_TYPES];
|
|
||||||
int FeatureEnabled[NUM_FEATURE_TYPES];
|
int FeatureEnabled[NUM_FEATURE_TYPES];
|
||||||
};
|
};
|
||||||
typedef FEATURE_DEFS_STRUCT *FEATURE_DEFS;
|
typedef FEATURE_DEFS_STRUCT *FEATURE_DEFS;
|
||||||
@ -65,8 +64,8 @@ CHAR_DESC NewCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs);
|
|||||||
bool ValidCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
bool ValidCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
||||||
CHAR_DESC CharDesc);
|
CHAR_DESC CharDesc);
|
||||||
|
|
||||||
void WriteCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
void WriteCharDescription(const FEATURE_DEFS_STRUCT& FeatureDefs,
|
||||||
FILE *File, CHAR_DESC CharDesc);
|
CHAR_DESC CharDesc, STRING* str);
|
||||||
|
|
||||||
CHAR_DESC ReadCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
CHAR_DESC ReadCharDescription(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
||||||
FILE *File);
|
FILE *File);
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
/******************************************************************************
|
|
||||||
** Filename: flexfx.c
|
|
||||||
** Purpose: Interface to flexible feature extractor.
|
|
||||||
** Author: Dan Johnson
|
|
||||||
** History: Wed May 23 13:45:10 1990, DSJ, Created.
|
|
||||||
**
|
|
||||||
** (c) Copyright Hewlett-Packard Company, 1988.
|
|
||||||
** 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 Files and Type Defines
|
|
||||||
----------------------------------------------------------------------------**/
|
|
||||||
#include "flexfx.h"
|
|
||||||
#include "featdefs.h"
|
|
||||||
#include "emalloc.h"
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------
|
|
||||||
Public Code
|
|
||||||
----------------------------------------------------------------------------**/
|
|
||||||
/*---------------------------------------------------------------------------*/
|
|
||||||
// Deprecated! Will be deleted soon!
|
|
||||||
// In the meantime, as all TBLOBs, Blob is in baseline normalized coords.
|
|
||||||
// See SetupBLCNDenorms in intfx.cpp for other args.
|
|
||||||
CHAR_DESC ExtractFlexFeatures(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
|
||||||
TBLOB *Blob, const DENORM& bl_denorm,
|
|
||||||
const DENORM& cn_denorm,
|
|
||||||
const INT_FX_RESULT_STRUCT& fx_info) {
|
|
||||||
/*
|
|
||||||
** Parameters:
|
|
||||||
** Blob blob to extract features from
|
|
||||||
** denorm control parameter for feature extractor
|
|
||||||
** Globals: none
|
|
||||||
** Operation: Allocate a new character descriptor and fill it in by
|
|
||||||
** calling all feature extractors which are enabled.
|
|
||||||
** Return: Structure containing features extracted from Blob.
|
|
||||||
** Exceptions: none
|
|
||||||
** History: Wed May 23 13:46:22 1990, DSJ, Created.
|
|
||||||
*/
|
|
||||||
int Type;
|
|
||||||
CHAR_DESC CharDesc;
|
|
||||||
|
|
||||||
CharDesc = NewCharDescription(FeatureDefs);
|
|
||||||
|
|
||||||
for (Type = 0; Type < CharDesc->NumFeatureSets; Type++)
|
|
||||||
if (FeatureDefs.FeatureExtractors[Type] != NULL &&
|
|
||||||
FeatureDefs.FeatureExtractors[Type]->Extractor != NULL) {
|
|
||||||
CharDesc->FeatureSets[Type] =
|
|
||||||
(FeatureDefs.FeatureExtractors[Type])->Extractor(Blob,
|
|
||||||
bl_denorm,
|
|
||||||
cn_denorm,
|
|
||||||
fx_info);
|
|
||||||
if (CharDesc->FeatureSets[Type] == NULL) {
|
|
||||||
tprintf("Feature extractor for type %d = %s returned NULL!\n",
|
|
||||||
Type, FeatureDefs.FeatureDesc[Type]->ShortName);
|
|
||||||
FreeCharDescription(CharDesc);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (CharDesc);
|
|
||||||
|
|
||||||
} /* ExtractFlexFeatures */
|
|
@ -1,36 +0,0 @@
|
|||||||
/******************************************************************************
|
|
||||||
** Filename: flexfx.h
|
|
||||||
** Purpose: Interface to flexible feature extractor.
|
|
||||||
** Author: Dan Johnson
|
|
||||||
** History: Wed May 23 13:36:58 1990, DSJ, Created.
|
|
||||||
**
|
|
||||||
** (c) Copyright Hewlett-Packard Company, 1988.
|
|
||||||
** 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 FLEXFX_H
|
|
||||||
#define FLEXFX_H
|
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------
|
|
||||||
Include Files and Type Defines
|
|
||||||
----------------------------------------------------------------------------**/
|
|
||||||
#include "featdefs.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------
|
|
||||||
Public Function Prototypes
|
|
||||||
----------------------------------------------------------------------------**/
|
|
||||||
// As with all TBLOBs this one is also baseline normalized.
|
|
||||||
CHAR_DESC ExtractFlexFeatures(const FEATURE_DEFS_STRUCT &FeatureDefs,
|
|
||||||
TBLOB *Blob, const DENORM& bl_denorm,
|
|
||||||
const DENORM& cn_denorm,
|
|
||||||
const INT_FX_RESULT_STRUCT& fx_info);
|
|
||||||
|
|
||||||
#endif
|
|
@ -111,12 +111,12 @@ void Classify::ComputeIntFeatures(FEATURE_SET Features,
|
|||||||
for (Fid = 0; Fid < Features->NumFeatures; Fid++) {
|
for (Fid = 0; Fid < Features->NumFeatures; Fid++) {
|
||||||
Feature = Features->Features[Fid];
|
Feature = Features->Features[Fid];
|
||||||
|
|
||||||
IntFeatures[Fid].X = BucketFor (Feature->Params[PicoFeatX],
|
IntFeatures[Fid].X =
|
||||||
X_SHIFT, INT_FEAT_RANGE);
|
Bucket8For(Feature->Params[PicoFeatX], X_SHIFT, INT_FEAT_RANGE);
|
||||||
IntFeatures[Fid].Y = BucketFor (Feature->Params[PicoFeatY],
|
IntFeatures[Fid].Y =
|
||||||
YShift, INT_FEAT_RANGE);
|
Bucket8For(Feature->Params[PicoFeatY], YShift, INT_FEAT_RANGE);
|
||||||
IntFeatures[Fid].Theta = CircBucketFor (Feature->Params[PicoFeatDir],
|
IntFeatures[Fid].Theta = CircBucketFor(Feature->Params[PicoFeatDir],
|
||||||
ANGLE_SHIFT, INT_FEAT_RANGE);
|
ANGLE_SHIFT, INT_FEAT_RANGE);
|
||||||
IntFeatures[Fid].CP_misses = 0;
|
IntFeatures[Fid].CP_misses = 0;
|
||||||
}
|
}
|
||||||
} /* ComputeIntFeatures */
|
} /* ComputeIntFeatures */
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
/******************************************************************************
|
|
||||||
** Filename: fxdefs.c
|
|
||||||
** Purpose: Utility functions to be used by feature extractors.
|
|
||||||
** Author: Dan Johnson
|
|
||||||
** History: Sun Jan 21 15:29:02 1990, DSJ, Created.
|
|
||||||
**
|
|
||||||
** (c) Copyright Hewlett-Packard Company, 1988.
|
|
||||||
** 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 "fxdefs.h"
|
|
||||||
#include "featdefs.h"
|
|
||||||
#include "mf.h"
|
|
||||||
#include "outfeat.h"
|
|
||||||
#include "picofeat.h"
|
|
||||||
#include "normfeat.h"
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------------
|
|
||||||
Global Data Definitions and Declarations
|
|
||||||
-----------------------------------------------------------------------------*/
|
|
||||||
// Definitions of extractors separated from feature definitions.
|
|
||||||
const FEATURE_EXT_STRUCT MicroFeatureExt = { ExtractMicros };
|
|
||||||
const FEATURE_EXT_STRUCT CharNormExt = { ExtractCharNormFeatures };
|
|
||||||
const FEATURE_EXT_STRUCT IntFeatExt = { ExtractIntCNFeatures };
|
|
||||||
const FEATURE_EXT_STRUCT GeoFeatExt = { ExtractIntGeoFeatures };
|
|
||||||
|
|
||||||
// MUST be kept in-sync with DescDefs in featdefs.cpp.
|
|
||||||
const FEATURE_EXT_STRUCT* ExtractorDefs[NUM_FEATURE_TYPES] = {
|
|
||||||
&MicroFeatureExt,
|
|
||||||
&CharNormExt,
|
|
||||||
&IntFeatExt,
|
|
||||||
&GeoFeatExt
|
|
||||||
};
|
|
||||||
|
|
||||||
void SetupExtractors(FEATURE_DEFS_STRUCT *FeatureDefs) {
|
|
||||||
for (int i = 0; i < NUM_FEATURE_TYPES; ++i)
|
|
||||||
FeatureDefs->FeatureExtractors[i] = ExtractorDefs[i];
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
/******************************************************************************
|
|
||||||
** Filename: fxdefs.h
|
|
||||||
** Purpose: Generic interface definitions for feature extractors
|
|
||||||
** Author: Dan Johnson
|
|
||||||
** History: Fri Jan 19 09:04:14 1990, DSJ, Created.
|
|
||||||
**
|
|
||||||
** (c) Copyright Hewlett-Packard Company, 1988.
|
|
||||||
** 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 FXDEFS_H
|
|
||||||
#define FXDEFS_H
|
|
||||||
|
|
||||||
#include "featdefs.h"
|
|
||||||
|
|
||||||
void SetupExtractors(FEATURE_DEFS_STRUCT *FeatureDefs);
|
|
||||||
|
|
||||||
#endif
|
|
@ -75,9 +75,9 @@ namespace tesseract {
|
|||||||
|
|
||||||
// Generates a TrainingSample from a TBLOB. Extracts features and sets
|
// Generates a TrainingSample from a TBLOB. Extracts features and sets
|
||||||
// the bounding box, so classifiers that operate on the image can work.
|
// the bounding box, so classifiers that operate on the image can work.
|
||||||
// TODO(rays) BlobToTrainingSample must remain a global function until
|
// TODO(rays) Make BlobToTrainingSample a member of Classify now that
|
||||||
// the FlexFx and FeatureDescription code can be removed and LearnBlob
|
// the FlexFx and FeatureDescription code have been removed and LearnBlob
|
||||||
// made a member of Classify.
|
// is now a member of Classify.
|
||||||
TrainingSample* BlobToTrainingSample(
|
TrainingSample* BlobToTrainingSample(
|
||||||
const TBLOB& blob, bool nonlinear_norm, INT_FX_RESULT_STRUCT* fx_info,
|
const TBLOB& blob, bool nonlinear_norm, INT_FX_RESULT_STRUCT* fx_info,
|
||||||
GenericVector<INT_FEATURE_STRUCT>* bl_features) {
|
GenericVector<INT_FEATURE_STRUCT>* bl_features) {
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
Include Files and Type Defines
|
Include Files and Type Defines
|
||||||
----------------------------------------------------------------------------*/
|
----------------------------------------------------------------------------*/
|
||||||
#include "intmatcher.h"
|
#include "intmatcher.h"
|
||||||
|
|
||||||
|
#include "fontinfo.h"
|
||||||
#include "intproto.h"
|
#include "intproto.h"
|
||||||
#include "callcpp.h"
|
#include "callcpp.h"
|
||||||
#include "scrollview.h"
|
#include "scrollview.h"
|
||||||
@ -36,6 +38,9 @@
|
|||||||
#include "shapetable.h"
|
#include "shapetable.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
using tesseract::ScoredFont;
|
||||||
|
using tesseract::UnicharRating;
|
||||||
|
|
||||||
/*----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
Global Data Definitions and Declarations
|
Global Data Definitions and Declarations
|
||||||
----------------------------------------------------------------------------*/
|
----------------------------------------------------------------------------*/
|
||||||
@ -45,58 +50,51 @@
|
|||||||
const float IntegerMatcher::kSEExponentialMultiplier = 0.0;
|
const float IntegerMatcher::kSEExponentialMultiplier = 0.0;
|
||||||
const float IntegerMatcher::kSimilarityCenter = 0.0075;
|
const float IntegerMatcher::kSimilarityCenter = 0.0075;
|
||||||
|
|
||||||
static const uinT8 offset_table[256] = {
|
#define offset_table_entries \
|
||||||
255, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
255, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, \
|
||||||
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, \
|
||||||
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, \
|
||||||
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, \
|
||||||
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, \
|
||||||
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, \
|
||||||
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, \
|
||||||
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, \
|
||||||
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, \
|
||||||
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, \
|
||||||
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
|
||||||
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
|
||||||
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
|
||||||
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
|
||||||
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
|
||||||
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uinT8 next_table[256] = {
|
#define INTMATCHER_OFFSET_TABLE_SIZE 256
|
||||||
0, 0, 0, 0x2, 0, 0x4, 0x4, 0x6, 0, 0x8, 0x8, 0x0a, 0x08, 0x0c, 0x0c, 0x0e,
|
|
||||||
0, 0x10, 0x10, 0x12, 0x10, 0x14, 0x14, 0x16, 0x10, 0x18, 0x18, 0x1a, 0x18,
|
#define next_table_entries \
|
||||||
0x1c, 0x1c, 0x1e,
|
0, 0, 0, 0x2, 0, 0x4, 0x4, 0x6, 0, 0x8, 0x8, 0x0a, 0x08, 0x0c, 0x0c, 0x0e, \
|
||||||
0, 0x20, 0x20, 0x22, 0x20, 0x24, 0x24, 0x26, 0x20, 0x28, 0x28, 0x2a, 0x28,
|
0, 0x10, 0x10, 0x12, 0x10, 0x14, 0x14, 0x16, 0x10, 0x18, 0x18, 0x1a, \
|
||||||
0x2c, 0x2c, 0x2e,
|
0x18, 0x1c, 0x1c, 0x1e, 0, 0x20, 0x20, 0x22, 0x20, 0x24, 0x24, 0x26, \
|
||||||
0x20, 0x30, 0x30, 0x32, 0x30, 0x34, 0x34, 0x36, 0x30, 0x38, 0x38, 0x3a,
|
0x20, 0x28, 0x28, 0x2a, 0x28, 0x2c, 0x2c, 0x2e, 0x20, 0x30, 0x30, 0x32, \
|
||||||
0x38, 0x3c, 0x3c, 0x3e,
|
0x30, 0x34, 0x34, 0x36, 0x30, 0x38, 0x38, 0x3a, 0x38, 0x3c, 0x3c, 0x3e, \
|
||||||
0, 0x40, 0x40, 0x42, 0x40, 0x44, 0x44, 0x46, 0x40, 0x48, 0x48, 0x4a, 0x48,
|
0, 0x40, 0x40, 0x42, 0x40, 0x44, 0x44, 0x46, 0x40, 0x48, 0x48, 0x4a, \
|
||||||
0x4c, 0x4c, 0x4e,
|
0x48, 0x4c, 0x4c, 0x4e, 0x40, 0x50, 0x50, 0x52, 0x50, 0x54, 0x54, 0x56, \
|
||||||
0x40, 0x50, 0x50, 0x52, 0x50, 0x54, 0x54, 0x56, 0x50, 0x58, 0x58, 0x5a,
|
0x50, 0x58, 0x58, 0x5a, 0x58, 0x5c, 0x5c, 0x5e, 0x40, 0x60, 0x60, 0x62, \
|
||||||
0x58, 0x5c, 0x5c, 0x5e,
|
0x60, 0x64, 0x64, 0x66, 0x60, 0x68, 0x68, 0x6a, 0x68, 0x6c, 0x6c, 0x6e, \
|
||||||
0x40, 0x60, 0x60, 0x62, 0x60, 0x64, 0x64, 0x66, 0x60, 0x68, 0x68, 0x6a,
|
0x60, 0x70, 0x70, 0x72, 0x70, 0x74, 0x74, 0x76, 0x70, 0x78, 0x78, 0x7a, \
|
||||||
0x68, 0x6c, 0x6c, 0x6e,
|
0x78, 0x7c, 0x7c, 0x7e, 0, 0x80, 0x80, 0x82, 0x80, 0x84, 0x84, 0x86, \
|
||||||
0x60, 0x70, 0x70, 0x72, 0x70, 0x74, 0x74, 0x76, 0x70, 0x78, 0x78, 0x7a,
|
0x80, 0x88, 0x88, 0x8a, 0x88, 0x8c, 0x8c, 0x8e, 0x80, 0x90, 0x90, 0x92, \
|
||||||
0x78, 0x7c, 0x7c, 0x7e,
|
0x90, 0x94, 0x94, 0x96, 0x90, 0x98, 0x98, 0x9a, 0x98, 0x9c, 0x9c, 0x9e, \
|
||||||
0, 0x80, 0x80, 0x82, 0x80, 0x84, 0x84, 0x86, 0x80, 0x88, 0x88, 0x8a, 0x88,
|
0x80, 0xa0, 0xa0, 0xa2, 0xa0, 0xa4, 0xa4, 0xa6, 0xa0, 0xa8, 0xa8, 0xaa, \
|
||||||
0x8c, 0x8c, 0x8e,
|
0xa8, 0xac, 0xac, 0xae, 0xa0, 0xb0, 0xb0, 0xb2, 0xb0, 0xb4, 0xb4, 0xb6, \
|
||||||
0x80, 0x90, 0x90, 0x92, 0x90, 0x94, 0x94, 0x96, 0x90, 0x98, 0x98, 0x9a,
|
0xb0, 0xb8, 0xb8, 0xba, 0xb8, 0xbc, 0xbc, 0xbe, 0x80, 0xc0, 0xc0, 0xc2, \
|
||||||
0x98, 0x9c, 0x9c, 0x9e,
|
0xc0, 0xc4, 0xc4, 0xc6, 0xc0, 0xc8, 0xc8, 0xca, 0xc8, 0xcc, 0xcc, 0xce, \
|
||||||
0x80, 0xa0, 0xa0, 0xa2, 0xa0, 0xa4, 0xa4, 0xa6, 0xa0, 0xa8, 0xa8, 0xaa,
|
0xc0, 0xd0, 0xd0, 0xd2, 0xd0, 0xd4, 0xd4, 0xd6, 0xd0, 0xd8, 0xd8, 0xda, \
|
||||||
0xa8, 0xac, 0xac, 0xae,
|
0xd8, 0xdc, 0xdc, 0xde, 0xc0, 0xe0, 0xe0, 0xe2, 0xe0, 0xe4, 0xe4, 0xe6, \
|
||||||
0xa0, 0xb0, 0xb0, 0xb2, 0xb0, 0xb4, 0xb4, 0xb6, 0xb0, 0xb8, 0xb8, 0xba,
|
0xe0, 0xe8, 0xe8, 0xea, 0xe8, 0xec, 0xec, 0xee, 0xe0, 0xf0, 0xf0, 0xf2, \
|
||||||
0xb8, 0xbc, 0xbc, 0xbe,
|
0xf0, 0xf4, 0xf4, 0xf6, 0xf0, 0xf8, 0xf8, 0xfa, 0xf8, 0xfc, 0xfc, 0xfe
|
||||||
0x80, 0xc0, 0xc0, 0xc2, 0xc0, 0xc4, 0xc4, 0xc6, 0xc0, 0xc8, 0xc8, 0xca,
|
|
||||||
0xc8, 0xcc, 0xcc, 0xce,
|
// See http://b/19318793 (#6) for a complete discussion. Merging arrays
|
||||||
0xc0, 0xd0, 0xd0, 0xd2, 0xd0, 0xd4, 0xd4, 0xd6, 0xd0, 0xd8, 0xd8, 0xda,
|
// offset_table and next_table helps improve performance of PIE code.
|
||||||
0xd8, 0xdc, 0xdc, 0xde,
|
static const uinT8 data_table[512] = {offset_table_entries, next_table_entries};
|
||||||
0xc0, 0xe0, 0xe0, 0xe2, 0xe0, 0xe4, 0xe4, 0xe6, 0xe0, 0xe8, 0xe8, 0xea,
|
|
||||||
0xe8, 0xec, 0xec, 0xee,
|
static const uinT8* const offset_table = &data_table[0];
|
||||||
0xe0, 0xf0, 0xf0, 0xf2, 0xf0, 0xf4, 0xf4, 0xf6, 0xf0, 0xf8, 0xf8, 0xfa,
|
static const uinT8* const next_table =
|
||||||
0xf8, 0xfc, 0xfc, 0xfe
|
&data_table[INTMATCHER_OFFSET_TABLE_SIZE];
|
||||||
};
|
|
||||||
|
|
||||||
namespace tesseract {
|
namespace tesseract {
|
||||||
|
|
||||||
@ -263,8 +261,8 @@ class ClassPruner {
|
|||||||
// Prunes the classes using <the maximum count> * pruning_factor/256 as a
|
// Prunes the classes using <the maximum count> * pruning_factor/256 as a
|
||||||
// threshold for keeping classes. If max_of_non_fragments, then ignore
|
// threshold for keeping classes. If max_of_non_fragments, then ignore
|
||||||
// fragments in computing the maximum count.
|
// fragments in computing the maximum count.
|
||||||
void PruneAndSort(int pruning_factor, bool max_of_non_fragments,
|
void PruneAndSort(int pruning_factor, int keep_this,
|
||||||
const UNICHARSET& unicharset) {
|
bool max_of_non_fragments, const UNICHARSET& unicharset) {
|
||||||
int max_count = 0;
|
int max_count = 0;
|
||||||
for (int c = 0; c < max_classes_; ++c) {
|
for (int c = 0; c < max_classes_; ++c) {
|
||||||
if (norm_count_[c] > max_count &&
|
if (norm_count_[c] > max_count &&
|
||||||
@ -284,7 +282,8 @@ class ClassPruner {
|
|||||||
pruning_threshold_ = 1;
|
pruning_threshold_ = 1;
|
||||||
num_classes_ = 0;
|
num_classes_ = 0;
|
||||||
for (int class_id = 0; class_id < max_classes_; class_id++) {
|
for (int class_id = 0; class_id < max_classes_; class_id++) {
|
||||||
if (norm_count_[class_id] >= pruning_threshold_) {
|
if (norm_count_[class_id] >= pruning_threshold_ ||
|
||||||
|
class_id == keep_this) {
|
||||||
++num_classes_;
|
++num_classes_;
|
||||||
sort_index_[num_classes_] = class_id;
|
sort_index_[num_classes_] = class_id;
|
||||||
sort_key_[num_classes_] = norm_count_[class_id];
|
sort_key_[num_classes_] = norm_count_[class_id];
|
||||||
@ -406,7 +405,7 @@ class ClassPruner {
|
|||||||
// results Sorted Array of pruned classes. Must be an array
|
// results Sorted Array of pruned classes. Must be an array
|
||||||
// of size at least int_templates->NumClasses.
|
// of size at least int_templates->NumClasses.
|
||||||
int Classify::PruneClasses(const INT_TEMPLATES_STRUCT* int_templates,
|
int Classify::PruneClasses(const INT_TEMPLATES_STRUCT* int_templates,
|
||||||
int num_features,
|
int num_features, int keep_this,
|
||||||
const INT_FEATURE_STRUCT* features,
|
const INT_FEATURE_STRUCT* features,
|
||||||
const uinT8* normalization_factors,
|
const uinT8* normalization_factors,
|
||||||
const uinT16* expected_num_features,
|
const uinT16* expected_num_features,
|
||||||
@ -441,7 +440,7 @@ int Classify::PruneClasses(const INT_TEMPLATES_STRUCT* int_templates,
|
|||||||
pruner.NoNormalization();
|
pruner.NoNormalization();
|
||||||
}
|
}
|
||||||
// Do the actual pruning and sort the short-list.
|
// Do the actual pruning and sort the short-list.
|
||||||
pruner.PruneAndSort(classify_class_pruner_threshold,
|
pruner.PruneAndSort(classify_class_pruner_threshold, keep_this,
|
||||||
shape_table_ == NULL, unicharset);
|
shape_table_ == NULL, unicharset);
|
||||||
|
|
||||||
if (classify_debug_level > 2) {
|
if (classify_debug_level > 2) {
|
||||||
@ -464,7 +463,7 @@ void IntegerMatcher::Match(INT_CLASS ClassTemplate,
|
|||||||
BIT_VECTOR ConfigMask,
|
BIT_VECTOR ConfigMask,
|
||||||
inT16 NumFeatures,
|
inT16 NumFeatures,
|
||||||
const INT_FEATURE_STRUCT* Features,
|
const INT_FEATURE_STRUCT* Features,
|
||||||
INT_RESULT Result,
|
UnicharRating* Result,
|
||||||
int AdaptFeatureThreshold,
|
int AdaptFeatureThreshold,
|
||||||
int Debug,
|
int Debug,
|
||||||
bool SeparateDebugWindows) {
|
bool SeparateDebugWindows) {
|
||||||
@ -477,7 +476,7 @@ void IntegerMatcher::Match(INT_CLASS ClassTemplate,
|
|||||||
** NormalizationFactor Fudge factor from blob
|
** NormalizationFactor Fudge factor from blob
|
||||||
** normalization process
|
** normalization process
|
||||||
** Result Class rating & configuration:
|
** Result Class rating & configuration:
|
||||||
** (0.0 -> 1.0), 0=good, 1=bad
|
** (0.0 -> 1.0), 0=bad, 1=good
|
||||||
** Debug Debugger flag: 1=debugger on
|
** Debug Debugger flag: 1=debugger on
|
||||||
** Globals:
|
** Globals:
|
||||||
** local_matcher_multiplier_ Normalization factor multiplier
|
** local_matcher_multiplier_ Normalization factor multiplier
|
||||||
@ -498,7 +497,7 @@ void IntegerMatcher::Match(INT_CLASS ClassTemplate,
|
|||||||
cprintf ("Integer Matcher -------------------------------------------\n");
|
cprintf ("Integer Matcher -------------------------------------------\n");
|
||||||
|
|
||||||
tables->Clear(ClassTemplate);
|
tables->Clear(ClassTemplate);
|
||||||
Result->FeatureMisses = 0;
|
Result->feature_misses = 0;
|
||||||
|
|
||||||
for (Feature = 0; Feature < NumFeatures; Feature++) {
|
for (Feature = 0; Feature < NumFeatures; Feature++) {
|
||||||
int csum = UpdateTablesForFeature(ClassTemplate, ProtoMask, ConfigMask,
|
int csum = UpdateTablesForFeature(ClassTemplate, ProtoMask, ConfigMask,
|
||||||
@ -506,7 +505,7 @@ void IntegerMatcher::Match(INT_CLASS ClassTemplate,
|
|||||||
tables, Debug);
|
tables, Debug);
|
||||||
// Count features that were missed over all configs.
|
// Count features that were missed over all configs.
|
||||||
if (csum == 0)
|
if (csum == 0)
|
||||||
Result->FeatureMisses++;
|
++Result->feature_misses;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef GRAPHICS_DISABLED
|
#ifndef GRAPHICS_DISABLED
|
||||||
@ -534,7 +533,7 @@ void IntegerMatcher::Match(INT_CLASS ClassTemplate,
|
|||||||
|
|
||||||
#ifndef GRAPHICS_DISABLED
|
#ifndef GRAPHICS_DISABLED
|
||||||
if (PrintMatchSummaryOn(Debug))
|
if (PrintMatchSummaryOn(Debug))
|
||||||
DebugBestMatch(BestMatch, Result);
|
Result->Print();
|
||||||
|
|
||||||
if (MatchDebuggingOn(Debug))
|
if (MatchDebuggingOn(Debug))
|
||||||
cprintf("Match Complete --------------------------------------------\n");
|
cprintf("Match Complete --------------------------------------------\n");
|
||||||
@ -1222,9 +1221,9 @@ void ScratchEvidence::NormalizeSums(
|
|||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
int IntegerMatcher::FindBestMatch(
|
int IntegerMatcher::FindBestMatch(
|
||||||
INT_CLASS ClassTemplate,
|
INT_CLASS class_template,
|
||||||
const ScratchEvidence &tables,
|
const ScratchEvidence &tables,
|
||||||
INT_RESULT Result) {
|
UnicharRating* result) {
|
||||||
/*
|
/*
|
||||||
** Parameters:
|
** Parameters:
|
||||||
** Globals:
|
** Globals:
|
||||||
@ -1236,35 +1235,27 @@ int IntegerMatcher::FindBestMatch(
|
|||||||
** Exceptions: none
|
** Exceptions: none
|
||||||
** History: Wed Feb 27 14:12:28 MST 1991, RWM, Created.
|
** History: Wed Feb 27 14:12:28 MST 1991, RWM, Created.
|
||||||
*/
|
*/
|
||||||
int BestMatch = 0;
|
int best_match = 0;
|
||||||
int Best2Match = 0;
|
result->config = 0;
|
||||||
Result->Config = 0;
|
result->fonts.truncate(0);
|
||||||
Result->Config2 = 0;
|
result->fonts.reserve(class_template->NumConfigs);
|
||||||
|
|
||||||
/* Find best match */
|
/* Find best match */
|
||||||
for (int ConfigNum = 0; ConfigNum < ClassTemplate->NumConfigs; ConfigNum++) {
|
for (int c = 0; c < class_template->NumConfigs; ++c) {
|
||||||
int rating = tables.sum_feature_evidence_[ConfigNum];
|
int rating = tables.sum_feature_evidence_[c];
|
||||||
if (*classify_debug_level_ > 2)
|
if (*classify_debug_level_ > 2)
|
||||||
cprintf("Config %d, rating=%d\n", ConfigNum, rating);
|
tprintf("Config %d, rating=%d\n", c, rating);
|
||||||
if (rating > BestMatch) {
|
if (rating > best_match) {
|
||||||
if (BestMatch > 0) {
|
result->config = c;
|
||||||
Result->Config2 = Result->Config;
|
best_match = rating;
|
||||||
Best2Match = BestMatch;
|
|
||||||
} else {
|
|
||||||
Result->Config2 = ConfigNum;
|
|
||||||
}
|
|
||||||
Result->Config = ConfigNum;
|
|
||||||
BestMatch = rating;
|
|
||||||
} else if (rating > Best2Match) {
|
|
||||||
Result->Config2 = ConfigNum;
|
|
||||||
Best2Match = rating;
|
|
||||||
}
|
}
|
||||||
|
result->fonts.push_back(ScoredFont(c, rating));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compute Certainty Rating */
|
// Compute confidence on a Probability scale.
|
||||||
Result->Rating = (65536.0 - BestMatch) / 65536.0;
|
result->rating = best_match / 65536.0f;
|
||||||
|
|
||||||
return BestMatch;
|
return best_match;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applies the CN normalization factor to the given rating and returns
|
// Applies the CN normalization factor to the given rating and returns
|
||||||
@ -1277,17 +1268,6 @@ float IntegerMatcher::ApplyCNCorrection(float rating, int blob_length,
|
|||||||
(blob_length + matcher_multiplier);
|
(blob_length + matcher_multiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
|
||||||
#ifndef GRAPHICS_DISABLED
|
|
||||||
// Print debug information about the best match for the current class.
|
|
||||||
void IntegerMatcher::DebugBestMatch(
|
|
||||||
int BestMatch, INT_RESULT Result) {
|
|
||||||
tprintf("Rating = %5.1f%% Best Config = %3d, Distance = %5.1f\n",
|
|
||||||
100.0 * Result->Rating, Result->Config,
|
|
||||||
100.0 * (65536.0 - BestMatch) / 65536.0);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
void
|
void
|
||||||
HeapSort (int n, register int ra[], register int rb[]) {
|
HeapSort (int n, register int ra[], register int rb[]) {
|
||||||
|
@ -38,25 +38,14 @@ extern INT_VAR_H(classify_integer_matcher_multiplier, 10,
|
|||||||
#include "intproto.h"
|
#include "intproto.h"
|
||||||
#include "cutoffs.h"
|
#include "cutoffs.h"
|
||||||
|
|
||||||
struct INT_RESULT_STRUCT {
|
namespace tesseract {
|
||||||
INT_RESULT_STRUCT() : Rating(0.0f), Config(0), Config2(0), FeatureMisses(0) {}
|
class UnicharRating;
|
||||||
|
}
|
||||||
FLOAT32 Rating;
|
|
||||||
// TODO(rays) It might be desirable for these to be able to represent a
|
|
||||||
// null config.
|
|
||||||
uinT8 Config;
|
|
||||||
uinT8 Config2;
|
|
||||||
uinT16 FeatureMisses;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef INT_RESULT_STRUCT *INT_RESULT;
|
|
||||||
|
|
||||||
|
|
||||||
struct CP_RESULT_STRUCT {
|
struct CP_RESULT_STRUCT {
|
||||||
CP_RESULT_STRUCT() : Rating(0.0f), Class(0) {}
|
CP_RESULT_STRUCT() : Rating(0.0f), Class(0) {}
|
||||||
|
|
||||||
FLOAT32 Rating;
|
FLOAT32 Rating;
|
||||||
INT_RESULT_STRUCT IMResult;
|
|
||||||
CLASS_ID Class;
|
CLASS_ID Class;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -113,7 +102,7 @@ class IntegerMatcher {
|
|||||||
BIT_VECTOR ConfigMask,
|
BIT_VECTOR ConfigMask,
|
||||||
inT16 NumFeatures,
|
inT16 NumFeatures,
|
||||||
const INT_FEATURE_STRUCT* Features,
|
const INT_FEATURE_STRUCT* Features,
|
||||||
INT_RESULT Result,
|
tesseract::UnicharRating* Result,
|
||||||
int AdaptFeatureThreshold,
|
int AdaptFeatureThreshold,
|
||||||
int Debug,
|
int Debug,
|
||||||
bool SeparateDebugWindows);
|
bool SeparateDebugWindows);
|
||||||
@ -155,7 +144,7 @@ class IntegerMatcher {
|
|||||||
|
|
||||||
int FindBestMatch(INT_CLASS ClassTemplate,
|
int FindBestMatch(INT_CLASS ClassTemplate,
|
||||||
const ScratchEvidence &tables,
|
const ScratchEvidence &tables,
|
||||||
INT_RESULT Result);
|
tesseract::UnicharRating* Result);
|
||||||
|
|
||||||
#ifndef GRAPHICS_DISABLED
|
#ifndef GRAPHICS_DISABLED
|
||||||
void DebugFeatureProtoError(
|
void DebugFeatureProtoError(
|
||||||
@ -182,8 +171,6 @@ class IntegerMatcher {
|
|||||||
int AdaptFeatureThreshold,
|
int AdaptFeatureThreshold,
|
||||||
int Debug,
|
int Debug,
|
||||||
bool SeparateDebugWindows);
|
bool SeparateDebugWindows);
|
||||||
|
|
||||||
void DebugBestMatch(int BestMatch, INT_RESULT Result);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
@ -439,52 +439,25 @@ void AddProtoToProtoPruner(PROTO Proto, int ProtoId,
|
|||||||
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
int BucketFor(FLOAT32 Param, FLOAT32 Offset, int NumBuckets) {
|
// Returns a quantized bucket for the given param shifted by offset,
|
||||||
/*
|
// notionally (param + offset) * num_buckets, but clipped and casted to the
|
||||||
** Parameters:
|
// appropriate type.
|
||||||
** Param parameter value to map into a bucket number
|
uinT8 Bucket8For(FLOAT32 param, FLOAT32 offset, int num_buckets) {
|
||||||
** Offset amount to shift param before mapping it
|
int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
|
||||||
** NumBuckets number of buckets to map param into
|
return static_cast<uinT8>(ClipToRange(bucket, 0, num_buckets - 1));
|
||||||
** Globals: none
|
}
|
||||||
** Operation: This routine maps a parameter value into a bucket between
|
uinT16 Bucket16For(FLOAT32 param, FLOAT32 offset, int num_buckets) {
|
||||||
** 0 and NumBuckets-1. Offset is added to the parameter
|
int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
|
||||||
** before mapping it. Values which map to buckets outside
|
return static_cast<uinT16>(ClipToRange(bucket, 0, num_buckets - 1));
|
||||||
** the range are truncated to fit within the range. Mapping
|
}
|
||||||
** is done by truncating rather than rounding.
|
|
||||||
** Return: Bucket number corresponding to Param + Offset.
|
|
||||||
** Exceptions: none
|
|
||||||
** History: Thu Feb 14 13:24:33 1991, DSJ, Created.
|
|
||||||
*/
|
|
||||||
return ClipToRange(static_cast<int>(MapParam(Param, Offset, NumBuckets)),
|
|
||||||
0, NumBuckets - 1);
|
|
||||||
} /* BucketFor */
|
|
||||||
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
int CircBucketFor(FLOAT32 Param, FLOAT32 Offset, int NumBuckets) {
|
// Returns a quantized bucket for the given circular param shifted by offset,
|
||||||
/*
|
// notionally (param + offset) * num_buckets, but modded and casted to the
|
||||||
** Parameters:
|
// appropriate type.
|
||||||
** Param parameter value to map into a circular bucket
|
uinT8 CircBucketFor(FLOAT32 param, FLOAT32 offset, int num_buckets) {
|
||||||
** Offset amount to shift param before mapping it
|
int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
|
||||||
** NumBuckets number of buckets to map param into
|
return static_cast<uinT8>(Modulo(bucket, num_buckets));
|
||||||
** Globals: none
|
|
||||||
** Operation: This routine maps a parameter value into a bucket between
|
|
||||||
** 0 and NumBuckets-1. Offset is added to the parameter
|
|
||||||
** before mapping it. Values which map to buckets outside
|
|
||||||
** the range are wrapped to a new value in a circular fashion.
|
|
||||||
** Mapping is done by truncating rather than rounding.
|
|
||||||
** Return: Bucket number corresponding to Param + Offset.
|
|
||||||
** Exceptions: none
|
|
||||||
** History: Thu Feb 14 13:24:33 1991, DSJ, Created.
|
|
||||||
*/
|
|
||||||
int Bucket;
|
|
||||||
|
|
||||||
Bucket = static_cast<int>(MapParam(Param, Offset, NumBuckets));
|
|
||||||
if (Bucket < 0)
|
|
||||||
Bucket += NumBuckets;
|
|
||||||
else if (Bucket >= NumBuckets)
|
|
||||||
Bucket -= NumBuckets;
|
|
||||||
return Bucket;
|
|
||||||
} /* CircBucketFor */
|
} /* CircBucketFor */
|
||||||
|
|
||||||
|
|
||||||
@ -1694,23 +1667,23 @@ void InitTableFiller (FLOAT32 EndPad, FLOAT32 SidePad,
|
|||||||
|
|
||||||
if (fabs (Angle - 0.0) < HV_TOLERANCE || fabs (Angle - 0.5) < HV_TOLERANCE) {
|
if (fabs (Angle - 0.0) < HV_TOLERANCE || fabs (Angle - 0.5) < HV_TOLERANCE) {
|
||||||
/* horizontal proto - handle as special case */
|
/* horizontal proto - handle as special case */
|
||||||
Filler->X = BucketFor(X - HalfLength - EndPad, XS, NB);
|
Filler->X = Bucket8For(X - HalfLength - EndPad, XS, NB);
|
||||||
Filler->YStart = BucketFor(Y - SidePad, YS, NB * 256);
|
Filler->YStart = Bucket16For(Y - SidePad, YS, NB * 256);
|
||||||
Filler->YEnd = BucketFor(Y + SidePad, YS, NB * 256);
|
Filler->YEnd = Bucket16For(Y + SidePad, YS, NB * 256);
|
||||||
Filler->StartDelta = 0;
|
Filler->StartDelta = 0;
|
||||||
Filler->EndDelta = 0;
|
Filler->EndDelta = 0;
|
||||||
Filler->Switch[0].Type = LastSwitch;
|
Filler->Switch[0].Type = LastSwitch;
|
||||||
Filler->Switch[0].X = BucketFor(X + HalfLength + EndPad, XS, NB);
|
Filler->Switch[0].X = Bucket8For(X + HalfLength + EndPad, XS, NB);
|
||||||
} else if (fabs(Angle - 0.25) < HV_TOLERANCE ||
|
} else if (fabs(Angle - 0.25) < HV_TOLERANCE ||
|
||||||
fabs(Angle - 0.75) < HV_TOLERANCE) {
|
fabs(Angle - 0.75) < HV_TOLERANCE) {
|
||||||
/* vertical proto - handle as special case */
|
/* vertical proto - handle as special case */
|
||||||
Filler->X = BucketFor(X - SidePad, XS, NB);
|
Filler->X = Bucket8For(X - SidePad, XS, NB);
|
||||||
Filler->YStart = BucketFor(Y - HalfLength - EndPad, YS, NB * 256);
|
Filler->YStart = Bucket16For(Y - HalfLength - EndPad, YS, NB * 256);
|
||||||
Filler->YEnd = BucketFor(Y + HalfLength + EndPad, YS, NB * 256);
|
Filler->YEnd = Bucket16For(Y + HalfLength + EndPad, YS, NB * 256);
|
||||||
Filler->StartDelta = 0;
|
Filler->StartDelta = 0;
|
||||||
Filler->EndDelta = 0;
|
Filler->EndDelta = 0;
|
||||||
Filler->Switch[0].Type = LastSwitch;
|
Filler->Switch[0].Type = LastSwitch;
|
||||||
Filler->Switch[0].X = BucketFor(X + SidePad, XS, NB);
|
Filler->Switch[0].X = Bucket8For(X + SidePad, XS, NB);
|
||||||
} else {
|
} else {
|
||||||
/* diagonal proto */
|
/* diagonal proto */
|
||||||
|
|
||||||
@ -1736,36 +1709,34 @@ void InitTableFiller (FLOAT32 EndPad, FLOAT32 SidePad,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* translate into bucket positions and deltas */
|
/* translate into bucket positions and deltas */
|
||||||
Filler->X = (inT8) MapParam(Start.x, XS, NB);
|
Filler->X = Bucket8For(Start.x, XS, NB);
|
||||||
Filler->StartDelta = -(inT16) ((Cos / Sin) * 256);
|
Filler->StartDelta = -(inT16) ((Cos / Sin) * 256);
|
||||||
Filler->EndDelta = (inT16) ((Sin / Cos) * 256);
|
Filler->EndDelta = (inT16) ((Sin / Cos) * 256);
|
||||||
|
|
||||||
XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
|
XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
|
||||||
YAdjust = XAdjust * Cos / Sin;
|
YAdjust = XAdjust * Cos / Sin;
|
||||||
Filler->YStart = (inT16) MapParam(Start.y - YAdjust, YS, NB * 256);
|
Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
|
||||||
YAdjust = XAdjust * Sin / Cos;
|
YAdjust = XAdjust * Sin / Cos;
|
||||||
Filler->YEnd = (inT16) MapParam(Start.y + YAdjust, YS, NB * 256);
|
Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
|
||||||
|
|
||||||
Filler->Switch[S1].Type = StartSwitch;
|
Filler->Switch[S1].Type = StartSwitch;
|
||||||
Filler->Switch[S1].X = (inT8) MapParam(Switch1.x, XS, NB);
|
Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
|
||||||
Filler->Switch[S1].Y = (inT8) MapParam(Switch1.y, YS, NB);
|
Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
|
||||||
XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
|
XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
|
||||||
YAdjust = XAdjust * Sin / Cos;
|
YAdjust = XAdjust * Sin / Cos;
|
||||||
Filler->Switch[S1].YInit =
|
Filler->Switch[S1].YInit = Bucket16For(Switch1.y - YAdjust, YS, NB * 256);
|
||||||
(inT16) MapParam(Switch1.y - YAdjust, YS, NB * 256);
|
|
||||||
Filler->Switch[S1].Delta = Filler->EndDelta;
|
Filler->Switch[S1].Delta = Filler->EndDelta;
|
||||||
|
|
||||||
Filler->Switch[S2].Type = EndSwitch;
|
Filler->Switch[S2].Type = EndSwitch;
|
||||||
Filler->Switch[S2].X = (inT8) MapParam(Switch2.x, XS, NB);
|
Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
|
||||||
Filler->Switch[S2].Y = (inT8) MapParam(Switch2.y, YS, NB);
|
Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
|
||||||
XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
|
XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
|
||||||
YAdjust = XAdjust * Cos / Sin;
|
YAdjust = XAdjust * Cos / Sin;
|
||||||
Filler->Switch[S2].YInit =
|
Filler->Switch[S2].YInit = Bucket16For(Switch2.y + YAdjust, YS, NB * 256);
|
||||||
(inT16) MapParam(Switch2.y + YAdjust, YS, NB * 256);
|
|
||||||
Filler->Switch[S2].Delta = Filler->StartDelta;
|
Filler->Switch[S2].Delta = Filler->StartDelta;
|
||||||
|
|
||||||
Filler->Switch[2].Type = LastSwitch;
|
Filler->Switch[2].Type = LastSwitch;
|
||||||
Filler->Switch[2].X = (inT8)MapParam(End.x, XS, NB);
|
Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
|
||||||
} else {
|
} else {
|
||||||
/* falling diagonal proto */
|
/* falling diagonal proto */
|
||||||
Angle *= 2.0 * PI;
|
Angle *= 2.0 * PI;
|
||||||
@ -1788,36 +1759,34 @@ void InitTableFiller (FLOAT32 EndPad, FLOAT32 SidePad,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* translate into bucket positions and deltas */
|
/* translate into bucket positions and deltas */
|
||||||
Filler->X = (inT8) MapParam(Start.x, XS, NB);
|
Filler->X = Bucket8For(Start.x, XS, NB);
|
||||||
Filler->StartDelta = -(inT16) ((Sin / Cos) * 256);
|
Filler->StartDelta = -(inT16) ((Sin / Cos) * 256);
|
||||||
Filler->EndDelta = (inT16) ((Cos / Sin) * 256);
|
Filler->EndDelta = (inT16) ((Cos / Sin) * 256);
|
||||||
|
|
||||||
XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
|
XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
|
||||||
YAdjust = XAdjust * Sin / Cos;
|
YAdjust = XAdjust * Sin / Cos;
|
||||||
Filler->YStart = (inT16) MapParam(Start.y - YAdjust, YS, NB * 256);
|
Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
|
||||||
YAdjust = XAdjust * Cos / Sin;
|
YAdjust = XAdjust * Cos / Sin;
|
||||||
Filler->YEnd = (inT16) MapParam(Start.y + YAdjust, YS, NB * 256);
|
Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
|
||||||
|
|
||||||
Filler->Switch[S1].Type = EndSwitch;
|
Filler->Switch[S1].Type = EndSwitch;
|
||||||
Filler->Switch[S1].X = (inT8) MapParam(Switch1.x, XS, NB);
|
Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
|
||||||
Filler->Switch[S1].Y = (inT8) MapParam(Switch1.y, YS, NB);
|
Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
|
||||||
XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
|
XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
|
||||||
YAdjust = XAdjust * Sin / Cos;
|
YAdjust = XAdjust * Sin / Cos;
|
||||||
Filler->Switch[S1].YInit =
|
Filler->Switch[S1].YInit = Bucket16For(Switch1.y + YAdjust, YS, NB * 256);
|
||||||
(inT16) MapParam(Switch1.y + YAdjust, YS, NB * 256);
|
|
||||||
Filler->Switch[S1].Delta = Filler->StartDelta;
|
Filler->Switch[S1].Delta = Filler->StartDelta;
|
||||||
|
|
||||||
Filler->Switch[S2].Type = StartSwitch;
|
Filler->Switch[S2].Type = StartSwitch;
|
||||||
Filler->Switch[S2].X = (inT8) MapParam(Switch2.x, XS, NB);
|
Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
|
||||||
Filler->Switch[S2].Y = (inT8) MapParam(Switch2.y, YS, NB);
|
Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
|
||||||
XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
|
XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
|
||||||
YAdjust = XAdjust * Cos / Sin;
|
YAdjust = XAdjust * Cos / Sin;
|
||||||
Filler->Switch[S2].YInit =
|
Filler->Switch[S2].YInit = Bucket16For(Switch2.y - YAdjust, YS, NB * 256);
|
||||||
(inT16) MapParam(Switch2.y - YAdjust, YS, NB * 256);
|
|
||||||
Filler->Switch[S2].Delta = Filler->EndDelta;
|
Filler->Switch[S2].Delta = Filler->EndDelta;
|
||||||
|
|
||||||
Filler->Switch[2].Type = LastSwitch;
|
Filler->Switch[2].Type = LastSwitch;
|
||||||
Filler->Switch[2].X = (inT8) MapParam(End.x, XS, NB);
|
Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} /* InitTableFiller */
|
} /* InitTableFiller */
|
||||||
|
@ -218,9 +218,10 @@ void AddProtoToClassPruner(PROTO Proto,
|
|||||||
void AddProtoToProtoPruner(PROTO Proto, int ProtoId,
|
void AddProtoToProtoPruner(PROTO Proto, int ProtoId,
|
||||||
INT_CLASS Class, bool debug);
|
INT_CLASS Class, bool debug);
|
||||||
|
|
||||||
int BucketFor(FLOAT32 Param, FLOAT32 Offset, int NumBuckets);
|
uinT8 Bucket8For(FLOAT32 param, FLOAT32 offset, int num_buckets);
|
||||||
|
uinT16 Bucket16For(FLOAT32 param, FLOAT32 offset, int num_buckets);
|
||||||
|
|
||||||
int CircBucketFor(FLOAT32 Param, FLOAT32 Offset, int NumBuckets);
|
uinT8 CircBucketFor(FLOAT32 param, FLOAT32 offset, int num_buckets);
|
||||||
|
|
||||||
void UpdateMatchDisplay();
|
void UpdateMatchDisplay();
|
||||||
|
|
||||||
|
@ -26,36 +26,32 @@
|
|||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
Global Data Definitions and Declarations
|
Global Data Definitions and Declarations
|
||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
/**----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
Private Code
|
Private Code
|
||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
/*---------------------------------------------------------------------------*/
|
/**
|
||||||
|
* Call the old micro-feature extractor and then copy
|
||||||
|
* the features into the new format. Then deallocate the
|
||||||
|
* old micro-features.
|
||||||
|
* @param Blob blob to extract micro-features from
|
||||||
|
* @param denorm control parameter to feature extractor.
|
||||||
|
* @return Micro-features for Blob.
|
||||||
|
* @note Exceptions: none
|
||||||
|
* @note History: Wed May 23 18:06:38 1990, DSJ, Created.
|
||||||
|
*/
|
||||||
FEATURE_SET ExtractMicros(TBLOB *Blob, const DENORM& bl_denorm,
|
FEATURE_SET ExtractMicros(TBLOB *Blob, const DENORM& bl_denorm,
|
||||||
const DENORM& cn_denorm,
|
const DENORM& cn_denorm,
|
||||||
const INT_FX_RESULT_STRUCT& fx_info) {
|
const INT_FX_RESULT_STRUCT& fx_info) {
|
||||||
/*
|
|
||||||
** Parameters:
|
|
||||||
** Blob blob to extract micro-features from
|
|
||||||
** denorm control parameter to feature extractor.
|
|
||||||
** Globals: none
|
|
||||||
** Operation: Call the old micro-feature extractor and then copy
|
|
||||||
** the features into the new format. Then deallocate the
|
|
||||||
** old micro-features.
|
|
||||||
** Return: Micro-features for Blob.
|
|
||||||
** Exceptions: none
|
|
||||||
** History: Wed May 23 18:06:38 1990, DSJ, Created.
|
|
||||||
*/
|
|
||||||
int NumFeatures;
|
int NumFeatures;
|
||||||
MICROFEATURES Features, OldFeatures;
|
MICROFEATURES Features, OldFeatures;
|
||||||
FEATURE_SET FeatureSet;
|
FEATURE_SET FeatureSet;
|
||||||
FEATURE Feature;
|
FEATURE Feature;
|
||||||
MICROFEATURE OldFeature;
|
MICROFEATURE OldFeature;
|
||||||
|
|
||||||
OldFeatures = (MICROFEATURES)BlobMicroFeatures(Blob, bl_denorm, cn_denorm,
|
OldFeatures = BlobMicroFeatures(Blob, cn_denorm);
|
||||||
fx_info);
|
|
||||||
if (OldFeatures == NULL)
|
if (OldFeatures == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
NumFeatures = count (OldFeatures);
|
NumFeatures = count (OldFeatures);
|
||||||
|
@ -34,8 +34,6 @@ typedef float MicroFeature[MFCount];
|
|||||||
/*----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
Private Function Prototypes
|
Private Function Prototypes
|
||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
FEATURE_SET ExtractMicros(TBLOB *Blob, const DENORM& bl_denorm,
|
FEATURE_SET ExtractMicros(TBLOB* Blob, const DENORM& cn_denorm);
|
||||||
const DENORM& cn_denorm,
|
|
||||||
const INT_FX_RESULT_STRUCT& fx_info);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
#include "oldlist.h"
|
#include "oldlist.h"
|
||||||
#include "matchdefs.h"
|
#include "matchdefs.h"
|
||||||
#include "xform2d.h"
|
|
||||||
|
|
||||||
/* definition of a list of micro-features */
|
/* definition of a list of micro-features */
|
||||||
typedef LIST MICROFEATURES;
|
typedef LIST MICROFEATURES;
|
||||||
|
@ -59,9 +59,7 @@ MICROFEATURE ExtractMicroFeature(MFOUTLINE Start, MFOUTLINE End);
|
|||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
CHAR_FEATURES BlobMicroFeatures(TBLOB *Blob, const DENORM& bl_denorm,
|
MICROFEATURES BlobMicroFeatures(TBLOB* Blob, const DENORM& cn_denorm) {
|
||||||
const DENORM& cn_denorm,
|
|
||||||
const INT_FX_RESULT_STRUCT& fx_info) {
|
|
||||||
/*
|
/*
|
||||||
** Parameters:
|
** Parameters:
|
||||||
** Blob blob to extract micro-features from
|
** Blob blob to extract micro-features from
|
||||||
@ -98,7 +96,7 @@ CHAR_FEATURES BlobMicroFeatures(TBLOB *Blob, const DENORM& bl_denorm,
|
|||||||
}
|
}
|
||||||
FreeOutlines(Outlines);
|
FreeOutlines(Outlines);
|
||||||
}
|
}
|
||||||
return ((CHAR_FEATURES) MicroFeatures);
|
return MicroFeatures;
|
||||||
} /* BlobMicroFeatures */
|
} /* BlobMicroFeatures */
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
/*----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
Include Files and Type Defines
|
Include Files and Type Defines
|
||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
|
#include "mfdefs.h"
|
||||||
#include "params.h"
|
#include "params.h"
|
||||||
/*----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
Variables
|
Variables
|
||||||
@ -35,8 +36,6 @@ extern double_VAR_H(classify_max_slope, 2.414213562,
|
|||||||
/*----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
Public Function Prototypes
|
Public Function Prototypes
|
||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
CHAR_FEATURES BlobMicroFeatures(TBLOB *Blob, const DENORM& bl_denorm,
|
MICROFEATURES BlobMicroFeatures(TBLOB* Blob, const DENORM& cn_denorm);
|
||||||
const DENORM& cn_denorm,
|
|
||||||
const INT_FX_RESULT_STRUCT& fx_info);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -59,9 +59,7 @@ FLOAT32 ActualOutlineLength(FEATURE Feature) {
|
|||||||
// the x center of the grapheme's bounding box.
|
// the x center of the grapheme's bounding box.
|
||||||
// English: [0.011, 0.31]
|
// English: [0.011, 0.31]
|
||||||
//
|
//
|
||||||
FEATURE_SET ExtractCharNormFeatures(TBLOB *blob, const DENORM& bl_denorm,
|
FEATURE_SET ExtractCharNormFeatures(const INT_FX_RESULT_STRUCT& fx_info) {
|
||||||
const DENORM& cn_denorm,
|
|
||||||
const INT_FX_RESULT_STRUCT& fx_info) {
|
|
||||||
FEATURE_SET feature_set = NewFeatureSet(1);
|
FEATURE_SET feature_set = NewFeatureSet(1);
|
||||||
FEATURE feature = NewFeature(&CharNormDesc);
|
FEATURE feature = NewFeature(&CharNormDesc);
|
||||||
|
|
||||||
|
@ -34,8 +34,6 @@ typedef enum {
|
|||||||
----------------------------------------------------------------------------**/
|
----------------------------------------------------------------------------**/
|
||||||
FLOAT32 ActualOutlineLength(FEATURE Feature);
|
FLOAT32 ActualOutlineLength(FEATURE Feature);
|
||||||
|
|
||||||
FEATURE_SET ExtractCharNormFeatures(TBLOB *Blob, const DENORM& bl_denorm,
|
FEATURE_SET ExtractCharNormFeatures(const INT_FX_RESULT_STRUCT& fx_info);
|
||||||
const DENORM& cn_denorm,
|
|
||||||
const INT_FX_RESULT_STRUCT& fx_info);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -209,55 +209,52 @@ FEATURE_SET ReadFeatureSet(FILE *File, const FEATURE_DESC_STRUCT* FeatureDesc) {
|
|||||||
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
void WriteFeature(FILE *File, FEATURE Feature) {
|
|
||||||
/*
|
/*
|
||||||
** Parameters:
|
** Parameters:
|
||||||
** File open text file to write Feature to
|
** Feature: feature to write out to str
|
||||||
** Feature feature to write out to File
|
** str: string to write Feature to
|
||||||
** Globals: none
|
** Operation: Appends a textual representation of Feature to str.
|
||||||
** Operation: Write a textual representation of Feature to File.
|
** This representation is simply a list of the N parameters
|
||||||
** This representation is simply a list of the N parameters
|
** of the feature, terminated with a newline. It is assumed
|
||||||
** of the feature, terminated with a newline. It is assumed
|
** that the ExtraPenalty field can be reconstructed from the
|
||||||
** that the ExtraPenalty field can be reconstructed from the
|
** parameters of the feature. It is also assumed that the
|
||||||
** parameters of the feature. It is also assumed that the
|
** feature type information is specified or assumed elsewhere.
|
||||||
** feature type information is specified or assumed elsewhere.
|
** Return: none
|
||||||
** Return: none
|
** Exceptions: none
|
||||||
** Exceptions: none
|
** History: Wed May 23 09:28:18 1990, DSJ, Created.
|
||||||
** History: Wed May 23 09:28:18 1990, DSJ, Created.
|
|
||||||
*/
|
*/
|
||||||
int i;
|
void WriteFeature(FEATURE Feature, STRING* str) {
|
||||||
|
for (int i = 0; i < Feature->Type->NumParams; i++) {
|
||||||
for (i = 0; i < Feature->Type->NumParams; i++) {
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
assert(!isnan(Feature->Params[i]));
|
assert(!isnan(Feature->Params[i]));
|
||||||
#endif
|
#endif
|
||||||
fprintf(File, " %g", Feature->Params[i]);
|
str->add_str_double(" ", Feature->Params[i]);
|
||||||
}
|
}
|
||||||
fprintf(File, "\n");
|
*str += "\n";
|
||||||
} /* WriteFeature */
|
} /* WriteFeature */
|
||||||
|
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
void WriteFeatureSet(FILE *File, FEATURE_SET FeatureSet) {
|
|
||||||
/*
|
/*
|
||||||
** Parameters:
|
** Parameters:
|
||||||
** File open text file to write FeatureSet to
|
** FeatureSet: feature set to write to File
|
||||||
** FeatureSet feature set to write to File
|
** str: string to write Feature to
|
||||||
** Globals: none
|
** Globals: none
|
||||||
** Operation: Write a textual representation of FeatureSet to File.
|
** Operation: Write a textual representation of FeatureSet to File.
|
||||||
** This representation is an integer specifying the number of
|
** This representation is an integer specifying the number of
|
||||||
** features in the set, followed by a newline, followed by
|
** features in the set, followed by a newline, followed by
|
||||||
** text representations for each feature in the set.
|
** text representations for each feature in the set.
|
||||||
** Return: none
|
** Return: none
|
||||||
** Exceptions: none
|
** Exceptions: none
|
||||||
** History: Wed May 23 10:06:03 1990, DSJ, Created.
|
** History: Wed May 23 10:06:03 1990, DSJ, Created.
|
||||||
*/
|
*/
|
||||||
int i;
|
void WriteFeatureSet(FEATURE_SET FeatureSet, STRING* str) {
|
||||||
|
|
||||||
if (FeatureSet) {
|
if (FeatureSet) {
|
||||||
fprintf (File, "%d\n", FeatureSet->NumFeatures);
|
str->add_str_int("", FeatureSet->NumFeatures);
|
||||||
for (i = 0; i < FeatureSet->NumFeatures; i++)
|
*str += "\n";
|
||||||
WriteFeature (File, FeatureSet->Features[i]);
|
for (int i = 0; i < FeatureSet->NumFeatures; i++) {
|
||||||
|
WriteFeature(FeatureSet->Features[i], str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} /* WriteFeatureSet */
|
} /* WriteFeatureSet */
|
||||||
|
|
||||||
|
@ -79,13 +79,6 @@ typedef FEATURE_SET_STRUCT *FEATURE_SET;
|
|||||||
// classifier does not need to know the details of this data structure.
|
// classifier does not need to know the details of this data structure.
|
||||||
typedef char *CHAR_FEATURES;
|
typedef char *CHAR_FEATURES;
|
||||||
|
|
||||||
typedef FEATURE_SET (*FX_FUNC)(TBLOB *, const DENORM&, const DENORM&,
|
|
||||||
const INT_FX_RESULT_STRUCT&);
|
|
||||||
|
|
||||||
struct FEATURE_EXT_STRUCT {
|
|
||||||
FX_FUNC Extractor; // func to extract features
|
|
||||||
};
|
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
Macros for defining the parameters of a new features
|
Macros for defining the parameters of a new features
|
||||||
----------------------------------------------------------------------*/
|
----------------------------------------------------------------------*/
|
||||||
@ -125,10 +118,8 @@ FEATURE ReadFeature(FILE *File, const FEATURE_DESC_STRUCT *FeatureDesc);
|
|||||||
|
|
||||||
FEATURE_SET ReadFeatureSet(FILE *File, const FEATURE_DESC_STRUCT *FeatureDesc);
|
FEATURE_SET ReadFeatureSet(FILE *File, const FEATURE_DESC_STRUCT *FeatureDesc);
|
||||||
|
|
||||||
void WriteFeature(FILE *File, FEATURE Feature);
|
void WriteFeature(FEATURE Feature, STRING* str);
|
||||||
|
|
||||||
void WriteFeatureSet(FILE *File, FEATURE_SET FeatureSet);
|
void WriteFeatureSet(FEATURE_SET FeatureSet, STRING* str);
|
||||||
|
|
||||||
void WriteOldParamDesc(FILE *File, const FEATURE_DESC_STRUCT *FeatureDesc);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user