26
26
#include < cstdint> // for int16_t, int32_t
27
27
#include < cstdio> // for fclose, fopen, FILE
28
28
#include < ctime> // for clock
29
+ #include < future>
29
30
#include " control.h"
30
31
#ifndef DISABLED_LEGACY_ENGINE
31
32
# include " docqual.h"
@@ -194,36 +195,42 @@ void Tesseract::SetupWordPassN(int pass_n, WordData *word) {
194
195
}
195
196
}
196
197
197
- // Runs word recognition on all the words.
198
- bool Tesseract::RecogAllWordsPassN (int pass_n, ETEXT_DESC *monitor, PAGE_RES_IT *pr_it,
199
- std::vector<WordData> *words) {
200
- // TODO(rays) Before this loop can be parallelized (it would yield a massive
201
- // speed-up) all remaining member globals need to be converted to local/heap
202
- // (eg set_pass1 and set_pass2) and an intermediate adaption pass needs to be
203
- // added. The results will be significantly different with adaption on, and
204
- // deterioration will need investigation.
205
- pr_it->restart_page ();
206
- for (unsigned w = 0 ; w < words->size (); ++w) {
207
- WordData *word = &(*words)[w];
208
- if (w > 0 ) {
209
- word->prev_word = &(*words)[w - 1 ];
198
+ bool Tesseract::RecogWordsSegment (std::vector<WordData>::iterator start,
199
+ std::vector<WordData>::iterator end,
200
+ int pass_n,
201
+ ETEXT_DESC *monitor,
202
+ PAGE_RES *page_res,
203
+ LSTMRecognizer *lstm_recognizer,
204
+ std::atomic<int >& words_done,
205
+ int total_words,
206
+ std::mutex& monitor_mutex) {
207
+ PAGE_RES_IT pr_it (page_res);
208
+ // Process a segment of the words vector
209
+ pr_it.restart_page ();
210
+
211
+ for (auto it = start; it != end; ++it, ++words_done) {
212
+ WordData *word = &(*it);
213
+ if (it != start) {
214
+ word->prev_word = &(*(it - 1 ));
210
215
}
211
216
if (monitor != nullptr ) {
217
+ std::lock_guard<std::mutex> lock (monitor_mutex);
212
218
monitor->ocr_alive = true ;
213
219
if (pass_n == 1 ) {
214
- monitor->progress = 70 * w / words-> size () ;
220
+ monitor->progress = 70 * words_done / total_words ;
215
221
} else {
216
- monitor->progress = 70 + 30 * w / words-> size () ;
222
+ monitor->progress = 70 + 30 * words_done / total_words ;
217
223
}
224
+ // Only call the progress callback for the first thread.
218
225
if (monitor->progress_callback2 != nullptr ) {
219
- TBOX box = pr_it-> word ()->word ->bounding_box ();
226
+ TBOX box = pr_it. word ()->word ->bounding_box ();
220
227
(*monitor->progress_callback2 )(monitor, box.left (), box.right (), box.top (), box.bottom ());
221
228
}
222
229
if (monitor->deadline_exceeded () ||
223
- (monitor->cancel != nullptr && (*monitor->cancel )(monitor->cancel_this , words-> size () ))) {
230
+ (monitor->cancel != nullptr && (*monitor->cancel )(monitor->cancel_this , total_words ))) {
224
231
// Timeout. Fake out the rest of the words.
225
- for (; w < words-> size () ; ++w ) {
226
- (*words)[w]. word ->SetupFake (unicharset);
232
+ for (; it != end ; ++it ) {
233
+ it-> word ->SetupFake (unicharset);
227
234
}
228
235
return false ;
229
236
}
@@ -238,31 +245,69 @@ bool Tesseract::RecogAllWordsPassN(int pass_n, ETEXT_DESC *monitor, PAGE_RES_IT
238
245
}
239
246
}
240
247
// Sync pr_it with the WordData.
241
- while (pr_it-> word () != nullptr && pr_it-> word () != word->word ) {
242
- pr_it-> forward ();
248
+ while (pr_it. word () != nullptr && pr_it. word () != word->word ) {
249
+ pr_it. forward ();
243
250
}
244
- ASSERT_HOST (pr_it-> word () != nullptr );
251
+ ASSERT_HOST (pr_it. word () != nullptr );
245
252
bool make_next_word_fuzzy = false ;
246
253
#ifndef DISABLED_LEGACY_ENGINE
247
- if (!AnyLSTMLang () && ReassignDiacritics (pass_n, pr_it, &make_next_word_fuzzy)) {
254
+ if (!AnyLSTMLang () && ReassignDiacritics (pass_n, & pr_it, &make_next_word_fuzzy)) {
248
255
// Needs to be setup again to see the new outlines in the chopped_word.
249
256
SetupWordPassN (pass_n, word);
250
257
}
251
258
#endif // ndef DISABLED_LEGACY_ENGINE
252
259
253
- classify_word_and_language (pass_n, pr_it, word);
260
+ classify_word_and_language (pass_n, & pr_it, word, lstm_recognizer );
254
261
if (tessedit_dump_choices || debug_noise_removal) {
255
262
tprintf (" Pass%d: %s [%s]\n " , pass_n, word->word ->best_choice ->unichar_string ().c_str (),
256
263
word->word ->best_choice ->debug_string ().c_str ());
257
264
}
258
- pr_it-> forward ();
259
- if (make_next_word_fuzzy && pr_it-> word () != nullptr ) {
260
- pr_it-> MakeCurrentWordFuzzy ();
265
+ pr_it. forward ();
266
+ if (make_next_word_fuzzy && pr_it. word () != nullptr ) {
267
+ pr_it. MakeCurrentWordFuzzy ();
261
268
}
262
269
}
263
270
return true ;
264
271
}
265
272
273
+ // Runs word recognition on all the words.
274
+ bool Tesseract::RecogAllWordsPassN (int pass_n, ETEXT_DESC *monitor, PAGE_RES *page_res,
275
+ std::vector<WordData> *words) {
276
+ int total_words = words->size ();
277
+ int segment_size = total_words / lstm_num_threads;
278
+ std::atomic<int > words_done (0 );
279
+ std::mutex monitor_mutex;
280
+ std::vector<std::future<bool >> futures;
281
+
282
+ // Launch multiple threads to recognize the words in parallel
283
+ auto segment_start = words->begin () + segment_size;
284
+ for (int i = 1 ; i < lstm_num_threads; ++i) {
285
+ auto segment_end = (i == lstm_num_threads - 1 ) ? words->end () : segment_start + segment_size;
286
+ futures.push_back (std::async (std::launch::async, &Tesseract::RecogWordsSegment,
287
+ this , segment_start, segment_end, pass_n, monitor, page_res,
288
+ lstm_recognizers_[i], std::ref (words_done), total_words, std::ref (monitor_mutex)));
289
+ segment_start = segment_end;
290
+ }
291
+
292
+ // Process the first segment in this thread
293
+ bool overall_result = RecogWordsSegment (words->begin (),
294
+ words->begin () + segment_size,
295
+ pass_n,
296
+ monitor,
297
+ page_res,
298
+ lstm_recognizers_[0 ],
299
+ std::ref (words_done),
300
+ total_words,
301
+ std::ref (monitor_mutex));
302
+
303
+ // Wait for all threads to complete and aggregate results
304
+ for (auto &f : futures) {
305
+ overall_result &= f.get ();
306
+ }
307
+
308
+ return overall_result;
309
+ }
310
+
266
311
/* *
267
312
* recog_all_words()
268
313
*
@@ -340,7 +385,7 @@ bool Tesseract::recog_all_words(PAGE_RES *page_res, ETEXT_DESC *monitor,
340
385
341
386
most_recently_used_ = this ;
342
387
// Run pass 1 word recognition.
343
- if (!RecogAllWordsPassN (1 , monitor, &page_res_it , &words)) {
388
+ if (!RecogAllWordsPassN (1 , monitor, page_res , &words)) {
344
389
return false ;
345
390
}
346
391
// Pass 1 post-processing.
@@ -380,11 +425,10 @@ bool Tesseract::recog_all_words(PAGE_RES *page_res, ETEXT_DESC *monitor,
380
425
}
381
426
most_recently_used_ = this ;
382
427
// Run pass 2 word recognition.
383
- if (!RecogAllWordsPassN (2 , monitor, &page_res_it , &words)) {
428
+ if (!RecogAllWordsPassN (2 , monitor, page_res , &words)) {
384
429
return false ;
385
430
}
386
431
}
387
-
388
432
// The next passes are only required for Tess-only.
389
433
if (AnyTessLang () && !AnyLSTMLang ()) {
390
434
// ****************** Pass 3 *******************
@@ -871,14 +915,15 @@ static int SelectBestWords(double rating_ratio, double certainty_margin, bool de
871
915
// Returns positive if this recognizer found more new best words than the
872
916
// number kept from best_words.
873
917
int Tesseract::RetryWithLanguage (const WordData &word_data, WordRecognizer recognizer, bool debug,
874
- WERD_RES **in_word, PointerVector<WERD_RES> *best_words) {
918
+ WERD_RES **in_word, PointerVector<WERD_RES> *best_words,
919
+ LSTMRecognizer *lstm_recognizer) {
875
920
if (debug) {
876
921
tprintf (" Trying word using lang %s, oem %d\n " , lang.c_str (),
877
922
static_cast <int >(tessedit_ocr_engine_mode));
878
923
}
879
924
// Run the recognizer on the word.
880
925
PointerVector<WERD_RES> new_words;
881
- (this ->*recognizer)(word_data, in_word, &new_words);
926
+ (this ->*recognizer)(word_data, in_word, &new_words, lstm_recognizer );
882
927
if (new_words.empty ()) {
883
928
// Transfer input word to new_words, as the classifier must have put
884
929
// the result back in the input.
@@ -1300,7 +1345,10 @@ float Tesseract::ClassifyBlobAsWord(int pass_n, PAGE_RES_IT *pr_it, C_BLOB *blob
1300
1345
// Recognizes in the current language, and if successful that is all.
1301
1346
// If recognition was not successful, tries all available languages until
1302
1347
// it gets a successful result or runs out of languages. Keeps the best result.
1303
- void Tesseract::classify_word_and_language (int pass_n, PAGE_RES_IT *pr_it, WordData *word_data) {
1348
+ void Tesseract::classify_word_and_language (int pass_n, PAGE_RES_IT *pr_it, WordData *word_data,
1349
+ LSTMRecognizer *lstm_recognizer_thread_local) {
1350
+ LSTMRecognizer *lstm_recognizer = lstm_recognizer_thread_local ? lstm_recognizer_thread_local
1351
+ : lstm_recognizer_;
1304
1352
#ifdef DISABLED_LEGACY_ENGINE
1305
1353
WordRecognizer recognizer = &Tesseract::classify_word_pass1;
1306
1354
#else
@@ -1333,19 +1381,19 @@ void Tesseract::classify_word_and_language(int pass_n, PAGE_RES_IT *pr_it, WordD
1333
1381
}
1334
1382
}
1335
1383
most_recently_used_->RetryWithLanguage (*word_data, recognizer, debug, &word_data->lang_words [sub],
1336
- &best_words);
1384
+ &best_words, lstm_recognizer );
1337
1385
Tesseract *best_lang_tess = most_recently_used_;
1338
1386
if (!WordsAcceptable (best_words)) {
1339
1387
// Try all the other languages to see if they are any better.
1340
1388
if (most_recently_used_ != this &&
1341
1389
this ->RetryWithLanguage (*word_data, recognizer, debug,
1342
- &word_data->lang_words [sub_langs_.size ()], &best_words) > 0 ) {
1390
+ &word_data->lang_words [sub_langs_.size ()], &best_words, lstm_recognizer ) > 0 ) {
1343
1391
best_lang_tess = this ;
1344
1392
}
1345
1393
for (unsigned i = 0 ; !WordsAcceptable (best_words) && i < sub_langs_.size (); ++i) {
1346
1394
if (most_recently_used_ != sub_langs_[i] &&
1347
1395
sub_langs_[i]->RetryWithLanguage (*word_data, recognizer, debug, &word_data->lang_words [i],
1348
- &best_words) > 0 ) {
1396
+ &best_words, lstm_recognizer ) > 0 ) {
1349
1397
best_lang_tess = sub_langs_[i];
1350
1398
}
1351
1399
}
@@ -1378,7 +1426,7 @@ void Tesseract::classify_word_and_language(int pass_n, PAGE_RES_IT *pr_it, WordD
1378
1426
*/
1379
1427
1380
1428
void Tesseract::classify_word_pass1 (const WordData &word_data, WERD_RES **in_word,
1381
- PointerVector<WERD_RES> *out_words) {
1429
+ PointerVector<WERD_RES> *out_words, LSTMRecognizer *lstm_recognizer ) {
1382
1430
ROW *row = word_data.row ;
1383
1431
BLOCK *block = word_data.block ;
1384
1432
prev_word_best_choice_ =
@@ -1390,14 +1438,14 @@ void Tesseract::classify_word_pass1(const WordData &word_data, WERD_RES **in_wor
1390
1438
tessedit_ocr_engine_mode == OEM_TESSERACT_LSTM_COMBINED) {
1391
1439
#endif // def DISABLED_LEGACY_ENGINE
1392
1440
if (!(*in_word)->odd_size || tessedit_ocr_engine_mode == OEM_LSTM_ONLY) {
1393
- LSTMRecognizeWord (*block, row, *in_word, out_words);
1441
+ LSTMRecognizeWord (*block, row, *in_word, out_words, lstm_recognizer );
1394
1442
if (!out_words->empty ()) {
1395
1443
return ; // Successful lstm recognition.
1396
1444
}
1397
1445
}
1398
1446
if (tessedit_ocr_engine_mode == OEM_LSTM_ONLY) {
1399
1447
// No fallback allowed, so use a fake.
1400
- (*in_word)->SetupFake (lstm_recognizer_ ->GetUnicharset ());
1448
+ (*in_word)->SetupFake (lstm_recognizer ->GetUnicharset ());
1401
1449
return ;
1402
1450
}
1403
1451
@@ -1534,7 +1582,7 @@ bool Tesseract::TestNewNormalization(int original_misfits, float baseline_shift,
1534
1582
*/
1535
1583
1536
1584
void Tesseract::classify_word_pass2 (const WordData &word_data, WERD_RES **in_word,
1537
- PointerVector<WERD_RES> *out_words) {
1585
+ PointerVector<WERD_RES> *out_words, LSTMRecognizer *lstm_recognizer ) {
1538
1586
// Return if we do not want to run Tesseract.
1539
1587
if (tessedit_ocr_engine_mode == OEM_LSTM_ONLY) {
1540
1588
return ;
0 commit comments