// (C) Copyright 2017, 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 "include_gunit.h" #include "colpartitiongrid.h" #include "equationdetect.h" #include "tesseractclass.h" #include #include #include #include #define ENABLE_IdentifySpecialText_TEST 0 #if ENABLE_IdentifySpecialText_TEST #define EQU_TRAINEDDATA_NAME "equ" #else #define EQU_TRAINEDDATA_NAME "equINTENTIONALLY_MISSING_FILE" #endif namespace tesseract { class TestableEquationDetect : public EquationDetect { public: TestableEquationDetect(const char* tessdata, Tesseract* lang_tesseract) : EquationDetect(tessdata, EQU_TRAINEDDATA_NAME) { SetLangTesseract(lang_tesseract); } // Insert a certain math and digit blobs into part. void AddMathDigitBlobs(const int math_blobs, const int digit_blobs, const int total_blobs, ColPartition* part) { CHECK(part != nullptr); CHECK_LE(math_blobs + digit_blobs, total_blobs); int count = 0; for (int i = 0; i < math_blobs; i++, count++) { BLOBNBOX* blob = new BLOBNBOX(); blob->set_special_text_type(BSTT_MATH); part->AddBox(blob); } for (int i = 0; i < digit_blobs; i++, count++) { BLOBNBOX* blob = new BLOBNBOX(); blob->set_special_text_type(BSTT_DIGIT); part->AddBox(blob); } for (int i = count; i < total_blobs; i++) { BLOBNBOX* blob = new BLOBNBOX(); blob->set_special_text_type(BSTT_NONE); part->AddBox(blob); } } // Set up pix_binary for lang_tesseract_. void SetPixBinary(Pix* pix) { CHECK_EQ(1, pixGetDepth(pix)); *(lang_tesseract_->mutable_pix_binary()) = pix; } void RunIdentifySpecialText(BLOBNBOX* blob, const int height_th) { IdentifySpecialText(blob, height_th); } BlobSpecialTextType RunEstimateTypeForUnichar(const char* val) { const UNICHARSET& unicharset = lang_tesseract_->unicharset; return EstimateTypeForUnichar(unicharset, unicharset.unichar_to_id(val)); } EquationDetect::IndentType RunIsIndented(ColPartitionGrid* part_grid, ColPartition* part) { this->part_grid_ = part_grid; return IsIndented(part); } bool RunIsNearSmallNeighbor(const TBOX& seed_box, const TBOX& part_box) { return IsNearSmallNeighbor(seed_box, part_box); } bool RunCheckSeedBlobsCount(ColPartition* part) { return CheckSeedBlobsCount(part); } float RunComputeForegroundDensity(const TBOX& tbox) { return ComputeForegroundDensity(tbox); } int RunCountAlignment(const GenericVector& sorted_vec, const int val) { return CountAlignment(sorted_vec, val); } void RunSplitCPHorLite(ColPartition* part, GenericVector* splitted_boxes) { SplitCPHorLite(part, splitted_boxes); } void RunSplitCPHor(ColPartition* part, GenericVector* parts_splitted) { SplitCPHor(part, parts_splitted); } void TestComputeCPsSuperBBox(const TBOX& box, ColPartitionGrid* part_grid) { CHECK(part_grid != nullptr); part_grid_ = part_grid; ComputeCPsSuperBBox(); EXPECT_TRUE(*cps_super_bbox_ == box); } }; class EquationFinderTest : public testing::Test { protected: std::unique_ptr equation_det_; std::unique_ptr tesseract_; // The directory for testdata; std::string testdata_dir_; void SetUp() { std::locale::global(std::locale("")); tesseract_.reset(new Tesseract()); tesseract_->init_tesseract(TESSDATA_DIR, "eng", OEM_TESSERACT_ONLY); tesseract_->set_source_resolution(300); equation_det_.reset( new TestableEquationDetect(TESSDATA_DIR, tesseract_.get())); equation_det_->SetResolution(300); testdata_dir_ = TESTDATA_DIR; } void TearDown() { tesseract_.reset(nullptr); equation_det_.reset(nullptr); } // Add a BLOCK covering the whole page. void AddPageBlock(Pix* pix, BLOCK_LIST* blocks) { CHECK(pix != nullptr); CHECK(blocks != nullptr); BLOCK_IT block_it(blocks); BLOCK* block = new BLOCK("", true, 0, 0, 0, 0, pixGetWidth(pix), pixGetHeight(pix)); block_it.add_to_end(block); } // Create col partitions, add into part_grid, and put them into all_parts. void CreateColParts(const int rows, const int cols, ColPartitionGrid* part_grid, std::vector* all_parts) { const int kWidth = 10, kHeight = 10; ClearParts(all_parts); for (int y = 0; y < rows; ++y) { for (int x = 0; x < cols; ++x) { int left = x * kWidth * 2, bottom = y * kHeight * 2; TBOX box(left, bottom, left + kWidth, bottom + kHeight); ColPartition* part = ColPartition::FakePartition(box, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); part_grid->InsertBBox(true, true, part); all_parts->push_back(part); } } } void ClearParts(std::vector* all_parts) { for (size_t i = 0; i < all_parts->size(); ++i) { (*all_parts)[i]->DeleteBoxes(); delete ((*all_parts)[i]); } } // Create a BLOBNBOX object with bounding box tbox, and add it into part. void AddBlobIntoPart(const TBOX& tbox, ColPartition* part) { CHECK(part != nullptr); BLOBNBOX* blob = new BLOBNBOX(); blob->set_bounding_box(tbox); part->AddBox(blob); } }; TEST_F(EquationFinderTest, IdentifySpecialText) { #if !ENABLE_IdentifySpecialText_TEST GTEST_SKIP(); #else // TODO: missing equ_gt1.tif // Load Image. std::string imagefile = file::JoinPath(testdata_dir_, "equ_gt1.tif"); Pix* pix_binary = pixRead(imagefile.c_str()); CHECK(pix_binary != nullptr && pixGetDepth(pix_binary) == 1); // Get components. BLOCK_LIST blocks; TO_BLOCK_LIST to_blocks; AddPageBlock(pix_binary, &blocks); Textord* textord = tesseract_->mutable_textord(); textord->find_components(pix_binary, &blocks, &to_blocks); // Identify special texts from to_blocks. TO_BLOCK_IT to_block_it(&to_blocks); std::map stt_count; for (to_block_it.mark_cycle_pt(); !to_block_it.cycled_list(); to_block_it.forward()) { TO_BLOCK* to_block = to_block_it.data(); BLOBNBOX_IT blob_it(&(to_block->blobs)); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { BLOBNBOX* blob = blob_it.data(); // blob->set_special_text_type(BSTT_NONE); equation_det_->RunIdentifySpecialText(blob, 0); tensorflow::gtl::InsertIfNotPresent(&stt_count, blob->special_text_type(), 0); stt_count[blob->special_text_type()]++; } } // Verify the number, but allow a range of +/- kCountRange before squealing. const int kCountRange = 3; EXPECT_GE(39 + kCountRange, stt_count[BSTT_NONE]); EXPECT_LE(39 - kCountRange, stt_count[BSTT_NONE]); // if you count all the subscripts etc, there are ~45 italic chars. EXPECT_GE(45 + kCountRange, stt_count[BSTT_ITALIC]); EXPECT_LE(45 - kCountRange, stt_count[BSTT_ITALIC]); EXPECT_GE(41 + kCountRange, stt_count[BSTT_DIGIT]); EXPECT_LE(41 - kCountRange, stt_count[BSTT_DIGIT]); EXPECT_GE(50 + kCountRange, stt_count[BSTT_MATH]); EXPECT_LE(50 - kCountRange, stt_count[BSTT_MATH]); EXPECT_GE(10 + kCountRange, stt_count[BSTT_UNCLEAR]); EXPECT_LE(10 - kCountRange, stt_count[BSTT_UNCLEAR]); // Release memory. pixDestroy(&pix_binary); #endif } TEST_F(EquationFinderTest, EstimateTypeForUnichar) { // Test abc characters. EXPECT_EQ(BSTT_NONE, equation_det_->RunEstimateTypeForUnichar("a")); EXPECT_EQ(BSTT_NONE, equation_det_->RunEstimateTypeForUnichar("c")); // Test punctuation characters. EXPECT_EQ(BSTT_NONE, equation_det_->RunEstimateTypeForUnichar("'")); EXPECT_EQ(BSTT_NONE, equation_det_->RunEstimateTypeForUnichar(",")); // Test digits. EXPECT_EQ(BSTT_DIGIT, equation_det_->RunEstimateTypeForUnichar("1")); EXPECT_EQ(BSTT_DIGIT, equation_det_->RunEstimateTypeForUnichar("4")); EXPECT_EQ(BSTT_DIGIT, equation_det_->RunEstimateTypeForUnichar("|")); // Test math symbols. EXPECT_EQ(BSTT_MATH, equation_det_->RunEstimateTypeForUnichar("(")); EXPECT_EQ(BSTT_MATH, equation_det_->RunEstimateTypeForUnichar("+")); } TEST_F(EquationFinderTest, IsIndented) { ColPartitionGrid part_grid(10, ICOORD(0, 0), ICOORD(1000, 1000)); // Create five ColPartitions: // part 1: ************ // part 2: ********* // part 3: ******* // part 4: ***** // // part 5: ******** TBOX box1(0, 950, 999, 999); ColPartition* part1 = ColPartition::FakePartition(box1, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); part_grid.InsertBBox(true, true, part1); TBOX box2(300, 920, 900, 940); ColPartition* part2 = ColPartition::FakePartition(box2, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); part_grid.InsertBBox(true, true, part2); TBOX box3(0, 900, 600, 910); ColPartition* part3 = ColPartition::FakePartition(box3, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); part_grid.InsertBBox(true, true, part3); TBOX box4(300, 890, 600, 899); ColPartition* part4 = ColPartition::FakePartition(box4, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); part_grid.InsertBBox(true, true, part4); TBOX box5(300, 500, 900, 510); ColPartition* part5 = ColPartition::FakePartition(box5, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); part_grid.InsertBBox(true, true, part5); // Test // part1 should be no indent. EXPECT_EQ(EquationDetect::NO_INDENT, equation_det_->RunIsIndented(&part_grid, part1)); // part2 should be left indent in terms of part1. EXPECT_EQ(EquationDetect::LEFT_INDENT, equation_det_->RunIsIndented(&part_grid, part2)); // part3 should be right indent. EXPECT_EQ(EquationDetect::RIGHT_INDENT, equation_det_->RunIsIndented(&part_grid, part3)); // part4 should be both indented. EXPECT_EQ(EquationDetect::BOTH_INDENT, equation_det_->RunIsIndented(&part_grid, part4)); // part5 should be no indent because it is too far from part1. EXPECT_EQ(EquationDetect::NO_INDENT, equation_det_->RunIsIndented(&part_grid, part5)); // Release memory. part1->DeleteBoxes(); delete (part1); part2->DeleteBoxes(); delete (part2); part3->DeleteBoxes(); delete (part3); part4->DeleteBoxes(); delete (part4); part5->DeleteBoxes(); delete (part5); } TEST_F(EquationFinderTest, IsNearSmallNeighbor) { // Create four tboxes: // part 1, part 2 // ***** ***** // part 3: ***** // // part 4: ***************** TBOX box1(0, 950, 499, 999); TBOX box2(500, 950, 999, 998); TBOX box3(0, 900, 499, 949); TBOX box4(0, 550, 499, 590); // Test // box2 should be box1's near neighbor but not vice versa. EXPECT_TRUE(equation_det_->RunIsNearSmallNeighbor(box1, box2)); EXPECT_FALSE(equation_det_->RunIsNearSmallNeighbor(box2, box1)); // box1 and box3 should be near neighbors of each other. EXPECT_TRUE(equation_det_->RunIsNearSmallNeighbor(box1, box3)); EXPECT_FALSE(equation_det_->RunIsNearSmallNeighbor(box2, box3)); // box2 and box3 should not be near neighbors of each other. EXPECT_FALSE(equation_det_->RunIsNearSmallNeighbor(box2, box3)); EXPECT_FALSE(equation_det_->RunIsNearSmallNeighbor(box3, box2)); // box4 should not be the near neighbor of any one. EXPECT_FALSE(equation_det_->RunIsNearSmallNeighbor(box1, box4)); EXPECT_FALSE(equation_det_->RunIsNearSmallNeighbor(box2, box4)); EXPECT_FALSE(equation_det_->RunIsNearSmallNeighbor(box3, box4)); } TEST_F(EquationFinderTest, CheckSeedBlobsCount) { TBOX box(0, 950, 999, 999); ColPartition* part1 = ColPartition::FakePartition(box, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); ColPartition* part2 = ColPartition::FakePartition(box, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); ColPartition* part3 = ColPartition::FakePartition(box, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); ColPartition* part4 = ColPartition::FakePartition(box, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); // Part 1: 8 math, 0 digit, 20 total. equation_det_->AddMathDigitBlobs(8, 0, 20, part1); EXPECT_TRUE(equation_det_->RunCheckSeedBlobsCount(part1)); // Part 2: 1 math, 8 digit, 20 total. equation_det_->AddMathDigitBlobs(1, 8, 20, part2); EXPECT_FALSE(equation_det_->RunCheckSeedBlobsCount(part2)); // Part 3: 3 math, 8 digit, 8 total. equation_det_->AddMathDigitBlobs(3, 8, 20, part3); EXPECT_TRUE(equation_det_->RunCheckSeedBlobsCount(part3)); // Part 4: 8 math, 0 digit, 8 total. equation_det_->AddMathDigitBlobs(0, 0, 8, part4); EXPECT_FALSE(equation_det_->RunCheckSeedBlobsCount(part4)); // Release memory. part1->DeleteBoxes(); delete (part1); part2->DeleteBoxes(); delete (part2); part3->DeleteBoxes(); delete (part3); part4->DeleteBoxes(); delete (part4); } TEST_F(EquationFinderTest, ComputeForegroundDensity) { // Create the pix with top half foreground, bottom half background. int width = 1024, height = 768; Pix* pix = pixCreate(width, height, 1); pixRasterop(pix, 0, 0, width, height / 2, PIX_SET, nullptr, 0, 0); TBOX box1(100, 0, 140, 140), box2(100, height / 2 - 20, 140, height / 2 + 20), box3(100, height - 40, 140, height); equation_det_->SetPixBinary(pix); // Verify EXPECT_NEAR(0.0, equation_det_->RunComputeForegroundDensity(box1), 0.0001f); EXPECT_NEAR(0.5, equation_det_->RunComputeForegroundDensity(box2), 0.0001f); EXPECT_NEAR(1.0, equation_det_->RunComputeForegroundDensity(box3), 0.0001f); } TEST_F(EquationFinderTest, CountAlignment) { GenericVector vec; vec.push_back(1); vec.push_back(1); vec.push_back(1); vec.push_back(100); vec.push_back(200); vec.push_back(200); // Test the right point. EXPECT_EQ(3, equation_det_->RunCountAlignment(vec, 1)); EXPECT_EQ(1, equation_det_->RunCountAlignment(vec, 100)); EXPECT_EQ(2, equation_det_->RunCountAlignment(vec, 200)); // Test the near neighbors. EXPECT_EQ(3, equation_det_->RunCountAlignment(vec, 3)); EXPECT_EQ(1, equation_det_->RunCountAlignment(vec, 99)); EXPECT_EQ(2, equation_det_->RunCountAlignment(vec, 202)); // Test the far neighbors. EXPECT_EQ(0, equation_det_->RunCountAlignment(vec, 150)); EXPECT_EQ(0, equation_det_->RunCountAlignment(vec, 50)); EXPECT_EQ(0, equation_det_->RunCountAlignment(vec, 250)); } TEST_F(EquationFinderTest, ComputeCPsSuperBBox) { Pix* pix = pixCreate(1001, 1001, 1); equation_det_->SetPixBinary(pix); ColPartitionGrid part_grid(10, ICOORD(0, 0), ICOORD(1000, 1000)); TBOX box1(0, 0, 999, 99); ColPartition* part1 = ColPartition::FakePartition(box1, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); TBOX box2(0, 100, 499, 199); ColPartition* part2 = ColPartition::FakePartition(box2, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); TBOX box3(500, 100, 999, 199); ColPartition* part3 = ColPartition::FakePartition(box3, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); TBOX box4(0, 200, 999, 299); ColPartition* part4 = ColPartition::FakePartition(box4, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); TBOX box5(0, 900, 999, 999); ColPartition* part5 = ColPartition::FakePartition(box5, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); // Add part1->part3 into part_grid and test. part_grid.InsertBBox(true, true, part1); part_grid.InsertBBox(true, true, part2); part_grid.InsertBBox(true, true, part3); TBOX super_box(0, 0, 999, 199); equation_det_->TestComputeCPsSuperBBox(super_box, &part_grid); // Add part4 and test. part_grid.InsertBBox(true, true, part4); TBOX super_box2(0, 0, 999, 299); equation_det_->TestComputeCPsSuperBBox(super_box2, &part_grid); // Add part5 and test. part_grid.InsertBBox(true, true, part5); TBOX super_box3(0, 0, 999, 999); equation_det_->TestComputeCPsSuperBBox(super_box3, &part_grid); // Release memory. part1->DeleteBoxes(); delete (part1); part2->DeleteBoxes(); delete (part2); part3->DeleteBoxes(); delete (part3); part4->DeleteBoxes(); delete (part4); part5->DeleteBoxes(); delete (part5); } TEST_F(EquationFinderTest, SplitCPHorLite) { TBOX box(0, 0, 999, 99); ColPartition* part = ColPartition::FakePartition(box, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); part->DeleteBoxes(); part->set_median_width(10); GenericVector splitted_boxes; // Test an empty part. equation_det_->RunSplitCPHorLite(part, &splitted_boxes); EXPECT_TRUE(splitted_boxes.empty()); // Test with one blob. AddBlobIntoPart(TBOX(0, 0, 10, 50), part); equation_det_->RunSplitCPHorLite(part, &splitted_boxes); EXPECT_EQ(1, splitted_boxes.size()); EXPECT_TRUE(TBOX(0, 0, 10, 50) == splitted_boxes[0]); // Add more blob and test. AddBlobIntoPart(TBOX(11, 0, 20, 60), part); AddBlobIntoPart(TBOX(25, 0, 30, 55), part); // break point. AddBlobIntoPart(TBOX(100, 0, 110, 15), part); AddBlobIntoPart(TBOX(125, 0, 140, 45), part); // break point. AddBlobIntoPart(TBOX(500, 0, 540, 35), part); // break point. equation_det_->RunSplitCPHorLite(part, &splitted_boxes); // Verify. EXPECT_EQ(3, splitted_boxes.size()); EXPECT_TRUE(TBOX(0, 0, 30, 60) == splitted_boxes[0]); EXPECT_TRUE(TBOX(100, 0, 140, 45) == splitted_boxes[1]); EXPECT_TRUE(TBOX(500, 0, 540, 35) == splitted_boxes[2]); part->DeleteBoxes(); delete (part); } TEST_F(EquationFinderTest, SplitCPHor) { TBOX box(0, 0, 999, 99); ColPartition* part = ColPartition::FakePartition(box, PT_FLOWING_TEXT, BRT_TEXT, BTFT_NONE); part->DeleteBoxes(); part->set_median_width(10); GenericVector parts_splitted; // Test an empty part. equation_det_->RunSplitCPHor(part, &parts_splitted); EXPECT_TRUE(parts_splitted.empty()); // Test with one blob. AddBlobIntoPart(TBOX(0, 0, 10, 50), part); equation_det_->RunSplitCPHor(part, &parts_splitted); EXPECT_EQ(1, parts_splitted.size()); EXPECT_TRUE(TBOX(0, 0, 10, 50) == parts_splitted[0]->bounding_box()); // Add more blob and test. AddBlobIntoPart(TBOX(11, 0, 20, 60), part); AddBlobIntoPart(TBOX(25, 0, 30, 55), part); // break point. AddBlobIntoPart(TBOX(100, 0, 110, 15), part); AddBlobIntoPart(TBOX(125, 0, 140, 45), part); // break point. AddBlobIntoPart(TBOX(500, 0, 540, 35), part); // break point. equation_det_->RunSplitCPHor(part, &parts_splitted); // Verify. EXPECT_EQ(3, parts_splitted.size()); EXPECT_TRUE(TBOX(0, 0, 30, 60) == parts_splitted[0]->bounding_box()); EXPECT_TRUE(TBOX(100, 0, 140, 45) == parts_splitted[1]->bounding_box()); EXPECT_TRUE(TBOX(500, 0, 540, 35) == parts_splitted[2]->bounding_box()); parts_splitted.delete_data_pointers(); part->DeleteBoxes(); delete (part); } } // namespace tesseract