blob: 2a7388d6ff8cf4672d57e18940eebe62f40c05e8 [file] [log] [blame]
/**********************************************************************
* File: baseapi.cpp
* Description: Simple API for calling tesseract.
* Author: Ray Smith
* Created: Fri Oct 06 15:35:01 PDT 2006
*
* (C) Copyright 2006, Google Inc.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
// Include automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif
#ifdef HAVE_LIBLEPT
// Include leptonica library only if autoconf (or makefile etc) tell us to.
#include "allheaders.h"
#endif
#include "baseapi.h"
#include "thresholder.h"
#include "tesseractmain.h"
#include "tesseractclass.h"
#include "tessedit.h"
#include "ocrclass.h"
#include "pageres.h"
#include "tessvars.h"
#include "control.h"
#include "applybox.h"
#include "pgedit.h"
#include "varabled.h"
#include "output.h"
#include "mainblk.h"
#include "globals.h"
#include "adaptmatch.h"
#include "edgblob.h"
#include "tessbox.h"
#include "tordvars.h"
#include "imgs.h"
#include "makerow.h"
#include "tstruct.h"
#include "tessout.h"
#include "tface.h"
#include "permute.h"
#include "otsuthr.h"
#include "osdetect.h"
#include "chopper.h"
#include "matchtab.h"
namespace tesseract {
// Minimum sensible image size to be worth running tesseract.
const int kMinRectSize = 10;
// Character returned when Tesseract couldn't recognize as anything.
const char kTesseractReject = '~';
// Character used by UNLV error counter as a reject.
const char kUNLVReject = '~';
// Character used by UNLV as a suspect marker.
const char kUNLVSuspect = '^';
// Filename used for input image file, from which to derive a name to search
// for a possible UNLV zone file, if none is specified by SetInputName.
const char* kInputFile = "noname.tif";
TessBaseAPI::TessBaseAPI()
: tesseract_(NULL),
// Thresholder is initialized to NULL here, but will be set before use by:
// A constructor of a derived API, SetThresholder(), or
// created implicitly when used in InternalSetImage.
thresholder_(NULL),
threshold_done_(false),
block_list_(NULL),
page_res_(NULL),
input_file_(NULL),
output_file_(NULL),
datapath_(NULL),
language_(NULL),
rect_left_(0), rect_top_(0), rect_width_(0), rect_height_(0),
image_width_(0), image_height_(0) {
}
TessBaseAPI::~TessBaseAPI() {
End();
}
// Set the name of the input file. Needed only for training and
// loading a UNLV zone file.
void TessBaseAPI::SetInputName(const char* name) {
if (input_file_ == NULL)
input_file_ = new STRING(name);
else
*input_file_ = name;
}
// Set the name of the output files. Needed only for debugging.
void TessBaseAPI::SetOutputName(const char* name) {
if (output_file_ == NULL)
output_file_ = new STRING(name);
else
*output_file_ = name;
}
// Set the value of an internal "variable" (of either old or new types).
// Supply the name of the variable and the value as a string, just as
// you would in a config file.
// Returns false if the name lookup failed.
// SetVariable may be used before Init, to set things that control
// initialization, but note that on End all settings are lost and
// the next Init will use the defaults unless SetVariable is used again.
bool TessBaseAPI::SetVariable(const char* variable, const char* value) {
if (tesseract_ == NULL)
tesseract_ = new Tesseract;
return set_variable(variable, value);
}
// The datapath must be the name of the data directory (no ending /) or
// some other file in which the data directory resides (for instance argv[0].)
// The language is (usually) an ISO 639-3 string or NULL will default to eng.
// If numeric_mode is true, then only digits and Roman numerals will
// be returned.
// Returns 0 on success and -1 on initialization failure.
int TessBaseAPI::Init(const char* datapath, const char* language,
char **configs, int configs_size,
bool configs_global_only) {
// If the datapath or the language have changed, then start again.
// Note that the language_ field stores the last requested language that was
// initialized successfully, while tesseract_->lang stores the language
// actually used. They differ only if the requested language was NULL, in
// which case tesseract_->lang is set to the Tesseract default ("eng").
if (tesseract_ != NULL &&
(datapath_ == NULL || language_ == NULL || *datapath_ != datapath
|| (*language_ != language && tesseract_->lang != language))) {
tesseract_->end_tesseract();
delete tesseract_;
tesseract_ = NULL;
}
bool reset_classifier = true;
if (tesseract_ == NULL) {
reset_classifier = false;
tesseract_ = new Tesseract;
if (tesseract_->init_tesseract(
datapath, output_file_ != NULL ? output_file_->string() : NULL,
language, configs, configs_size, configs_global_only) != 0) {
return -1;
}
}
// Update datapath and language requested for the last valid initialization.
if (datapath_ == NULL)
datapath_ = new STRING(datapath);
else
*datapath_ = datapath;
if (language_ == NULL)
language_ = new STRING(language);
else
*language_ = language;
// For same language and datapath, just reset the adaptive classifier.
if (reset_classifier) tesseract_->ResetAdaptiveClassifier();
return 0;
}
// Init only the lang model component of Tesseract. The only functions
// that work after this init are SetVariable and IsValidWord.
// WARNING: temporary! This function will be removed from here and placed
// in a separate API at some future time.
int TessBaseAPI::InitLangMod(const char* datapath, const char* language) {
if (tesseract_ == NULL)
tesseract_ = new Tesseract;
return tesseract_->init_tesseract_lm(datapath, NULL, language);
}
// Init only the classifer component of Tesseract. Used to initialize the
// specified language when no dawg models are available.
int TessBaseAPI::InitWithoutLangModel(const char* datapath,
const char* language) {
// If the datapath or the language have changed, then start again.
if (tesseract_ != NULL &&
(datapath_ == NULL || language_ == NULL ||
*datapath_ != datapath || *language_ != language)) {
tesseract_->end_tesseract();
delete tesseract_;
tesseract_ = NULL;
}
if (datapath_ == NULL)
datapath_ = new STRING(datapath);
else
*datapath_ = datapath;
if (language_ == NULL)
language_ = new STRING(language);
else
*language_ = language;
if (tesseract_ == NULL) {
tesseract_ = new Tesseract;
return tesseract_->init_tesseract_classifier(
datapath, output_file_ != NULL ? output_file_->string() : NULL,
language, NULL, 0, false);
}
// For same language and datapath, just reset the adaptive classifier.
tesseract_->ResetAdaptiveClassifier();
return 0;
}
// Read a "config" file containing a set of variable, value pairs.
// Searches the standard places: tessdata/configs, tessdata/tessconfigs
// and also accepts a relative or absolute path name.
void TessBaseAPI::ReadConfigFile(const char* filename, bool global_only) {
tesseract_->read_config_file(filename, global_only);
}
// Set the current page segmentation mode. Defaults to PSM_AUTO.
// The mode is stored as an INT_VARIABLE so it can also be modified by
// ReadConfigFile or SetVariable("tessedit_pageseg_mode", mode as string).
void TessBaseAPI::SetPageSegMode(PageSegMode mode) {
if (tesseract_ == NULL)
tesseract_ = new Tesseract;
tesseract_->tessedit_pageseg_mode.set_value(mode);
}
// Return the current page segmentation mode.
PageSegMode TessBaseAPI::GetPageSegMode() const {
if (tesseract_ == NULL)
return PSM_SINGLE_BLOCK;
return static_cast<PageSegMode>(
static_cast<int>(tesseract_->tessedit_pageseg_mode));
}
// Set the hint for trading accuracy against speed.
// Default is AVS_FASTEST, which is the old behaviour.
// Note that this is only a hint. Depending on the language and/or
// build configuration, speed and accuracy may not be tradeable.
// Also note that despite being an enum, any value in the range
// AVS_FASTEST to AVS_MOST_ACCURATE can be provided, and may or may not
// have an effect, depending on the implementation.
// The mode is stored as an INT_VARIABLE so it can also be modified by
// ReadConfigFile or SetVariable("tessedit_accuracyvspeed", mode as string).
void TessBaseAPI::SetAccuracyVSpeed(AccuracyVSpeed mode) {
if (tesseract_ == NULL)
tesseract_ = new Tesseract;
tesseract_->tessedit_accuracyvspeed.set_value(mode);
}
// Recognize a rectangle from an image and return the result as a string.
// May be called many times for a single Init.
// Currently has no error checking.
// Greyscale of 8 and color of 24 or 32 bits per pixel may be given.
// Palette color images will not work properly and must be converted to
// 24 bit.
// Binary images of 1 bit per pixel may also be given but they must be
// byte packed with the MSB of the first byte being the first pixel, and a
// one pixel is WHITE. For binary images set bytes_per_pixel=0.
// The recognized text is returned as a char* which is coded
// as UTF8 and must be freed with the delete [] operator.
char* TessBaseAPI::TesseractRect(const unsigned char* imagedata,
int bytes_per_pixel,
int bytes_per_line,
int left, int top,
int width, int height) {
if (tesseract_ == NULL || width < kMinRectSize || height < kMinRectSize)
return NULL; // Nothing worth doing.
// Since this original api didn't give the exact size of the image,
// we have to invent a reasonable value.
int bits_per_pixel = bytes_per_pixel == 0 ? 1 : bytes_per_pixel * 8;
SetImage(imagedata, bytes_per_line * 8 / bits_per_pixel, height,
bytes_per_pixel, bytes_per_line);
SetRectangle(left, top, width, height);
return GetUTF8Text();
}
// Call between pages or documents etc to free up memory and forget
// adaptive data.
void TessBaseAPI::ClearAdaptiveClassifier() {
if (tesseract_ == NULL)
return;
tesseract_->ResetAdaptiveClassifier();
}
// Provide an image for Tesseract to recognize. Format is as
// TesseractRect above. Does not copy the image buffer, or take
// ownership. The source image may be destroyed after Recognize is called,
// either explicitly or implicitly via one of the Get*Text functions.
// SetImage clears all recognition results, and sets the rectangle to the
// full image, so it may be followed immediately by a GetUTF8Text, and it
// will automatically perform recognition.
void TessBaseAPI::SetImage(const unsigned char* imagedata,
int width, int height,
int bytes_per_pixel, int bytes_per_line) {
if (InternalSetImage())
thresholder_->SetImage(imagedata, width, height,
bytes_per_pixel, bytes_per_line);
}
// Provide an image for Tesseract to recognize. As with SetImage above,
// Tesseract doesn't take a copy or ownership or pixDestroy the image, so
// it must persist until after Recognize.
// Pix vs raw, which to use?
// Use Pix where possible. A future version of Tesseract may choose to use Pix
// as its internal representation and discard IMAGE altogether.
// Because of that, an implementation that sources and targets Pix may end up
// with less copies than an implementation that does not.
void TessBaseAPI::SetImage(const Pix* pix) {
#ifdef HAVE_LIBLEPT
if (InternalSetImage())
thresholder_->SetImage(pix);
#endif
}
// Restrict recognition to a sub-rectangle of the image. Call after SetImage.
// Each SetRectangle clears the recogntion results so multiple rectangles
// can be recognized with the same image.
void TessBaseAPI::SetRectangle(int left, int top, int width, int height) {
if (thresholder_ == NULL)
return;
thresholder_->SetRectangle(left, top, width, height);
ClearResults();
}
// ONLY available if you have Leptonica installed.
// Get a copy of the internal thresholded image from Tesseract.
Pix* TessBaseAPI::GetThresholdedImage() {
#ifdef HAVE_LIBLEPT
if (tesseract_ == NULL)
return NULL;
if (tesseract_->pix_binary() == NULL)
Threshold(tesseract_->mutable_pix_binary());
return pixClone(tesseract_->pix_binary());
#else
return NULL;
#endif
}
// Get the result of page layout analysis as a leptonica-style
// Boxa, Pixa pair, in reading order.
// Can be called before or after Recognize.
// For now only gets text regions.
Boxa* TessBaseAPI::GetRegions(Pixa** pixa) {
#ifdef HAVE_LIBLEPT
if (block_list_ == NULL || block_list_->empty()) {
FindLines();
}
int im_height = pixGetHeight(tesseract_->pix_binary());
Boxa* boxa = boxaCreate(block_list_->length());
if (pixa != NULL) {
*pixa = pixaCreate(boxaGetCount(boxa));
}
BLOCK_IT it(block_list_);
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
BLOCK* block = it.data();
POLY_BLOCK* poly = block->poly_block();
TBOX box;
if (poly != NULL) {
if (!poly->IsText())
continue; // Use only text blocks.
POLY_BLOCK image_block(poly->points(), poly->isA());
image_block.rotate(block->re_rotation());
box = *image_block.bounding_box();
if (pixa != NULL) {
Pix* pix = pixCreate(box.width(), box.height(), 1);
PB_LINE_IT *lines;
// Block outline is a polygon, so use a PC_LINE_IT to get the
// rasterized interior. (Runs of interior pixels on a line.)
lines = new PB_LINE_IT(&image_block);
for (int y = box.bottom(); y < box.top(); ++y) {
ICOORDELT_LIST* segments = lines->get_line(y);
if (!segments->empty()) {
ICOORDELT_IT s_it(segments);
// Each element of segments is a start x and x size of the
// run of interior pixels.
for (s_it.mark_cycle_pt(); !s_it.cycled_list(); s_it.forward()) {
int start = s_it.data()->x();
int xext = s_it.data()->y();
// Copy the run from the source image to the block image.
pixRasterop(pix, start - box.left(),
box.height() - 1 - (y - box.bottom()),
xext, 1, PIX_SRC, tesseract_->pix_binary(),
start, im_height - 1 - y);
}
}
delete segments;
}
delete lines;
pixaAddPix(*pixa, pix, L_INSERT);
}
} else {
if (!block_list_->singleton())
continue; // A null poly block can only be used if it is the only block.
box = block->bounding_box();
if (pixa != NULL) {
Pix* pix = pixCreate(box.width(), box.height(), 1);
// Just copy the whole block as there is only a bounding box.
pixRasterop(pix, 0, 0, box.width(), box.height(),
PIX_SRC, tesseract_->pix_binary(),
box.left(), im_height - box.top());
pixaAddPix(*pixa, pix, L_INSERT);
}
}
Box* lbox = boxCreate(box.left(), im_height - box.top(),
box.width(), box.height());
boxaAddBox(boxa, lbox, L_INSERT);
}
return boxa;
#else
return NULL;
#endif
}
// Get the textlines as a leptonica-style
// Boxa, Pixa pair, in reading order.
// Can be called before or after Recognize.
// If blockids is not NULL, the block-id of each line is also returned as an
// array of one element per line. delete [] after use.
Boxa* TessBaseAPI::GetTextlines(Pixa** pixa, int** blockids) {
#ifdef HAVE_LIBLEPT
if (block_list_ == NULL || block_list_->empty()) {
FindLines();
}
// A local PAGE_RES prevents the clear if Recognize is called after.
PAGE_RES page_res(block_list_);
PAGE_RES_IT page_res_it(page_res_ != NULL ? page_res_ : &page_res);
// Count the lines to get a size for the arrays.
int line_count = 0;
for (page_res_it.restart_page(); page_res_it.word() != NULL;
page_res_it.forward()) {
if (page_res_it.row() != page_res_it.next_row()) {
++line_count;
}
}
int im_height = pixGetHeight(tesseract_->pix_binary());
Boxa* boxa = boxaCreate(line_count);
if (pixa != NULL)
*pixa = pixaCreate(line_count);
if (blockids != NULL)
*blockids = new int[line_count];
int blockid = 0;
int lineindex = 0;
for (page_res_it.restart_page(); page_res_it.word() != NULL;
page_res_it.forward(), ++lineindex) {
WERD_RES *word = page_res_it.word();
BLOCK* block = page_res_it.block()->block;
// Get the line bounding box.
PAGE_RES_IT word_it(page_res_it); // Save start of line.
TBOX line_box = word->word->bounding_box();
while (page_res_it.next_row() == page_res_it.row()) {
page_res_it.forward();
word = page_res_it.word();
TBOX word_box = word->word->bounding_box();
word_box.rotate(block->re_rotation());
line_box += word_box;
}
Box* lbox = boxCreate(line_box.left(), im_height - line_box.top(),
line_box.width(), line_box.height());
boxaAddBox(boxa, lbox, L_INSERT);
if (pixa != NULL) {
Pix* pix = pixCreate(line_box.width(), line_box.height(), 1);
// Copy all the words to the output pix.
while (word_it.row() == page_res_it.row()) {
word = word_it.word();
TBOX word_box = word->word->bounding_box();
word_box.rotate(block->re_rotation());
pixRasterop(pix, word_box.left() - line_box.left(),
line_box.top() - word_box.top(),
word_box.width(), word_box.height(),
PIX_SRC, tesseract_->pix_binary(),
word_box.left(), im_height - word_box.top());
word_it.forward();
}
pixaAddPix(*pixa, pix, L_INSERT);
pixaAddBox(*pixa, lbox, L_CLONE);
}
if (blockids != NULL) {
(*blockids)[lineindex] = blockid;
if (page_res_it.block() != page_res_it.next_block())
++blockid;
}
}
return boxa;
#else
return NULL;
#endif
}
// Get the words as a leptonica-style
// Boxa, Pixa pair, in reading order.
// Can be called before or after Recognize.
Boxa* TessBaseAPI::GetWords(Pixa** pixa) {
#ifdef HAVE_LIBLEPT
if (block_list_ == NULL || block_list_->empty()) {
FindLines();
}
// A local PAGE_RES prevents the clear if Recognize is called after.
PAGE_RES page_res(block_list_);
PAGE_RES_IT page_res_it(page_res_ != NULL ? page_res_ : &page_res);
// Count the words to get a size for the arrays.
int word_count = 0;
for (page_res_it.restart_page(); page_res_it.word () != NULL;
page_res_it.forward())
++word_count;
int im_height = pixGetHeight(tesseract_->pix_binary());
Boxa* boxa = boxaCreate(word_count);
if (pixa != NULL) {
*pixa = pixaCreate(word_count);
}
for (page_res_it.restart_page(); page_res_it.word () != NULL;
page_res_it.forward()) {
WERD_RES *word = page_res_it.word();
BLOCK* block = page_res_it.block()->block;
TBOX box = word->word->bounding_box();
box.rotate(block->re_rotation());
Box* lbox = boxCreate(box.left(), im_height - box.top(),
box.width(), box.height());
boxaAddBox(boxa, lbox, L_INSERT);
if (pixa != NULL) {
Pix* pix = pixCreate(box.width(), box.height(), 1);
// Copy the whole word bounding box to the output pix.
pixRasterop(pix, 0, 0, box.width(), box.height(),
PIX_SRC, tesseract_->pix_binary(),
box.left(), im_height - box.top());
pixaAddPix(*pixa, pix, L_INSERT);
pixaAddBox(*pixa, lbox, L_CLONE);
}
}
return boxa;
#else
return NULL;
#endif // HAVE_LIBLEPT
}
// Dump the internal binary image to a PGM file.
void TessBaseAPI::DumpPGM(const char* filename) {
if (tesseract_ == NULL)
return;
IMAGELINE line;
line.init(page_image.get_xsize());
FILE *fp = fopen(filename, "w");
fprintf(fp, "P5 " INT32FORMAT " " INT32FORMAT " 255\n",
page_image.get_xsize(), page_image.get_ysize());
for (int j = page_image.get_ysize()-1; j >= 0 ; --j) {
page_image.get_line(0, j, page_image.get_xsize(), &line, 0);
for (int i = 0; i < page_image.get_xsize(); ++i) {
uinT8 b = line.pixels[i] ? 255 : 0;
fwrite(&b, 1, 1, fp);
}
}
fclose(fp);
}
// Recognize the tesseract global image and return the result as Tesseract
// internal structures.
int TessBaseAPI::Recognize(struct ETEXT_STRUCT* monitor) {
if (tesseract_ == NULL)
return -1;
if (thresholder_ == NULL || thresholder_->IsEmpty()) {
tprintf("Please call SetImage before attempting recognition.");
return -1;
}
if (page_res_ != NULL)
ClearResults();
if (FindLines() != 0)
return -1;
if (tesseract_->tessedit_resegment_from_boxes)
tesseract_->apply_boxes(*input_file_, block_list_);
tesseract_->SetBlackAndWhitelist();
page_res_ = new PAGE_RES(block_list_);
int result = 0;
if (interactive_mode) {
#ifndef GRAPHICS_DISABLED
tesseract_->pgeditor_main(block_list_);
#endif
// The page_res is invalid after an interactive session, so cleanup
// in a way that lets us continue to the next page without crashing.
delete page_res_;
page_res_ = NULL;
return -1;
} else if (tesseract_->tessedit_train_from_boxes) {
apply_box_training(*output_file_, block_list_);
} else if (tesseract_->global_tessedit_ambigs_training) {
FILE *ambigs_output_file = tesseract_->init_ambigs_training(*input_file_);
// OCR the page segmented into words by tesseract.
tesseract_->ambigs_training_segmented(
*input_file_, page_res_, monitor, ambigs_output_file);
fclose(ambigs_output_file);
} else {
// Now run the main recognition.
// Running base tesseract if the inttemp for the current language loaded.
if (tesseract_->inttemp_loaded_) {
tesseract_->recog_all_words(page_res_, monitor);
}
}
return result;
}
// Tests the chopper by exhaustively running chop_one_blob.
int TessBaseAPI::RecognizeForChopTest(struct ETEXT_STRUCT* monitor) {
if (tesseract_ == NULL)
return -1;
if (thresholder_ == NULL || thresholder_->IsEmpty()) {
tprintf("Please call SetImage before attempting recognition.");
return -1;
}
if (page_res_ != NULL)
ClearResults();
if (FindLines() != 0)
return -1;
// Additional conditions under which chopper test cannot be run
if (tesseract_->tessedit_train_from_boxes_word_level || interactive_mode)
return -1;
ASSERT_HOST(tesseract_->inttemp_loaded_);
page_res_ = new PAGE_RES(block_list_);
PAGE_RES_IT page_res_it(page_res_);
tesseract_->tess_matcher = &Tesseract::tess_default_matcher;
tesseract_->tess_tester = NULL;
tesseract_->tess_trainer = NULL;
while (page_res_it.word() != NULL) {
WERD_RES *word_res = page_res_it.word();
WERD *word = word_res->word;
if (word->cblob_list()->empty()) {
page_res_it.forward();
continue;
}
WERD *bln_word = make_bln_copy(word, page_res_it.row()->row,
page_res_it.block()->block,
word_res->x_height, &word_res->denorm);
ASSERT_HOST(!bln_word->blob_list()->empty());
TWERD *tessword = make_tess_word(bln_word, NULL);
if (tessword->blobs == NULL) {
make_tess_word(bln_word, NULL);
}
TBLOB *pblob;
TBLOB *blob;
init_match_table();
BLOB_CHOICE_LIST *match_result;
BLOB_CHOICE_LIST_VECTOR *char_choices = new BLOB_CHOICE_LIST_VECTOR();
tesseract_->tess_denorm = &word_res->denorm;
tesseract_->tess_word = bln_word;
ASSERT_HOST(tessword->blobs != NULL);
for (blob = tessword->blobs, pblob = NULL;
blob != NULL; blob = blob->next) {
match_result = tesseract_->classify_blob(pblob, blob, blob->next, NULL,
"chop_word:", Green);
if (match_result == NULL)
tprintf("Null classifier output!\n");
tesseract_->modify_blob_choice(match_result, 0);
ASSERT_HOST(!match_result->empty());
*char_choices += match_result;
pblob = blob;
}
inT32 blob_number;
SEAMS seam_list = start_seam_list(tessword->blobs);
int right_chop_index = 0;
while (tesseract_->chop_one_blob(tessword, char_choices,
&blob_number, &seam_list,
&right_chop_index)) {
}
word_res->best_choice = new WERD_CHOICE();
word_res->raw_choice = new WERD_CHOICE();
word_res->best_choice->make_bad();
word_res->raw_choice->make_bad();
tesseract_->getDict().permute_characters(*char_choices, 1000.0,
word_res->best_choice,
word_res->raw_choice);
word_res->outword = make_ed_word(tessword, bln_word);
page_res_it.forward();
}
return 0;
}
// Make a text string from the internal data structures.
char* TessBaseAPI::GetUTF8Text() {
if (tesseract_ == NULL ||
(page_res_ == NULL && Recognize(NULL) < 0))
return NULL;
int total_length = TextLength(NULL);
PAGE_RES_IT page_res_it(page_res_);
char* result = new char[total_length];
char* ptr = result;
for (page_res_it.restart_page(); page_res_it.word () != NULL;
page_res_it.forward()) {
WERD_RES *word = page_res_it.word();
WERD_CHOICE* choice = word->best_choice;
if (choice != NULL) {
strcpy(ptr, choice->unichar_string().string());
ptr += choice->unichar_string().length();
if (word->word->flag(W_EOL))
*ptr++ = '\n';
else
*ptr++ = ' ';
}
}
*ptr++ = '\n';
*ptr = '\0';
return result;
}
static int ConvertWordToBoxText(WERD_RES *word,
ROW_RES* row,
int left,
int bottom,
char* word_str) {
// Copy the output word and denormalize it back to image coords.
WERD copy_outword;
copy_outword = *(word->outword);
copy_outword.baseline_denormalise(&word->denorm);
PBLOB_IT blob_it;
blob_it.set_to_list(copy_outword.blob_list());
int length = copy_outword.blob_list()->length();
int output_size = 0;
if (length > 0) {
for (int index = 0, offset = 0; index < length;
offset += word->best_choice->unichar_lengths()[index++],
blob_it.forward()) {
PBLOB* blob = blob_it.data();
TBOX blob_box = blob->bounding_box();
if (word->tess_failed ||
blob_box.left() < 0 ||
blob_box.right() > page_image.get_xsize() ||
blob_box.bottom() < 0 ||
blob_box.top() > page_image.get_ysize()) {
// Bounding boxes can be illegal when tess fails on a word.
blob_box = word->word->bounding_box(); // Use original word as backup.
tprintf("Using substitute bounding box at (%d,%d)->(%d,%d)\n",
blob_box.left(), blob_box.bottom(),
blob_box.right(), blob_box.top());
}
// A single classification unit can be composed of several UTF-8
// characters. Append each of them to the result.
for (int sub = 0;
sub < word->best_choice->unichar_lengths()[index]; ++sub) {
char ch = word->best_choice->unichar_string()[offset + sub];
// Tesseract uses space for recognition failure. Fix to a reject
// character, kTesseractReject so we don't create illegal box files.
if (ch == ' ')
ch = kTesseractReject;
word_str[output_size++] = ch;
}
sprintf(word_str + output_size, " %d %d %d %d\n",
blob_box.left() + left, blob_box.bottom() + bottom,
blob_box.right() + left, blob_box.top() + bottom);
output_size += strlen(word_str + output_size);
}
}
return output_size;
}
// Multiplier for max expected textlength assumes typically 4 numbers @
// (5 digits and a space) plus the newline = 4*(5+1)+1. Add to this the
// orginal UTF8 characters, and one kMaxCharsPerChar.
const int kCharsPerChar = 25;
// A maximal single box could occupy 4 numbers at 20 digits (for 64 bit) and a
// space plus the newline 4*(20+1)+1 and the maximum length of a UNICHAR.
// Test against this on each iteration for safety.
const int kMaxCharsPerChar = 85 + UNICHAR_LEN;
// The recognized text is returned as a char* which is coded
// as a UTF8 box file and must be freed with the delete [] operator.
char* TessBaseAPI::GetBoxText() {
int bottom = image_height_ - (rect_top_ + rect_height_);
if (tesseract_ == NULL ||
(page_res_ == NULL && Recognize(NULL) < 0))
return NULL;
int blob_count;
int utf8_length = TextLength(&blob_count);
int total_length = blob_count*kCharsPerChar + utf8_length + kMaxCharsPerChar;
PAGE_RES_IT page_res_it(page_res_);
char* result = new char[total_length];
char* ptr = result;
for (page_res_it.restart_page(); page_res_it.word () != NULL;
page_res_it.forward()) {
WERD_RES *word = page_res_it.word();
ptr += ConvertWordToBoxText(word, page_res_it.row(), rect_left_, bottom,
ptr);
// Just in case...
if (ptr - result + kMaxCharsPerChar > total_length)
break;
}
*ptr = '\0';
return result;
}
// Conversion table for non-latin characters.
// Maps characters out of the latin set into the latin set.
// TODO(rays) incorporate this translation into unicharset.
const int kUniChs[] = {
0x20ac, 0x201c, 0x201d, 0x2018, 0x2019, 0x2022, 0x2014, 0
};
// Latin chars corresponding to the unicode chars above.
const int kLatinChs[] = {
0x00a2, 0x0022, 0x0022, 0x0027, 0x0027, 0x00b7, 0x002d, 0
};
// The recognized text is returned as a char* which is coded
// as UNLV format Latin-1 with specific reject and suspect codes
// and must be freed with the delete [] operator.
char* TessBaseAPI::GetUNLVText() {
if (tesseract_ == NULL ||
(page_res_ == NULL && Recognize(NULL) < 0))
return NULL;
bool tilde_crunch_written = false;
bool last_char_was_newline = true;
bool last_char_was_tilde = false;
int total_length = TextLength(NULL);
PAGE_RES_IT page_res_it(page_res_);
char* result = new char[total_length];
char* ptr = result;
for (page_res_it.restart_page(); page_res_it.word () != NULL;
page_res_it.forward()) {
WERD_RES *word = page_res_it.word();
// Process the current word.
if (word->unlv_crunch_mode != CR_NONE) {
if (word->unlv_crunch_mode != CR_DELETE &&
(!tilde_crunch_written ||
(word->unlv_crunch_mode == CR_KEEP_SPACE &&
word->word->space() > 0 &&
!word->word->flag(W_FUZZY_NON) &&
!word->word->flag(W_FUZZY_SP)))) {
if (!word->word->flag(W_BOL) &&
word->word->space() > 0 &&
!word->word->flag(W_FUZZY_NON) &&
!word->word->flag(W_FUZZY_SP)) {
/* Write a space to separate from preceeding good text */
*ptr++ = ' ';
last_char_was_tilde = false;
}
if (!last_char_was_tilde) {
// Write a reject char.
last_char_was_tilde = true;
*ptr++ = kUNLVReject;
tilde_crunch_written = true;
last_char_was_newline = false;
}
}
} else {
// NORMAL PROCESSING of non tilde crunched words.
tilde_crunch_written = false;
if (word->word->flag(W_REP_CHAR) && tessedit_consistent_reps)
ensure_rep_chars_are_consistent(word);
tesseract_->set_unlv_suspects(word);
const char* wordstr = word->best_choice->unichar_string().string();
const STRING& lengths = word->best_choice->unichar_lengths();
int length = lengths.length();
int i = 0;
int offset = 0;
if (last_char_was_tilde &&
word->word->space() == 0 && wordstr[offset] == ' ') {
// Prevent adjacent tilde across words - we know that adjacent tildes
// within words have been removed.
// Skip the first character.
offset = lengths[i++];
}
if (i < length && wordstr[offset] != 0) {
if (!last_char_was_newline)
*ptr++ = ' ';
else
last_char_was_newline = false;
for (; i < length; offset += lengths[i++]) {
if (wordstr[offset] == ' ' ||
wordstr[offset] == kTesseractReject) {
*ptr++ = kUNLVReject;
last_char_was_tilde = true;
} else {
if (word->reject_map[i].rejected())
*ptr++ = kUNLVSuspect;
UNICHAR ch(wordstr + offset, lengths[i]);
int uni_ch = ch.first_uni();
for (int j = 0; kUniChs[j] != 0; ++j) {
if (kUniChs[j] == uni_ch) {
uni_ch = kLatinChs[j];
break;
}
}
if (uni_ch <= 0xff) {
*ptr++ = static_cast<char>(uni_ch);
last_char_was_tilde = false;
} else {
*ptr++ = kUNLVReject;
last_char_was_tilde = true;
}
}
}
}
}
if (word->word->flag(W_EOL) && !last_char_was_newline) {
/* Add a new line output */
*ptr++ = '\n';
tilde_crunch_written = false;
last_char_was_newline = true;
last_char_was_tilde = false;
}
}
*ptr++ = '\n';
*ptr = '\0';
return result;
}
// Returns the average word confidence for Tesseract page result.
int TessBaseAPI::MeanTextConf() {
int* conf = AllWordConfidences();
if (!conf) return 0;
int sum = 0;
int *pt = conf;
while (*pt >= 0) sum += *pt++;
if (pt != conf) sum /= pt - conf;
delete [] conf;
return sum;
}
// Returns an array of all word confidences, terminated by -1.
int* TessBaseAPI::AllWordConfidences() {
if (tesseract_ == NULL ||
(page_res_ == NULL && Recognize(NULL) < 0))
return NULL;
int n_word = 0;
PAGE_RES_IT res_it(page_res_);
for (res_it.restart_page(); res_it.word() != NULL; res_it.forward())
n_word++;
int* conf = new int[n_word+1];
n_word = 0;
for (res_it.restart_page(); res_it.word() != NULL; res_it.forward()) {
WERD_RES *word = res_it.word();
WERD_CHOICE* choice = word->best_choice;
int w_conf = static_cast<int>(100 + 5 * choice->certainty());
// This is the eq for converting Tesseract confidence to 1..100
if (w_conf < 0) w_conf = 0;
if (w_conf > 100) w_conf = 100;
conf[n_word++] = w_conf;
}
conf[n_word] = -1;
return conf;
}
// Free up recognition results and any stored image data, without actually
// freeing any recognition data that would be time-consuming to reload.
// Afterwards, you must call SetImage or TesseractRect before doing
// any Recognize or Get* operation.
void TessBaseAPI::Clear() {
if (thresholder_ != NULL)
thresholder_->Clear();
ClearResults();
page_image.destroy();
}
// Close down tesseract and free up all memory. End() is equivalent to
// destructing and reconstructing your TessBaseAPI.
// Once End() has been used, none of the other API functions may be used
// other than Init and anything declared above it in the class definition.
void TessBaseAPI::End() {
if (thresholder_ != NULL) {
delete thresholder_;
thresholder_ = NULL;
}
if (page_res_ != NULL) {
delete page_res_;
page_res_ = NULL;
}
if (block_list_ != NULL) {
delete block_list_;
block_list_ = NULL;
}
if (tesseract_ != NULL) {
tesseract_->end_tesseract();
delete tesseract_;
tesseract_ = NULL;
}
if (input_file_ != NULL) {
delete input_file_;
input_file_ = NULL;
}
if (output_file_ != NULL) {
delete output_file_;
output_file_ = NULL;
}
if (datapath_ != NULL) {
delete datapath_;
datapath_ = NULL;
}
if (language_ != NULL) {
delete language_;
language_ = NULL;
}
}
// Check whether a word is valid according to Tesseract's language model
// returns 0 if the word is invalid, non-zero if valid
int TessBaseAPI::IsValidWord(const char *word) {
return tesseract_->getDict().valid_word(word);
}
bool TessBaseAPI::GetTextDirection(int* out_offset, float* out_slope) {
if (page_res_ == NULL)
FindLines();
if (block_list_->length() < 1) {
return false;
}
// Get first block
BLOCK_IT block_it(block_list_);
block_it.move_to_first();
ROW_LIST* rows = block_it.data()->row_list();
if (rows->length() != 1) {
return false;
}
// Get first line of block
ROW_IT row_it(rows);
row_it.move_to_first();
ROW* row = row_it.data();
// Calculate offset and slope (NOTE: Kind of ugly)
*out_offset = static_cast<int>(row->base_line(0.0));
*out_slope = row->base_line(1.0) - row->base_line(0.0);
return true;
}
// Set the letter_is_okay function to point somewhere else.
void TessBaseAPI::SetDictFunc(DictFunc f) {
if (tesseract_ != NULL) {
tesseract_->getDict().letter_is_okay_ = f;
}
}
// Common code for setting the image.
bool TessBaseAPI::InternalSetImage() {
if (tesseract_ == NULL) {
tprintf("Please call Init before attempting to send an image.");
return false;
}
if (thresholder_ == NULL)
thresholder_ = new ImageThresholder;
ClearResults();
return true;
}
// Run the thresholder to make the thresholded image. If pix is not NULL,
// the source is thresholded to pix instead of the internal IMAGE.
void TessBaseAPI::Threshold(Pix** pix) {
#ifdef HAVE_LIBLEPT
if (pix != NULL)
thresholder_->ThresholdToPix(pix);
else
thresholder_->ThresholdToIMAGE(&page_image);
#else
thresholder_->ThresholdToIMAGE(&page_image);
#endif
thresholder_->GetImageSizes(&rect_left_, &rect_top_,
&rect_width_, &rect_height_,
&image_width_, &image_height_);
threshold_done_ = true;
}
// Find lines from the image making the BLOCK_LIST.
int TessBaseAPI::FindLines() {
if (!block_list_->empty()) {
return 0;
}
if (tesseract_ == NULL) {
tesseract_ = new Tesseract;
tesseract_->InitAdaptiveClassifier();
}
#ifdef HAVE_LIBLEPT
if (tesseract_->pix_binary() == NULL)
Threshold(tesseract_->mutable_pix_binary());
#endif
if (!threshold_done_)
Threshold(NULL);
if (tesseract_->SegmentPage(input_file_, &page_image, block_list_) < 0)
return -1;
ASSERT_HOST(page_image.get_xsize() == rect_width_ ||
page_image.get_xsize() == rect_width_ - 1);
ASSERT_HOST(page_image.get_ysize() == rect_height_ ||
page_image.get_ysize() == rect_height_ - 1);
return 0;
}
// Delete the pageres and clear the block list ready for a new page.
void TessBaseAPI::ClearResults() {
threshold_done_ = false;
if (tesseract_ != NULL)
tesseract_->Clear();
if (page_res_ != NULL) {
delete page_res_;
page_res_ = NULL;
}
if (block_list_ == NULL)
block_list_ = new BLOCK_LIST;
else
block_list_->clear();
}
// Return the length of the output text string, as UTF8, assuming
// one newline per line and one per block, with a terminator,
// and assuming a single character reject marker for each rejected character.
// Also return the number of recognized blobs in blob_count.
int TessBaseAPI::TextLength(int* blob_count) {
if (tesseract_ == NULL || page_res_ == NULL)
return 0;
PAGE_RES_IT page_res_it(page_res_);
int total_length = 2;
int total_blobs = 0;
// Iterate over the data structures to extract the recognition result.
for (page_res_it.restart_page(); page_res_it.word () != NULL;
page_res_it.forward()) {
WERD_RES *word = page_res_it.word();
WERD_CHOICE* choice = word->best_choice;
if (choice != NULL) {
total_blobs += choice->length() + 1;
total_length += choice->unichar_string().length() + 1;
for (int i = 0; i < word->reject_map.length(); ++i) {
if (word->reject_map[i].rejected())
++total_length;
}
}
}
if (blob_count != NULL)
*blob_count = total_blobs;
return total_length;
}
// Estimates the Orientation And Script of the image.
// Returns true if the image was processed successfully.
bool TessBaseAPI::DetectOS(OSResults* osr) {
if (tesseract_ == NULL)
return false;
ClearResults();
Threshold(NULL);
if (input_file_ == NULL)
input_file_ = new STRING(kInputFile);
return orientation_and_script_detection(*input_file_, osr, tesseract_);
}
// ____________________________________________________________________________
// Ocropus add-ons.
// Find lines from the image making the BLOCK_LIST.
BLOCK_LIST* TessBaseAPI::FindLinesCreateBlockList() {
FindLines();
BLOCK_LIST* result = block_list_;
block_list_ = NULL;
return result;
}
// Delete a block list.
// This is to keep BLOCK_LIST pointer opaque
// and let go of including the other headers.
void TessBaseAPI::DeleteBlockList(BLOCK_LIST *block_list) {
delete block_list;
}
static ROW *make_tess_ocrrow(float baseline,
float xheight,
float descender,
float ascender) {
inT32 xstarts[] = {-32000};
double quad_coeffs[] = {0, 0, baseline};
return new ROW(1,
xstarts,
quad_coeffs,
xheight,
ascender - (baseline + xheight),
descender - baseline,
0,
0);
}
// Almost a copy of make_tess_row() from ccmain/tstruct.cpp.
static void fill_dummy_row(float baseline, float xheight,
float descender, float ascender,
TEXTROW* tessrow) {
tessrow->baseline.segments = 1;
tessrow->baseline.xstarts[0] = -32767;
tessrow->baseline.xstarts[1] = 32767;
tessrow->baseline.quads[0].a = 0;
tessrow->baseline.quads[0].b = 0;
tessrow->baseline.quads[0].c = bln_baseline_offset;
tessrow->xheight.segments = 1;
tessrow->xheight.xstarts[0] = -32767;
tessrow->xheight.xstarts[1] = 32767;
tessrow->xheight.quads[0].a = 0;
tessrow->xheight.quads[0].b = 0;
tessrow->xheight.quads[0].c = bln_baseline_offset + bln_x_height;
tessrow->lineheight = bln_x_height;
tessrow->ascrise = bln_x_height * (ascender - (xheight + baseline)) / xheight;
tessrow->descdrop = bln_x_height * (descender - baseline) / xheight;
}
// Return a TBLOB * from the whole page_image.
// To be freed later with free_blob().
TBLOB *make_tesseract_blob(float baseline, float xheight,
float descender, float ascender) {
BLOCK *block = new BLOCK("a character",
TRUE,
0, 0,
0, 0,
page_image.get_xsize(),
page_image.get_ysize());
// Create C_BLOBs from the page
extract_edges(
#ifndef GRAPHICS_DISABLED
NULL,
#endif
&page_image, &page_image,
ICOORD(page_image.get_xsize(), page_image.get_ysize()),
block);
// Create one PBLOB from all C_BLOBs
C_BLOB_LIST *list = block->blob_list();
C_BLOB_IT c_blob_it(list);
PBLOB *pblob = new PBLOB; // will be (hopefully) deleted by the pblob_list
for (c_blob_it.mark_cycle_pt();
!c_blob_it.cycled_list();
c_blob_it.forward()) {
C_BLOB *c_blob = c_blob_it.data();
PBLOB c_as_p(c_blob, baseline + xheight);
merge_blobs(pblob, &c_as_p);
}
PBLOB_LIST *pblob_list = new PBLOB_LIST; // will be deleted by the word
PBLOB_IT pblob_it(pblob_list);
pblob_it.add_after_then_move(pblob);
// Normalize PBLOB
WERD word(pblob_list, 0, " ");
ROW *row = make_tess_ocrrow(baseline, xheight, descender, ascender);
word.baseline_normalise(row);
delete row;
// Create a TBLOB from PBLOB
return make_tess_blob(pblob, /* flatten: */ TRUE);
}
// Adapt to recognize the current image as the given character.
// The image must be preloaded and be just an image of a single character.
void TessBaseAPI::AdaptToCharacter(const char *unichar_repr,
int length,
float baseline,
float xheight,
float descender,
float ascender) {
UNICHAR_ID id = tesseract_->unicharset.unichar_to_id(unichar_repr, length);
LINE_STATS LineStats;
TEXTROW row;
fill_dummy_row(baseline, xheight, descender, ascender, &row);
GetLineStatsFromRow(&row, &LineStats);
TBLOB *blob = make_tesseract_blob(baseline, xheight, descender, ascender);
float threshold;
UNICHAR_ID best_class = 0;
float best_rating = -100;
// Classify to get a raw choice.
BLOB_CHOICE_LIST choices;
tesseract_->AdaptiveClassifier(blob, NULL, &row, &choices, NULL);
BLOB_CHOICE_IT choice_it;
choice_it.set_to_list(&choices);
for (choice_it.mark_cycle_pt(); !choice_it.cycled_list();
choice_it.forward()) {
if (choice_it.data()->rating() > best_rating) {
best_rating = choice_it.data()->rating();
best_class = choice_it.data()->unichar_id();
}
}
if (id == best_class) {
threshold = matcher_good_threshold;
} else {
/* the blob was incorrectly classified - find the rating threshold
needed to create a template which will correct the error with
some margin. However, don't waste time trying to make
templates which are too tight. */
threshold = tesseract_->GetBestRatingFor(blob, &LineStats, id);
threshold *= .9;
const float max_threshold = .125;
const float min_threshold = .02;
if (threshold > max_threshold)
threshold = max_threshold;
// I have cuddled the following line to set it out of the strike
// of the coverage testing tool. I have no idea how to trigger
// this situation nor I have any necessity to do it. --mezhirov
if (threshold < min_threshold) threshold = min_threshold;
}
if (blob->outlines)
tesseract_->AdaptToChar(blob, &LineStats, id, threshold);
free_blob(blob);
}
PAGE_RES* TessBaseAPI::RecognitionPass1(BLOCK_LIST* block_list) {
PAGE_RES *page_res = new PAGE_RES(block_list);
tesseract_->recog_all_words(page_res, NULL, NULL, 1);
return page_res;
}
PAGE_RES* TessBaseAPI::RecognitionPass2(BLOCK_LIST* block_list,
PAGE_RES* pass1_result) {
if (!pass1_result)
pass1_result = new PAGE_RES(block_list);
tesseract_->recog_all_words(pass1_result, NULL, NULL, 2);
return pass1_result;
}
struct TESS_CHAR : ELIST_LINK {
char *unicode_repr;
int length; // of unicode_repr
float cost;
TBOX box;
TESS_CHAR(float _cost, const char *repr, int len = -1) : cost(_cost) {
length = (len == -1 ? strlen(repr) : len);
unicode_repr = new char[length + 1];
strncpy(unicode_repr, repr, length);
}
TESS_CHAR() { // Satisfies ELISTIZE.
}
~TESS_CHAR() {
delete [] unicode_repr;
}
};
ELISTIZEH(TESS_CHAR)
ELISTIZE(TESS_CHAR)
static void add_space(TESS_CHAR_IT* it) {
TESS_CHAR *t = new TESS_CHAR(0, " ");
it->add_after_then_move(t);
}
static float rating_to_cost(float rating) {
rating = 100 + 5*rating;
// cuddled that to save from coverage profiler
// (I have never seen ratings worse than -100,
// but the check won't hurt)
if (rating < 0) rating = 0;
return rating;
}
// Extract the OCR results, costs (penalty points for uncertainty),
// and the bounding boxes of the characters.
static void extract_result(TESS_CHAR_IT* out,
PAGE_RES* page_res) {
PAGE_RES_IT page_res_it(page_res);
int word_count = 0;
while (page_res_it.word() != NULL) {
WERD_RES *word = page_res_it.word();
const char *str = word->best_choice->unichar_string().string();
const char *len = word->best_choice->unichar_lengths().string();
if (word_count)
add_space(out);
TBOX bln_rect;
PBLOB_LIST *blobs = word->outword->blob_list();
PBLOB_IT it(blobs);
int n = strlen(len);
TBOX** boxes_to_fix = new TBOX*[n];
for (int i = 0; i < n; i++) {
PBLOB *blob = it.data();
TBOX current = blob->bounding_box();
bln_rect = bln_rect.bounding_union(current);
TESS_CHAR *tc = new TESS_CHAR(rating_to_cost(word->best_choice->certainty()),
str, *len);
tc->box = current;
boxes_to_fix[i] = &tc->box;
out->add_after_then_move(tc);
it.forward();
str += *len;
len++;
}
// Find the word bbox before normalization.
// Here we can't use the C_BLOB bboxes directly,
// since connected letters are not yet cut.
TBOX real_rect = word->word->bounding_box();
// Denormalize boxes by transforming the bbox of the whole bln word
// into the denorm bbox (`real_rect') of the whole word.
double x_stretch = static_cast<double>(real_rect.width())
/ bln_rect.width();
double y_stretch = static_cast<double>(real_rect.height())
/ bln_rect.height();
for (int j = 0; j < n; j++) {
TBOX *box = boxes_to_fix[j];
int x0 = static_cast<int>(real_rect.left() +
x_stretch * (box->left() - bln_rect.left()) + 0.5);
int x1 = static_cast<int>(real_rect.left() +
x_stretch * (box->right() - bln_rect.left()) + 0.5);
int y0 = static_cast<int>(real_rect.bottom() +
y_stretch * (box->bottom() - bln_rect.bottom()) + 0.5);
int y1 = static_cast<int>(real_rect.bottom() +
y_stretch * (box->top() - bln_rect.bottom()) + 0.5);
*box = TBOX(ICOORD(x0, y0), ICOORD(x1, y1));
}
delete [] boxes_to_fix;
page_res_it.forward();
word_count++;
}
}
// Extract the OCR results, costs (penalty points for uncertainty),
// and the bounding boxes of the characters.
int TessBaseAPI::TesseractExtractResult(char** text,
int** lengths,
float** costs,
int** x0,
int** y0,
int** x1,
int** y1,
PAGE_RES* page_res) {
TESS_CHAR_LIST tess_chars;
TESS_CHAR_IT tess_chars_it(&tess_chars);
extract_result(&tess_chars_it, page_res);
tess_chars_it.move_to_first();
int n = tess_chars.length();
int text_len = 0;
*lengths = new int[n];
*costs = new float[n];
*x0 = new int[n];
*y0 = new int[n];
*x1 = new int[n];
*y1 = new int[n];
int i = 0;
for (tess_chars_it.mark_cycle_pt();
!tess_chars_it.cycled_list();
tess_chars_it.forward(), i++) {
TESS_CHAR *tc = tess_chars_it.data();
text_len += (*lengths)[i] = tc->length;
(*costs)[i] = tc->cost;
(*x0)[i] = tc->box.left();
(*y0)[i] = tc->box.bottom();
(*x1)[i] = tc->box.right();
(*y1)[i] = tc->box.top();
}
char *p = *text = new char[text_len];
tess_chars_it.move_to_first();
for (tess_chars_it.mark_cycle_pt();
!tess_chars_it.cycled_list();
tess_chars_it.forward()) {
TESS_CHAR *tc = tess_chars_it.data();
strncpy(p, tc->unicode_repr, tc->length);
p += tc->length;
}
return n;
}
// This method returns the features associated with the current image.
// Make sure setimage has been called before calling this method.
void TessBaseAPI::GetFeatures(INT_FEATURE_ARRAY int_features,
int* num_features) {
if (page_res_ != NULL)
ClearResults();
if (!threshold_done_)
Threshold(NULL);
// We have only one block, which is of the size of the page.
BLOCK_LIST* blocks = new BLOCK_LIST;
BLOCK *block = new BLOCK("", // filename.
TRUE, // proportional.
0, // kerning.
0, // spacing.
0, // Left.
0, // Bottom.
page_image.get_xsize(), // Right.
page_image.get_ysize()); // Top.
ICOORD bleft, tright;
block->bounding_box (bleft, tright);
BLOCK_IT block_it_add = blocks;
block_it_add.add_to_end(block);
ICOORD page_tr(page_image.get_xsize(), page_image.get_ysize());
TEXTROW tessrow;
make_tess_row(NULL, // Denormalizer.
&tessrow); // Output row.
LINE_STATS line_stats;
GetLineStatsFromRow(&tessrow, &line_stats);
// Perform a CC analysis to detect the blobs.
BLOCK_IT block_it = blocks;
for (block_it.mark_cycle_pt (); !block_it.cycled_list ();
block_it.forward ()) {
BLOCK* block = block_it.data();
#ifndef GRAPHICS_DISABLED
extract_edges(NULL, // Scrollview window.
&page_image, // Image.
&page_image, // Thresholded image.
page_tr, // corner of page.
block); // block.
#else
extract_edges(&page_image, // Image.
&page_image, // Thresholded image.
page_tr, // corner of page.
block); // block.
#endif
C_BLOB_IT blob_it = block->blob_list();
PBLOB *pblob = new PBLOB;
// Iterate over all blobs found and get their features.
for (blob_it.mark_cycle_pt(); !blob_it.cycled_list();
blob_it.forward()) {
C_BLOB* blob = blob_it.data();
blob = blob;
PBLOB c_as_p(blob, page_image.get_ysize());
merge_blobs(pblob, &c_as_p);
}
PBLOB_LIST *pblob_list = new PBLOB_LIST;
PBLOB_IT pblob_it(pblob_list);
pblob_it.add_after_then_move(pblob);
WERD word(pblob_list, // Blob list.
0, // Blanks in front.
" "); // Correct text.
ROW *row = make_tess_ocrrow(0, // baseline.
page_image.get_ysize(), // xheight.
0, // ascent.
0); // descent.
word.baseline_normalise(row);
delete row;
if (pblob->out_list () == NULL) {
tprintf("Blob list is empty");
}
TBLOB* tblob = make_tess_blob(pblob, // Blob.
TRUE); // Flatten.
CLASS_NORMALIZATION_ARRAY norm_array;
inT32 len;
*num_features = tesseract_->GetCharNormFeatures(
tblob, &line_stats,
tesseract_->PreTrainedTemplates,
int_features, norm_array, &len);
}
delete blocks;
}
// Return the pointer to the i-th dawg loaded into tesseract_ object.
const Dawg *TessBaseAPI::GetDawg(int i) const {
if (tesseract_ == NULL || i >= NumDawgs()) return NULL;
return tesseract_->getDict().GetDawg(i);
}
// Return the number of dawgs loaded into tesseract_ object.
int TessBaseAPI::NumDawgs() const {
return tesseract_ == NULL ? 0 : tesseract_->getDict().NumDawgs();
}
// Return the language used in the last valid initialization.
const char* TessBaseAPI::GetLastInitLanguage() const {
return (tesseract_ == NULL || tesseract_->lang.string() == NULL) ?
"" : tesseract_->lang.string();
}
} // namespace tesseract.