resultiterator_test.cc 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. #include <allheaders.h>
  2. #include <tesseract/baseapi.h>
  3. #include <tesseract/resultiterator.h>
  4. #include <string>
  5. #include "scrollview.h"
  6. #include "include_gunit.h"
  7. #include "log.h" // for LOG
  8. namespace tesseract {
  9. // DEFINE_string(tess_config, "", "config file for tesseract");
  10. // DEFINE_bool(visual_test, false, "Runs a visual test using scrollview");
  11. // The fixture for testing Tesseract.
  12. class ResultIteratorTest : public testing::Test {
  13. protected:
  14. std::string TestDataNameToPath(const std::string &name) {
  15. return file::JoinPath(TESTING_DIR, name);
  16. }
  17. std::string TessdataPath() {
  18. return file::JoinPath(TESSDATA_DIR, "");
  19. }
  20. std::string OutputNameToPath(const std::string &name) {
  21. file::MakeTmpdir();
  22. return file::JoinPath(FLAGS_test_tmpdir, name);
  23. }
  24. ResultIteratorTest() {
  25. src_pix_ = nullptr;
  26. }
  27. ~ResultIteratorTest() override = default;
  28. void SetImage(const char *filename) {
  29. src_pix_ = pixRead(TestDataNameToPath(filename).c_str());
  30. api_.Init(TessdataPath().c_str(), "eng", tesseract::OEM_TESSERACT_ONLY);
  31. // if (!FLAGS_tess_config.empty())
  32. // api_.ReadConfigFile(FLAGS_tess_config.c_str());
  33. api_.SetPageSegMode(tesseract::PSM_AUTO);
  34. api_.SetImage(src_pix_);
  35. src_pix_.destroy();
  36. src_pix_ = api_.GetInputImage();
  37. }
  38. // Rebuilds the image using the binary images at the given level, and
  39. // EXPECTs that the number of pixels in the xor of the rebuilt image with
  40. // the original is at most max_diff.
  41. void VerifyRebuild(int max_diff, PageIteratorLevel level, PageIterator *it) {
  42. it->Begin();
  43. int width = pixGetWidth(src_pix_);
  44. int height = pixGetHeight(src_pix_);
  45. int depth = pixGetDepth(src_pix_);
  46. Image pix = pixCreate(width, height, depth);
  47. EXPECT_TRUE(depth == 1 || depth == 8);
  48. if (depth == 8) {
  49. pixSetAll(pix);
  50. }
  51. do {
  52. int left, top, right, bottom;
  53. PageIteratorLevel im_level = level;
  54. // If the return is false, it is a non-text block so get the block image.
  55. if (!it->BoundingBox(level, &left, &top, &right, &bottom)) {
  56. im_level = tesseract::RIL_BLOCK;
  57. EXPECT_TRUE(it->BoundingBox(im_level, &left, &top, &right, &bottom));
  58. }
  59. LOG(INFO) << "BBox: [L:" << left << ", T:" << top << ", R:" << right << ", B:" << bottom
  60. << "]"
  61. << "\n";
  62. Image block_pix;
  63. if (depth == 1) {
  64. block_pix = it->GetBinaryImage(im_level);
  65. pixRasterop(pix, left, top, right - left, bottom - top, PIX_SRC ^ PIX_DST, block_pix, 0, 0);
  66. } else {
  67. block_pix = it->GetImage(im_level, 2, src_pix_, &left, &top);
  68. pixRasterop(pix, left, top, pixGetWidth(block_pix), pixGetHeight(block_pix),
  69. PIX_SRC & PIX_DST, block_pix, 0, 0);
  70. }
  71. CHECK(block_pix != nullptr);
  72. block_pix.destroy();
  73. } while (it->Next(level));
  74. // if (base::GetFlag(FLAGS_v) >= 1)
  75. // pixWrite(OutputNameToPath("rebuilt.png").c_str(), pix, IFF_PNG);
  76. pixRasterop(pix, 0, 0, width, height, PIX_SRC ^ PIX_DST, src_pix_, 0, 0);
  77. if (depth == 8) {
  78. Image binary_pix = pixThresholdToBinary(pix, 128);
  79. pix.destroy();
  80. pixInvert(binary_pix, binary_pix);
  81. pix = binary_pix;
  82. }
  83. // if (base::GetFlag(FLAGS_v) >= 1)
  84. // pixWrite(OutputNameToPath("rebuiltxor.png").c_str(), pix, IFF_PNG);
  85. l_int32 pixcount;
  86. pixCountPixels(pix, &pixcount, nullptr);
  87. if (pixcount > max_diff) {
  88. std::string outfile = OutputNameToPath("failedxor.png");
  89. LOG(INFO) << "outfile = " << outfile << "\n";
  90. pixWrite(outfile.c_str(), pix, IFF_PNG);
  91. }
  92. pix.destroy();
  93. LOG(INFO) << "At level " << level << ": pix diff = " << pixcount << "\n";
  94. EXPECT_LE(pixcount, max_diff);
  95. // if (base::GetFlag(FLAGS_v) > 1) CHECK_LE(pixcount, max_diff);
  96. }
  97. // Rebuilds the text from the iterator strings at the given level, and
  98. // EXPECTs that the rebuild string exactly matches the truth string.
  99. void VerifyIteratorText(const std::string &truth, PageIteratorLevel level, ResultIterator *it) {
  100. LOG(INFO) << "Text Test Level " << level << "\n";
  101. it->Begin();
  102. std::string result;
  103. do {
  104. char *text = it->GetUTF8Text(level);
  105. result += text;
  106. delete[] text;
  107. if ((level == tesseract::RIL_WORD || level == tesseract::RIL_SYMBOL) &&
  108. it->IsAtFinalElement(tesseract::RIL_WORD, level)) {
  109. if (it->IsAtFinalElement(tesseract::RIL_TEXTLINE, level)) {
  110. result += '\n';
  111. } else {
  112. result += ' ';
  113. }
  114. if (it->IsAtFinalElement(tesseract::RIL_PARA, level) &&
  115. !(it->IsAtFinalElement(tesseract::RIL_BLOCK, level))) {
  116. result += '\n';
  117. }
  118. }
  119. } while (it->Next(level));
  120. EXPECT_STREQ(truth.c_str(), result.c_str()) << "Rebuild failed at Text Level " << level;
  121. }
  122. void VerifyRebuilds(int block_limit, int para_limit, int line_limit, int word_limit,
  123. int symbol_limit, PageIterator *it, PageIteratorLevel maxlevel=tesseract::RIL_SYMBOL) {
  124. VerifyRebuild(block_limit, tesseract::RIL_BLOCK, it);
  125. VerifyRebuild(para_limit, tesseract::RIL_PARA, it);
  126. VerifyRebuild(line_limit, tesseract::RIL_TEXTLINE, it);
  127. VerifyRebuild(word_limit, tesseract::RIL_WORD, it);
  128. if (maxlevel == tesseract::RIL_SYMBOL) {
  129. VerifyRebuild(symbol_limit, maxlevel, it);
  130. }
  131. }
  132. void VerifyAllText(const std::string &truth, ResultIterator *it) {
  133. VerifyIteratorText(truth, tesseract::RIL_BLOCK, it);
  134. VerifyIteratorText(truth, tesseract::RIL_PARA, it);
  135. VerifyIteratorText(truth, tesseract::RIL_TEXTLINE, it);
  136. VerifyIteratorText(truth, tesseract::RIL_WORD, it);
  137. VerifyIteratorText(truth, tesseract::RIL_SYMBOL, it);
  138. }
  139. // Verifies that ResultIterator::CalculateTextlineOrder() produces the right
  140. // results given an array of word directions (word_dirs[num_words]), an
  141. // expected output reading order
  142. // (expected_reading_order[num_reading_order_entries]) and a given reading
  143. // context (ltr or rtl).
  144. void ExpectTextlineReadingOrder(bool in_ltr_context, const StrongScriptDirection *word_dirs,
  145. int num_words, int *expected_reading_order,
  146. int num_reading_order_entries) const {
  147. std::vector<StrongScriptDirection> gv_word_dirs;
  148. for (int i = 0; i < num_words; i++) {
  149. gv_word_dirs.push_back(word_dirs[i]);
  150. }
  151. std::vector<int> calculated_order;
  152. ResultIterator::CalculateTextlineOrder(in_ltr_context, gv_word_dirs, &calculated_order);
  153. // STL vector can be used with EXPECT_EQ, so convert...
  154. std::vector<int> correct_order(expected_reading_order,
  155. expected_reading_order + num_reading_order_entries);
  156. EXPECT_EQ(correct_order, calculated_order);
  157. }
  158. // Verify that ResultIterator::CalculateTextlineOrder() produces sane output
  159. // for a given array of word_dirs[num_words] in ltr or rtl context.
  160. // Sane means that the output contains some permutation of the indices
  161. // 0..[num_words - 1] interspersed optionally with negative (marker) values.
  162. void VerifySaneTextlineOrder(bool in_ltr_context, const StrongScriptDirection *word_dirs,
  163. int num_words) const {
  164. std::vector<StrongScriptDirection> gv_word_dirs;
  165. for (int i = 0; i < num_words; i++) {
  166. gv_word_dirs.push_back(word_dirs[i]);
  167. }
  168. std::vector<int> output;
  169. ResultIterator::CalculateTextlineOrder(in_ltr_context, gv_word_dirs, &output);
  170. ASSERT_GE(output.size(), num_words);
  171. std::vector<int> output_copy(output);
  172. std::sort(output_copy.begin(), output_copy.end());
  173. bool sane = true;
  174. unsigned j = 0;
  175. while (j < output_copy.size() && output_copy[j] < 0) {
  176. j++;
  177. }
  178. for (int i = 0; i < num_words; i++, j++) {
  179. if (output_copy[j] != i) {
  180. sane = false;
  181. break;
  182. }
  183. }
  184. if (j != output_copy.size()) {
  185. sane = false;
  186. }
  187. if (!sane) {
  188. std::vector<int> empty;
  189. EXPECT_EQ(output, empty) << " permutation of 0.." << num_words - 1 << " not found in "
  190. << (in_ltr_context ? "ltr" : "rtl") << " context.";
  191. }
  192. }
  193. // Objects declared here can be used by all tests in the test case for Foo.
  194. Image src_pix_; // Borrowed from api_. Do not destroy.
  195. std::string ocr_text_;
  196. tesseract::TessBaseAPI api_;
  197. };
  198. // Tests layout analysis output (and scrollview) on the UNLV page numbered
  199. // 8087_054.3G.tif. (Dubrovnik), but only if --visual_test is true.
  200. //
  201. // TEST_F(ResultIteratorTest, VisualTest) {
  202. // if (!FLAGS_visual_test) return;
  203. // const char* kIms[] = {"8087_054.3G.tif", "8071_093.3B.tif", nullptr};
  204. // for (int i = 0; kIms[i] != nullptr; ++i) {
  205. // SetImage(kIms[i]);
  206. // // Just run layout analysis.
  207. // PageIterator* it = api_.AnalyseLayout();
  208. // EXPECT_FALSE(it == nullptr);
  209. // // Make a scrollview window for the display.
  210. // int width = pixGetWidth(src_pix_);
  211. // int height = pixGetHeight(src_pix_);
  212. // ScrollView* win =
  213. // new ScrollView(kIms[i], 100, 100, width / 2, height / 2, width, height);
  214. // win->Image(src_pix_, 0, 0);
  215. // it->Begin();
  216. // ScrollView::Color color = ScrollView::RED;
  217. // win->Brush(ScrollView::NONE);
  218. // do {
  219. // Pta* pts = it->BlockPolygon();
  220. // if (pts != nullptr) {
  221. // win->Pen(color);
  222. // int num_pts = ptaGetCount(pts);
  223. // l_float32 x, y;
  224. // ptaGetPt(pts, num_pts - 1, &x, &y);
  225. // win->SetCursor(static_cast<int>(x), static_cast<int>(y));
  226. // for (int p = 0; p < num_pts; ++p) {
  227. // ptaGetPt(pts, p, &x, &y);
  228. // win->DrawTo(static_cast<int>(x), static_cast<int>(y));
  229. // }
  230. // }
  231. // ptaDestroy(&pts);
  232. // } while (it->Next(tesseract::RIL_BLOCK));
  233. // win->Update();
  234. // delete win->AwaitEvent(SVET_DESTROY);
  235. // delete win;
  236. // delete it;
  237. // }
  238. //}
  239. // Tests that Tesseract gets exactly the right answer on phototest.
  240. TEST_F(ResultIteratorTest, EasyTest) {
  241. SetImage("phototest.tif");
  242. // Just run layout analysis.
  243. PageIterator *p_it = api_.AnalyseLayout();
  244. EXPECT_FALSE(p_it == nullptr);
  245. // Check iterator position.
  246. EXPECT_TRUE(p_it->IsAtBeginningOf(tesseract::RIL_BLOCK));
  247. // This should be a single block.
  248. EXPECT_FALSE(p_it->Next(tesseract::RIL_BLOCK));
  249. EXPECT_FALSE(p_it->IsAtBeginningOf(tesseract::RIL_BLOCK));
  250. // The images should rebuild almost perfectly.
  251. LOG(INFO) << "Verifying image rebuilds 1 (pageiterator)"
  252. << "\n";
  253. VerifyRebuilds(10, 10, 0, 0, 0, p_it);
  254. delete p_it;
  255. char *result = api_.GetUTF8Text();
  256. ocr_text_ = result;
  257. delete[] result;
  258. ResultIterator *r_it = api_.GetIterator();
  259. // The images should rebuild almost perfectly.
  260. LOG(INFO) << "Verifying image rebuilds 2a (resultiterator)"
  261. << "\n";
  262. VerifyRebuilds(8, 8, 0, 0, 40, r_it, tesseract::RIL_WORD);
  263. // Test the text.
  264. LOG(INFO) << "Verifying text rebuilds 1 (resultiterator)"
  265. << "\n";
  266. VerifyAllText(ocr_text_, r_it);
  267. // The images should rebuild almost perfectly.
  268. LOG(INFO) << "Verifying image rebuilds 2b (resultiterator)"
  269. << "\n";
  270. VerifyRebuilds(8, 8, 0, 0, 40, r_it, tesseract::RIL_WORD);
  271. r_it->Begin();
  272. // Test baseline of the first line.
  273. int x1, y1, x2, y2;
  274. r_it->Baseline(tesseract::RIL_TEXTLINE, &x1, &y1, &x2, &y2);
  275. LOG(INFO) << "Baseline ("
  276. << x1 << ',' << y1 << ")->(" << x2 << ',' << y2 << ")\n";
  277. // Make sure we have a decent vector.
  278. EXPECT_GE(x2, x1 + 400);
  279. // The point 200,116 should be very close to the baseline.
  280. // (x3,y3) is the vector from (x1,y1) to (200,116)
  281. int x3 = 200 - x1;
  282. int y3 = 116 - y1;
  283. x2 -= x1;
  284. y2 -= y1;
  285. // The cross product (x2,y1)x(x3,y3) should be small.
  286. int product = x2 * y3 - x3 * y2;
  287. EXPECT_LE(abs(product), x2);
  288. // Test font attributes for each word.
  289. do {
  290. float confidence = r_it->Confidence(tesseract::RIL_WORD);
  291. #ifndef DISABLED_LEGACY_ENGINE
  292. int pointsize, font_id;
  293. bool bold, italic, underlined, monospace, serif, smallcaps;
  294. const char *font = r_it->WordFontAttributes(&bold, &italic, &underlined, &monospace, &serif,
  295. &smallcaps, &pointsize, &font_id);
  296. EXPECT_GE(confidence, 80.0f);
  297. #endif
  298. char *word_str = r_it->GetUTF8Text(tesseract::RIL_WORD);
  299. #ifdef DISABLED_LEGACY_ENGINE
  300. LOG(INFO) << "Word " << word_str << ", conf " << confidence << "\n";
  301. #else
  302. LOG(INFO) << "Word " << word_str << " in font " << font
  303. << ", id " << font_id << ", size " << pointsize
  304. << ", conf " << confidence << "\n";
  305. #endif // def DISABLED_LEGACY_ENGINE
  306. delete[] word_str;
  307. #ifndef DISABLED_LEGACY_ENGINE
  308. EXPECT_FALSE(bold);
  309. EXPECT_FALSE(italic);
  310. EXPECT_FALSE(underlined);
  311. EXPECT_FALSE(monospace);
  312. EXPECT_FALSE(serif);
  313. // The text is about 31 pixels high. Above we say the source is 200 ppi,
  314. // which translates to:
  315. // 31 pixels / textline * (72 pts / inch) / (200 pixels / inch) = 11.16 pts
  316. EXPECT_GE(pointsize, 11.16 - 1.50);
  317. EXPECT_LE(pointsize, 11.16 + 1.50);
  318. #endif // def DISABLED_LEGACY_ENGINE
  319. } while (r_it->Next(tesseract::RIL_WORD));
  320. delete r_it;
  321. }
  322. // Tests image rebuild on the UNLV page numbered 8087_054.3B.tif. (Dubrovnik)
  323. TEST_F(ResultIteratorTest, ComplexTest) {
  324. SetImage("8087_054.3B.tif");
  325. // Just run layout analysis.
  326. PageIterator *it = api_.AnalyseLayout();
  327. EXPECT_FALSE(it == nullptr);
  328. // The images should rebuild almost perfectly.
  329. VerifyRebuilds(2073, 2073, 2080, 2081, 2090, it);
  330. delete it;
  331. }
  332. // Tests image rebuild on the UNLV page numbered 8087_054.3G.tif. (Dubrovnik)
  333. TEST_F(ResultIteratorTest, GreyTest) {
  334. SetImage("8087_054.3G.tif");
  335. // Just run layout analysis.
  336. PageIterator *it = api_.AnalyseLayout();
  337. EXPECT_FALSE(it == nullptr);
  338. // The images should rebuild almost perfectly.
  339. VerifyRebuilds(600, 600, 600, 600, 600, it);
  340. delete it;
  341. }
  342. // Tests that Tesseract gets smallcaps and dropcaps.
  343. TEST_F(ResultIteratorTest, SmallCapDropCapTest) {
  344. #ifdef DISABLED_LEGACY_ENGINE
  345. // Skip test as LSTM mode does not recognize smallcaps & dropcaps attributes.
  346. GTEST_SKIP();
  347. #else
  348. SetImage("8071_093.3B.tif");
  349. char *result = api_.GetUTF8Text();
  350. delete[] result;
  351. ResultIterator *r_it = api_.GetIterator();
  352. // Iterate over the words.
  353. int found_dropcaps = 0;
  354. int found_smallcaps = 0;
  355. int false_positives = 0;
  356. do {
  357. bool bold, italic, underlined, monospace, serif, smallcaps;
  358. int pointsize, font_id;
  359. r_it->WordFontAttributes(&bold, &italic, &underlined, &monospace, &serif, &smallcaps,
  360. &pointsize, &font_id);
  361. char *word_str = r_it->GetUTF8Text(tesseract::RIL_WORD);
  362. if (word_str != nullptr) {
  363. LOG(INFO) << "Word " << word_str
  364. << " is " << (smallcaps ? "SMALLCAPS" : "Normal") << "\n";
  365. if (r_it->SymbolIsDropcap()) {
  366. ++found_dropcaps;
  367. }
  368. if (strcmp(word_str, "SHE") == 0 || strcmp(word_str, "MOPED") == 0 ||
  369. strcmp(word_str, "RALPH") == 0 || strcmp(word_str, "KINNEY") == 0 || // Not working yet.
  370. strcmp(word_str, "BENNETT") == 0) {
  371. EXPECT_TRUE(smallcaps) << word_str;
  372. ++found_smallcaps;
  373. } else {
  374. if (smallcaps) {
  375. ++false_positives;
  376. }
  377. }
  378. // No symbol other than the first of any word should be dropcap.
  379. ResultIterator s_it(*r_it);
  380. while (s_it.Next(tesseract::RIL_SYMBOL) && !s_it.IsAtBeginningOf(tesseract::RIL_WORD)) {
  381. if (s_it.SymbolIsDropcap()) {
  382. char *sym_str = s_it.GetUTF8Text(tesseract::RIL_SYMBOL);
  383. LOG(ERROR) << "Symbol " << sym_str << " of word " << word_str << " is dropcap";
  384. delete[] sym_str;
  385. }
  386. EXPECT_FALSE(s_it.SymbolIsDropcap());
  387. }
  388. delete[] word_str;
  389. }
  390. } while (r_it->Next(tesseract::RIL_WORD));
  391. delete r_it;
  392. EXPECT_EQ(1, found_dropcaps);
  393. EXPECT_GE(4, found_smallcaps);
  394. EXPECT_LE(false_positives, 3);
  395. #endif // DISABLED_LEGACY_ENGINE
  396. }
  397. #if 0
  398. // TODO(rays) uncomment on the next change to layout analysis.
  399. // CL 22736106 breaks it, but it is fixed in the change when
  400. // the textline finders start to collapse.
  401. // Tests that Tesseract gets subscript and superscript.
  402. // TODO(rays) This test is a bit feeble, due to bad textline finding on this
  403. // image, so beef up the test a bit when we get less false positive subs.
  404. TEST_F(ResultIteratorTest, SubSuperTest) {
  405. SetImage("0146_281.3B.tif");
  406. char* result = api_.GetUTF8Text();
  407. delete [] result;
  408. ResultIterator* r_it = api_.GetIterator();
  409. // Iterate over the symbols.
  410. // Accuracy isn't great, so just count up and expect a decent count of
  411. // positives and negatives.
  412. const char kAllowedSupers[] = "O0123456789-";
  413. int found_subs = 0;
  414. int found_supers = 0;
  415. int found_normal = 0;
  416. do {
  417. if (r_it->SymbolIsSubscript()) {
  418. ++found_subs;
  419. } else if (r_it->SymbolIsSuperscript()) {
  420. result = r_it->GetUTF8Text(tesseract::RIL_SYMBOL);
  421. if (strchr(kAllowedSupers, result[0]) == nullptr) {
  422. char* word = r_it->GetUTF8Text(tesseract::RIL_WORD);
  423. LOG(ERROR) << "Char " << result << " in word " << word << " is unexpected super!";
  424. delete [] word;
  425. EXPECT_TRUE(strchr(kAllowedSupers, result[0]) != nullptr);
  426. }
  427. delete [] result;
  428. ++found_supers;
  429. } else {
  430. ++found_normal;
  431. }
  432. } while (r_it->Next(tesseract::RIL_SYMBOL));
  433. delete r_it;
  434. LOG(INFO) << "Subs = " << found_subs << ", supers= " << found_supers
  435. << ", normal = " << found_normal << "\n";
  436. EXPECT_GE(found_subs, 25);
  437. EXPECT_GE(found_supers, 25);
  438. EXPECT_GE(found_normal, 1350);
  439. }
  440. #endif
  441. static const StrongScriptDirection dL = DIR_LEFT_TO_RIGHT;
  442. static const StrongScriptDirection dR = DIR_RIGHT_TO_LEFT;
  443. static const StrongScriptDirection dN = DIR_NEUTRAL;
  444. // Test that a sequence of words that could be interpreted to start from
  445. // the left side left-to-right or from the right side right-to-left is
  446. // interpreted appropriately in different contexts.
  447. TEST_F(ResultIteratorTest, DualStartTextlineOrderTest) {
  448. const StrongScriptDirection word_dirs[] = {dL, dL, dN, dL, dN, dR, dR, dR};
  449. int reading_order_rtl_context[] = {7, 6, 5, 4, ResultIterator::kMinorRunStart,
  450. 0, 1, 2, 3, ResultIterator::kMinorRunEnd};
  451. int reading_order_ltr_context[] = {
  452. 0, 1, 2, 3, 4, ResultIterator::kMinorRunStart, 7, 6, 5, ResultIterator::kMinorRunEnd};
  453. ExpectTextlineReadingOrder(true, word_dirs, countof(word_dirs), reading_order_ltr_context,
  454. countof(reading_order_ltr_context));
  455. ExpectTextlineReadingOrder(false, word_dirs, countof(word_dirs), reading_order_rtl_context,
  456. countof(reading_order_rtl_context));
  457. }
  458. // Tests that clearly left-direction text (with no right-to-left indications)
  459. // comes out strictly left to right no matter the context.
  460. TEST_F(ResultIteratorTest, LeftwardTextlineOrderTest) {
  461. const StrongScriptDirection word_dirs[] = {dL, dL, dN, dL, dN, dN, dL, dL};
  462. // The order here is just left to right, nothing fancy.
  463. int reading_order_ltr_context[] = {0, 1, 2, 3, 4, 5, 6, 7};
  464. // In the strange event that this shows up in an RTL paragraph, nonetheless
  465. // just presume the whole thing is an LTR line.
  466. int reading_order_rtl_context[] = {ResultIterator::kMinorRunStart, 0, 1, 2, 3, 4, 5, 6, 7,
  467. ResultIterator::kMinorRunEnd};
  468. ExpectTextlineReadingOrder(true, word_dirs, countof(word_dirs), reading_order_ltr_context,
  469. countof(reading_order_ltr_context));
  470. ExpectTextlineReadingOrder(false, word_dirs, countof(word_dirs), reading_order_rtl_context,
  471. countof(reading_order_rtl_context));
  472. }
  473. // Test that right-direction text comes out strictly right-to-left in
  474. // a right-to-left context.
  475. TEST_F(ResultIteratorTest, RightwardTextlineOrderTest) {
  476. const StrongScriptDirection word_dirs[] = {dR, dR, dN, dR, dN, dN, dR, dR};
  477. // The order here is just right-to-left, nothing fancy.
  478. int reading_order_rtl_context[] = {7, 6, 5, 4, 3, 2, 1, 0};
  479. ExpectTextlineReadingOrder(false, word_dirs, countof(word_dirs), reading_order_rtl_context,
  480. countof(reading_order_rtl_context));
  481. }
  482. TEST_F(ResultIteratorTest, TextlineOrderSanityCheck) {
  483. // Iterate through all 7-word sequences and make sure that the output
  484. // contains each of the indices 0..6 exactly once.
  485. const int kNumWords(7);
  486. const int kNumCombos = 1 << (2 * kNumWords); // 4 ^ 7 combinations
  487. StrongScriptDirection word_dirs[kNumWords];
  488. for (int i = 0; i < kNumCombos; i++) {
  489. // generate the next combination.
  490. int tmp = i;
  491. for (auto &word_dir : word_dirs) {
  492. word_dir = static_cast<StrongScriptDirection>(tmp % 4);
  493. tmp = tmp / 4;
  494. }
  495. VerifySaneTextlineOrder(true, word_dirs, kNumWords);
  496. VerifySaneTextlineOrder(false, word_dirs, kNumWords);
  497. }
  498. }
  499. // TODO: Missing image
  500. TEST_F(ResultIteratorTest, DISABLED_NonNullChoicesTest) {
  501. SetImage("5318c4b679264.jpg");
  502. char *result = api_.GetUTF8Text();
  503. delete[] result;
  504. ResultIterator *r_it = api_.GetIterator();
  505. // Iterate over the words.
  506. do {
  507. char *word_str = r_it->GetUTF8Text(tesseract::RIL_WORD);
  508. if (word_str != nullptr) {
  509. LOG(INFO) << "Word " << word_str << ":\n";
  510. ResultIterator s_it = *r_it;
  511. do {
  512. tesseract::ChoiceIterator c_it(s_it);
  513. do {
  514. const char *char_str = c_it.GetUTF8Text();
  515. if (char_str == nullptr) {
  516. LOG(INFO) << "Null char choice"
  517. << "\n";
  518. } else {
  519. LOG(INFO) << "Char choice " << char_str << "\n";
  520. }
  521. CHECK(char_str != nullptr);
  522. } while (c_it.Next());
  523. } while (!s_it.IsAtFinalElement(tesseract::RIL_WORD, tesseract::RIL_SYMBOL) &&
  524. s_it.Next(tesseract::RIL_SYMBOL));
  525. delete[] word_str;
  526. }
  527. } while (r_it->Next(tesseract::RIL_WORD));
  528. delete r_it;
  529. }
  530. // TODO: Missing image
  531. TEST_F(ResultIteratorTest, NonNullConfidencesTest) {
  532. // SetImage("line6.tiff");
  533. SetImage("trainingitalline.tif");
  534. api_.SetPageSegMode(tesseract::PSM_SINGLE_BLOCK);
  535. // Force recognition so we can used the result iterator.
  536. // We don't care about the return from GetUTF8Text.
  537. char *result = api_.GetUTF8Text();
  538. delete[] result;
  539. ResultIterator *r_it = api_.GetIterator();
  540. // Iterate over the words.
  541. do {
  542. char *word_str = r_it->GetUTF8Text(tesseract::RIL_WORD);
  543. if (word_str != nullptr) {
  544. EXPECT_FALSE(r_it->Empty(tesseract::RIL_WORD));
  545. EXPECT_FALSE(r_it->Empty(tesseract::RIL_SYMBOL));
  546. ResultIterator s_it = *r_it;
  547. do {
  548. const char *char_str = s_it.GetUTF8Text(tesseract::RIL_SYMBOL);
  549. CHECK(char_str != nullptr);
  550. float confidence = s_it.Confidence(tesseract::RIL_SYMBOL);
  551. LOG(INFO) << "Char " << char_str << " has confidence " << confidence << "\n";
  552. delete[] char_str;
  553. } while (!s_it.IsAtFinalElement(tesseract::RIL_WORD, tesseract::RIL_SYMBOL) &&
  554. s_it.Next(tesseract::RIL_SYMBOL));
  555. delete[] word_str;
  556. } else {
  557. LOG(INFO) << "Empty word found"
  558. << "\n";
  559. }
  560. } while (r_it->Next(tesseract::RIL_WORD));
  561. delete r_it;
  562. }
  563. } // namespace tesseract