tesseract/wordrec/bestfirst.cpp
theraysmith fea38ee706 Misc root changes for 3.00
git-svn-id: https://tesseract-ocr.googlecode.com/svn/trunk@309 d0cd1f9f-072b-0410-8dd7-cf729c803f20
2009-07-11 03:05:57 +00:00

775 lines
27 KiB
C++

/* -*-C-*-
********************************************************************************
*
* File: bestfirst.c (Formerly bestfirst.c)
* Description: Best first search functions
* Author: Mark Seaman, OCR Technology
* Created: Mon May 14 11:23:29 1990
* Modified: Tue Jul 30 16:08:47 1991 (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.
*
***************************************************************************/
/*----------------------------------------------------------------------
I n c l u d e s
---------------------------------------------------------------------*/
#include <assert.h>
#include "bestfirst.h"
#include "baseline.h"
#include "bitvec.h"
#include "callback.h"
#include "dict.h"
#include "freelist.h"
#include "globals.h"
#include "heuristic.h"
#include "metrics.h"
#include "permute.h"
#include "pieces.h"
#include "plotseg.h"
#include "ratngs.h"
#include "states.h"
#include "stopper.h"
#include "structures.h"
#include "tordvars.h"
#include "unicharset.h"
#include "wordclass.h"
#include "wordrec.h"
void call_caller();
/*----------------------------------------------------------------------
V a r i a b l e s
---------------------------------------------------------------------*/
int num_joints; /* Number of chunks - 1 */
int num_pushed = 0;
int num_popped = 0;
INT_VAR(wordrec_num_seg_states, 30, "Segmentation states");
double_VAR(wordrec_worst_state, 1, "Worst segmentation state");
/**/
/*----------------------------------------------------------------------
F u n c t i o n s
----------------------------------------------------------------------*/
/**********************************************************************
* best_first_search
*
* Find the best segmentation by doing a best first search of the
* solution space.
**********************************************************************/
namespace tesseract {
void Wordrec::best_first_search(CHUNKS_RECORD *chunks_record,
WERD_CHOICE *best_choice,
WERD_CHOICE *raw_choice,
STATE *state,
DANGERR *fixpt,
STATE *best_state) {
SEARCH_RECORD *the_search;
inT16 keep_going;
STATE guided_state; // not used
num_joints = chunks_record->ratings->dimension() - 1;
the_search = new_search(chunks_record, num_joints,
best_choice, raw_choice, state);
// The default state is initialized as the best choice. In order to apply
// segmentation adjustment, or any other contextual processing in permute,
// we give the best choice a poor rating to force the processed raw choice
// to be promoted to best choice.
the_search->best_choice->set_rating(100000.0);
evaluate_state(chunks_record, the_search, fixpt);
if (permute_debug) {
tprintf("\n\n\n =========== BestFirstSearch ==============\n");
best_choice->print("**Initial BestChoice**");
}
#ifndef GRAPHICS_DISABLED
save_best_state(chunks_record);
#endif
start_recording();
FLOAT32 worst_priority = 2.0f * prioritize_state(chunks_record, the_search);
if (worst_priority < wordrec_worst_state)
worst_priority = wordrec_worst_state;
if (segment_debug) {
print_state("BestFirstSearch", best_state, num_joints);
}
guided_state = *state;
do {
/* Look for answer */
if (!hash_lookup (the_search->closed_states, the_search->this_state)) {
if (tord_blob_skip) {
free_state (the_search->this_state);
break;
}
guided_state = *(the_search->this_state);
keep_going = evaluate_state(chunks_record, the_search, fixpt);
hash_add (the_search->closed_states, the_search->this_state);
if (!keep_going ||
(the_search->num_states > wordrec_num_seg_states) ||
(tord_blob_skip)) {
if (segment_debug)
tprintf("Breaking best_first_search on keep_going %s numstates %d\n",
((keep_going) ? "T" :"F"), the_search->num_states);
free_state (the_search->this_state);
break;
}
FLOAT32 new_worst_priority = 2.0f * prioritize_state(chunks_record,
the_search);
if (new_worst_priority < worst_priority) {
if (segment_debug)
tprintf("Lowering WorstPriority %f --> %f\n",
worst_priority, new_worst_priority);
// Tighten the threshold for admitting new paths as better search
// candidates are found. After lowering this threshold, we can safely
// popout everything that is worse than this score also.
worst_priority = new_worst_priority;
}
expand_node(worst_priority, chunks_record, the_search);
}
free_state (the_search->this_state);
num_popped++;
the_search->this_state = pop_queue (the_search->open_states);
if (segment_debug && !the_search->this_state)
tprintf("No more states to evalaute after %d evals", num_popped);
}
while (the_search->this_state);
state->part1 = the_search->best_state->part1;
state->part2 = the_search->best_state->part2;
stop_recording();
if (permute_debug) {
tprintf("\n\n\n =========== BestFirstSearch ==============\n");
// best_choice->debug_string(getDict().getUnicharset()).string());
best_choice->print("**Final BestChoice**");
}
// save the best_state stats
delete_search(the_search);
}
} // namespace tesseract
/**********************************************************************
* chunks_width
*
* Return the width of a chunk which is a composed of several blobs
* blobs[start_blob..last_blob] inclusively,
* whose individual widths and gaps are record in width_record in the form
* width_record->num_char = n
* width_record->widths[2*n-1] = w0,g0,w1,g1..w(n-1),g(n-1)
**********************************************************************/
int chunks_width(WIDTH_RECORD *width_record, int start_blob, int last_blob) {
int result = 0;
for (int x = start_blob * 2; x <= last_blob * 2; x++)
result += width_record->widths[x];
return (result);
}
/**********************************************************************
* chunks_gap
*
* Return the width of between the specified chunk and next.
**********************************************************************/
int chunks_gap(WIDTH_RECORD *width_record, int last_chunk) {
return (last_chunk < width_record->num_chars - 1) ?
width_record->widths[last_chunk * 2 + 1] : 0;
}
/**********************************************************************
* delete_search
*
* Terminate the current search and free all the memory involved.
**********************************************************************/
void delete_search(SEARCH_RECORD *the_search) {
float closeness;
closeness = (the_search->num_joints ?
(hamming_distance(reinterpret_cast<uinT32*>(the_search->first_state),
reinterpret_cast<uinT32*>(the_search->best_state), 2) /
(float) the_search->num_joints) : 0.0f);
record_search_status (the_search->num_states,
the_search->before_best, closeness);
free_state (the_search->first_state);
free_state (the_search->best_state);
free_hash_table (the_search->closed_states);
FreeHeapData (the_search->open_states, (void_dest) free_state);
memfree(the_search);
}
/**********************************************************************
* evaluate_chunks
*
* A particular word level segmentation has been chosen. Evaluation
* this to find the word list that corresponds to it.
**********************************************************************/
namespace tesseract {
BLOB_CHOICE_LIST_VECTOR *Wordrec::evaluate_chunks(CHUNKS_RECORD *chunks_record,
SEARCH_STATE search_state) {
BLOB_CHOICE_LIST_VECTOR *char_choices = new BLOB_CHOICE_LIST_VECTOR();
BLOB_CHOICE_LIST *blob_choices;
BLOB_CHOICE_IT blob_choice_it;
int i;
int x = 0;
int y;
/* Iterate sub-paths */
for (i = 1; i <= search_state[0] + 1; i++) {
if (i > search_state[0])
y = count_blobs (chunks_record->chunks) - 1;
else
y = x + search_state[i];
if (tord_blob_skip) {
delete char_choices;
return (NULL);
} /* Process one square */
/* Classify if needed */
blob_choices = get_piece_rating(chunks_record->ratings,
chunks_record->chunks,
chunks_record->splits,
x, y);
if (blob_choices == NULL) {
delete char_choices;
return (NULL);
}
/* Add permuted ratings */
blob_choice_it.set_to_list(blob_choices);
last_segmentation[i - 1].certainty = blob_choice_it.data()->certainty();
last_segmentation[i - 1].match = blob_choice_it.data()->rating();
last_segmentation[i - 1].width =
chunks_width (chunks_record->chunk_widths, x, y);
last_segmentation[i - 1].gap =
chunks_gap (chunks_record->chunk_widths, y);
*char_choices += blob_choices;
x = y + 1;
}
return (char_choices);
}
/**********************************************************************
* evaluate_state
*
* Evaluate the segmentation that is represented by this state in the
* best first search. Add this state to the "states_seen" list.
**********************************************************************/
inT16 Wordrec::evaluate_state(CHUNKS_RECORD *chunks_record,
SEARCH_RECORD *the_search,
DANGERR *fixpt) {
BLOB_CHOICE_LIST_VECTOR *char_choices;
SEARCH_STATE chunk_groups;
float rating_limit = the_search->best_choice->rating();
inT16 keep_going = TRUE;
PIECES_STATE widths;
the_search->num_states++;
chunk_groups = bin_to_chunks(the_search->this_state,
the_search->num_joints);
bin_to_pieces (the_search->this_state, the_search->num_joints, widths);
getDict().LogNewSegmentation(widths);
char_choices = evaluate_chunks(chunks_record, chunk_groups);
wordseg_rating_adjust_factor = -1.0f;
if (char_choices != NULL && char_choices->length() > 0) {
// Compute the segmentation cost and include the cost in word rating.
// TODO(dsl): We should change the SEARCH_RECORD to store this cost
// from state evaluation and avoid recomputing it here.
prioritize_state(chunks_record, the_search);
wordseg_rating_adjust_factor = the_search->segcost_bias;
getDict().permute_characters(*char_choices, rating_limit,
the_search->best_choice,
the_search->raw_choice);
bool replaced = false;
if (getDict().AcceptableChoice(char_choices, the_search->best_choice,
*(the_search->raw_choice), fixpt,
ASSOCIATOR_CALLER, &replaced)) {
keep_going = FALSE;
}
}
wordseg_rating_adjust_factor = -1.0f;
#ifndef GRAPHICS_DISABLED
if (wordrec_display_segmentations) {
display_segmentation (chunks_record->chunks, chunk_groups);
if (wordrec_display_segmentations > 1)
window_wait(segm_window);
}
#endif
if (rating_limit != the_search->best_choice->rating()) {
the_search->before_best = the_search->num_states;
the_search->best_state->part1 = the_search->this_state->part1;
the_search->best_state->part2 = the_search->this_state->part2;
replace_char_widths(chunks_record, chunk_groups);
}
else if (char_choices != NULL)
fixpt->index = -1;
if (char_choices != NULL) delete char_choices;
memfree(chunk_groups);
return (keep_going);
}
/**********************************************************************
* rebuild_current_state
*
* Evaluate the segmentation that is represented by this state in the
* best first search. Add this state to the "states_seen" list.
**********************************************************************/
BLOB_CHOICE_LIST_VECTOR *Wordrec::rebuild_current_state(
TBLOB *blobs,
SEAMS seam_list,
STATE *state,
BLOB_CHOICE_LIST_VECTOR *old_choices,
int fx,
bool force_rebuild,
const WERD_CHOICE &best_choice,
const MATRIX *ratings) {
// Initialize search_state, num_joints, x, y.
int num_joints = array_count(seam_list);
#ifndef GRAPHICS_DISABLED
if (wordrec_display_segmentations) {
print_state("Rebuiling state", state, num_joints);
}
#endif
SEARCH_STATE search_state = bin_to_chunks(state, num_joints);
int x = 0;
int y;
int i;
for (i = 1; i <= search_state[0]; i++) {
y = x + search_state[i];
x = y + 1;
}
y = count_blobs (blobs) - 1;
// Initialize char_choices, expanded_fragment_lengths:
// e.g. if fragment_lengths = {1 1 2 3 1},
// expanded_fragment_lengths_str = {1 1 2 2 3 3 3 1}.
BLOB_CHOICE_LIST_VECTOR *char_choices = new BLOB_CHOICE_LIST_VECTOR();
STRING expanded_fragment_lengths_str = "";
bool state_has_fragments = false;
const char *fragment_lengths = NULL;
if (best_choice.length() > 0) {
fragment_lengths = best_choice.fragment_lengths();
}
if (fragment_lengths) {
for (int i = 0; i < best_choice.length(); ++i) {
*char_choices += NULL;
if (fragment_lengths[i] > 1) {
state_has_fragments = true;
}
for (int j = 0; j < fragment_lengths[i]; ++j) {
expanded_fragment_lengths_str += fragment_lengths[i];
}
}
} else {
for (i = 0; i <= search_state[0]; ++i) {
expanded_fragment_lengths_str += (char)1;
*char_choices += NULL;
}
}
// Finish early if force_rebuld is false and there are no fragments to merge.
if (!force_rebuild && !state_has_fragments) {
delete char_choices;
memfree(search_state);
return old_choices;
}
// Set up variables for concatenating fragments.
const char *word_lengths_ptr = NULL;
const char *word_ptr = NULL;
if (state_has_fragments) {
// Make word_lengths_ptr point to the last element in
// best_choice->unichar_lengths().
word_lengths_ptr = best_choice.unichar_lengths().string();
word_lengths_ptr += (strlen(word_lengths_ptr)-1);
// Make word_str point to the beginning of the last
// unichar in best_choice->unichar_string().
word_ptr = best_choice.unichar_string().string();
word_ptr += (strlen(word_ptr)-*word_lengths_ptr);
}
const char *expanded_fragment_lengths =
expanded_fragment_lengths_str.string();
bool merging_fragment = false;
int true_y = -1;
char unichar[UNICHAR_LEN + 1];
int fragment_pieces = -1;
float rating = 0.0;
float certainty = -MAX_FLOAT32;
// Populate char_choices list such that it corresponds to search_state.
//
// If we are rebuilding a state that contains character fragments:
// -- combine blobs that belong to character fragments
// -- re-classify the blobs to obtain choices list for the merged blob
// -- ensure that correct classification appears in the new choices list
// NOTE: a choice composed form original fragment choices will be always
// added to the new choices list for each character composed from
// fragments (even if the choice for the corresponding character appears
// in the re-classified choices list of for the newly merged blob).
BLOB_CHOICE_IT temp_it;
int char_choices_index = char_choices->length() - 1;
for (i = search_state[0]; i >= 0; i--) {
BLOB_CHOICE_LIST *current_choices = join_blobs_and_classify(
blobs, seam_list, x, y, fx, ratings, old_choices);
// Combine character fragments.
if (expanded_fragment_lengths[i] > 1) {
// Start merging character fragments.
if (!merging_fragment) {
merging_fragment = true;
true_y = y;
fragment_pieces = expanded_fragment_lengths[i];
rating = 0.0;
certainty = -MAX_FLOAT32;
strncpy(unichar, word_ptr, *word_lengths_ptr);
unichar[*word_lengths_ptr] = '\0';
}
// Take into account the fact that we could have joined pieces
// since we first recorded the ending point of a fragment (true_y).
true_y -= y - x;
// Populate fragment with updated values and look for the
// fragment with the same values in current_choices.
// Update rating and certainty of the character being composed.
fragment_pieces--;
CHAR_FRAGMENT fragment;
fragment.set_all(unichar, fragment_pieces,
expanded_fragment_lengths[i]);
temp_it.set_to_list(current_choices);
for (temp_it.mark_cycle_pt(); !temp_it.cycled_list();
temp_it.forward()) {
const CHAR_FRAGMENT *current_fragment =
getDict().getUnicharset().get_fragment(temp_it.data()->unichar_id());
if (current_fragment && fragment.equals(current_fragment)) {
rating += temp_it.data()->rating();
if (temp_it.data()->certainty() > certainty) {
certainty = temp_it.data()->certainty();
}
break;
}
}
assert(!temp_it.cycled_list()); // make sure we found the fragment
// Free current_choices for the fragmented character.
delete current_choices;
// Finish composing character from fragments.
if (fragment_pieces == 0) {
// Populate current_choices with the classification of
// the blob merged from blobs of each character fragment.
current_choices = join_blobs_and_classify(blobs, seam_list, x,
true_y, fx, ratings, NULL);
BLOB_CHOICE *merged_choice =
new BLOB_CHOICE(getDict().getUnicharset().unichar_to_id(unichar),
rating, certainty, 0, NO_PERM);
// Insert merged_blob into current_choices, such that current_choices
// are still sorted in non-descending order by rating.
ASSERT_HOST(!current_choices->empty());
temp_it.set_to_list(current_choices);
for (temp_it.mark_cycle_pt();
!temp_it.cycled_list() &&
merged_choice->rating() > temp_it.data()->rating();
temp_it.forward());
temp_it.add_before_stay_put(merged_choice);
// Done merging this fragmented character.
merging_fragment = false;
}
}
if (!merging_fragment) {
// Get rid of fragments in current_choices.
temp_it.set_to_list(current_choices);
for (temp_it.mark_cycle_pt(); !temp_it.cycled_list();
temp_it.forward()) {
if (getDict().getUnicharset().get_fragment(
temp_it.data()->unichar_id())) {
delete temp_it.extract();
}
}
char_choices->set(current_choices, char_choices_index);
char_choices_index--;
// Update word_ptr and word_lengths_ptr.
if (word_lengths_ptr != NULL && word_ptr != NULL) {
word_lengths_ptr--;
word_ptr -= (*word_lengths_ptr);
}
}
y = x - 1;
x = y - search_state[i];
}
old_choices->delete_data_pointers();
delete old_choices;
memfree(search_state);
return (char_choices);
}
} // namespace tesseract
/**********************************************************************
* expand_node
*
* Create the states that are attached to this one. Check to see that
* each one has not already been visited. If not add it to the priority
* queue.
**********************************************************************/
namespace tesseract {
void Wordrec::expand_node(FLOAT32 worst_priority,
CHUNKS_RECORD *chunks_record,
SEARCH_RECORD *the_search) {
STATE old_state;
int nodes_added = 0;
int x;
uinT32 mask = 1 << (the_search->num_joints - 1 - 32);
old_state.part1 = the_search->this_state->part1;
old_state.part2 = the_search->this_state->part2;
// We need to expand the search more intelligently, or we get stuck
// with a bad starting segmentation in a long word sequence as in CJK.
// Expand a child node only if it is within the global bound, and no
// worse than 2x of its parent.
// TODO(dsl): There is some redudency here in recomputing the priority,
// and in filtering of old_merit and worst_priority.
the_search->this_state->part2 = old_state.part2;
for (x = the_search->num_joints; x > 32; x--) {
the_search->this_state->part1 = mask ^ old_state.part1;
if (!hash_lookup (the_search->closed_states, the_search->this_state)) {
FLOAT32 new_merit = prioritize_state(chunks_record, the_search);
if (segment_debug && permute_debug) {
cprintf ("....checking state: %8.3f ", new_merit);
print_state ("", the_search->this_state, num_joints);
}
if (new_merit < worst_priority) {
push_queue (the_search->open_states, the_search->this_state,
worst_priority, new_merit);
nodes_added++;
}
}
mask >>= 1;
}
if (the_search->num_joints > 32) {
mask = 1 << 31;
}
else {
mask = 1 << (the_search->num_joints - 1);
}
the_search->this_state->part1 = old_state.part1;
while (x--) {
the_search->this_state->part2 = mask ^ old_state.part2;
if (!hash_lookup (the_search->closed_states, the_search->this_state)) {
FLOAT32 new_merit = prioritize_state(chunks_record, the_search);
if (segment_debug && permute_debug) {
cprintf ("....checking state: %8.3f ", new_merit);
print_state ("", the_search->this_state, num_joints);
}
if (new_merit < worst_priority) {
push_queue(the_search->open_states, the_search->this_state,
worst_priority, new_merit);
nodes_added++;
}
}
mask >>= 1;
}
}
} // namespace tesseract
/**********************************************************************
* new_search
*
* Create and initialize a new search record.
**********************************************************************/
SEARCH_RECORD *new_search(CHUNKS_RECORD *chunks_record,
int num_joints,
WERD_CHOICE *best_choice,
WERD_CHOICE *raw_choice,
STATE *state) {
SEARCH_RECORD *this_search;
this_search = (SEARCH_RECORD *) memalloc (sizeof (SEARCH_RECORD));
this_search->open_states = MakeHeap (wordrec_num_seg_states * 20);
this_search->closed_states = new_hash_table ();
if (state)
this_search->this_state = new_state (state);
else
cprintf ("error: bad initial state in new_search\n");
this_search->first_state = new_state (this_search->this_state);
this_search->best_state = new_state (this_search->this_state);
this_search->best_choice = best_choice;
this_search->raw_choice = raw_choice;
this_search->num_joints = num_joints;
this_search->num_states = 0;
this_search->before_best = 0;
this_search->segcost_bias = 0;
return (this_search);
}
/**********************************************************************
* pop_queue
*
* Get this state from the priority queue. It should be the state that
* has the greatest urgency to be evaluated.
**********************************************************************/
STATE *pop_queue(HEAP *queue) {
HEAPENTRY entry;
if (GetTopOfHeap (queue, &entry) == OK) {
#ifndef GRAPHICS_DISABLED
if (wordrec_display_segmentations) {
cprintf ("eval state: %8.3f ", entry.Key);
print_state ("", (STATE *) entry.Data, num_joints);
}
#endif
return ((STATE *) entry.Data);
}
else {
return (NULL);
}
}
/**********************************************************************
* push_queue
*
* Add this state into the priority queue.
**********************************************************************/
void push_queue(HEAP *queue, STATE *state, FLOAT32 worst_priority,
FLOAT32 priority) {
HEAPENTRY entry;
if (priority < worst_priority) {
if (SizeOfHeap (queue) >= MaxSizeOfHeap(queue)) {
if (segment_debug) tprintf("Heap is Full\n");
return;
}
if (segment_debug)
tprintf("\tpushing %d node %f\n", num_pushed, priority);
entry.Data = (char *) new_state (state);
num_pushed++;
entry.Key = priority;
HeapStore(queue, &entry);
}
}
/**********************************************************************
* replace_char_widths
*
* Replace the value of the char_width field in the chunks_record with
* the updated width measurements from the last_segmentation.
**********************************************************************/
void replace_char_widths(CHUNKS_RECORD *chunks_record, SEARCH_STATE state) {
WIDTH_RECORD *width_record;
int num_blobs;
int i;
free_widths (chunks_record->char_widths);
num_blobs = state[0] + 1;
width_record = (WIDTH_RECORD *) memalloc (sizeof (int) * num_blobs * 2);
width_record->num_chars = num_blobs;
for (i = 0; i < num_blobs; i++) {
width_record->widths[2 * i] = last_segmentation[i].width;
if (i + 1 < num_blobs)
width_record->widths[2 * i + 1] = last_segmentation[i].gap;
}
chunks_record->char_widths = width_record;
}
namespace tesseract {
BLOB_CHOICE_LIST *Wordrec::join_blobs_and_classify(
TBLOB *blobs, SEAMS seam_list,
int x, int y, int fx, const MATRIX *ratings,
BLOB_CHOICE_LIST_VECTOR *old_choices) {
BLOB_CHOICE_LIST *choices = NULL;
// First check to see if we can look up the classificaiton
// in old_choices (if there is no need to merge blobs).
if (x == y && old_choices != NULL && ratings == NULL) {
choices = old_choices->get(x);
old_choices->set(NULL, x);
return choices;
}
// The ratings matrix filled in by the associator will contain the most
// up-to-date classification info. Thus we look up the classification there
// first, and only call classify_blob() if the classification is not found.
if (ratings != NULL) {
BLOB_CHOICE_LIST *choices_ptr = ratings->get(x, y);
if (choices_ptr != NOT_CLASSIFIED) {
choices = new BLOB_CHOICE_LIST();
choices->deep_copy(choices_ptr, &BLOB_CHOICE::deep_copy);
}
}
if (x != y) {
join_pieces(blobs, seam_list, x, y);
int blobindex; // current blob
TBLOB *p_blob;
TBLOB *blob;
TBLOB *next_blob;
for (blob = blobs, blobindex = 0, p_blob = NULL;
blobindex < x; blobindex++) {
p_blob = blob;
blob = blob->next;
}
while (blobindex < y) {
next_blob = blob->next;
blob->next = next_blob->next;
oldblob(next_blob); // junk dead blobs
blobindex++;
}
if (choices == NULL) {
choices = classify_blob(p_blob, blob, blob->next,
NULL, "rebuild", Orange);
}
}
return choices;
}
} // namespace tesseract