Coverage Report

Created: 2026-09-14 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/ccstruct/boxword.cpp
Line
Count
Source
1
///////////////////////////////////////////////////////////////////////
2
// File:        boxword.cpp
3
// Description: Class to represent the bounding boxes of the output.
4
// Author:      Ray Smith
5
//
6
// (C) Copyright 2010, Google Inc.
7
// Licensed under the Apache License, Version 2.0 (the "License");
8
// you may not use this file except in compliance with the License.
9
// You may obtain a copy of the License at
10
// http://www.apache.org/licenses/LICENSE-2.0
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
///////////////////////////////////////////////////////////////////////
18
19
#include "boxword.h"
20
#include "blobs.h"
21
#include "host.h" // for NearlyEqual
22
#include "normalis.h"
23
#include "ocrblock.h"
24
#include "pageres.h"
25
26
namespace tesseract {
27
28
// Clip output boxes to input blob boxes for bounds that are within this
29
// tolerance. Otherwise, the blob may be chopped and we have to just use
30
// the word bounding box.
31
const int kBoxClipTolerance = 2;
32
33
692k
BoxWord::BoxWord() : length_(0) {}
34
35
27.7k
BoxWord::BoxWord(const BoxWord &src) {
36
27.7k
  CopyFrom(src);
37
27.7k
}
38
39
0
BoxWord &BoxWord::operator=(const BoxWord &src) {
40
0
  CopyFrom(src);
41
0
  return *this;
42
0
}
43
44
27.7k
void BoxWord::CopyFrom(const BoxWord &src) {
45
27.7k
  bbox_ = src.bbox_;
46
27.7k
  length_ = src.length_;
47
27.7k
  boxes_.clear();
48
27.7k
  boxes_.reserve(length_);
49
806k
  for (unsigned i = 0; i < length_; ++i) {
50
778k
    boxes_.push_back(src.boxes_[i]);
51
778k
  }
52
27.7k
}
53
54
// Factory to build a BoxWord from a TWERD using the DENORMs on each blob to
55
// switch back to original image coordinates.
56
571k
BoxWord *BoxWord::CopyFromNormalized(TWERD *tessword) {
57
571k
  auto *boxword = new BoxWord();
58
  // Count the blobs.
59
571k
  boxword->length_ = tessword->NumBlobs();
60
  // Allocate memory.
61
571k
  boxword->boxes_.reserve(boxword->length_);
62
63
3.98M
  for (unsigned b = 0; b < boxword->length_; ++b) {
64
3.41M
    TBLOB *tblob = tessword->blobs[b];
65
3.41M
    TBOX blob_box;
66
9.03M
    for (TESSLINE *outline = tblob->outlines; outline != nullptr;
67
5.62M
         outline = outline->next) {
68
5.62M
      EDGEPT *edgept = outline->loop;
69
      // Iterate over the edges.
70
26.7M
      do {
71
26.7M
        if (!edgept->IsHidden() || !edgept->prev->IsHidden()) {
72
26.7M
          ICOORD pos(edgept->pos.x, edgept->pos.y);
73
26.7M
          TPOINT denormed;
74
26.7M
          tblob->denorm().DenormTransform(nullptr, edgept->pos, &denormed);
75
26.7M
          pos.set_x(denormed.x);
76
26.7M
          pos.set_y(denormed.y);
77
26.7M
          TBOX pt_box(pos, pos);
78
26.7M
          blob_box += pt_box;
79
26.7M
        }
80
26.7M
        edgept = edgept->next;
81
26.7M
      } while (edgept != outline->loop);
82
5.62M
    }
83
3.41M
    boxword->boxes_.push_back(blob_box);
84
3.41M
  }
85
571k
  boxword->ComputeBoundingBox();
86
571k
  return boxword;
87
571k
}
88
89
// Clean up the bounding boxes from the polygonal approximation by
90
// expanding slightly, then clipping to the blobs from the original_word
91
// that overlap. If not null, the block provides the inverse rotation.
92
122k
void BoxWord::ClipToOriginalWord(const BLOCK *block, WERD *original_word) {
93
1.05M
  for (unsigned i = 0; i < length_; ++i) {
94
934k
    TBOX box = boxes_[i];
95
    // Expand by a single pixel, as the poly approximation error is 1 pixel.
96
934k
    box =
97
934k
        TBOX(box.left() - 1, box.bottom() - 1, box.right() + 1, box.top() + 1);
98
    // Now find the original box that matches.
99
934k
    TBOX original_box;
100
934k
    C_BLOB_IT b_it(original_word->cblob_list());
101
44.9M
    for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
102
44.0M
      TBOX blob_box = b_it.data()->bounding_box();
103
44.0M
      if (block != nullptr) {
104
44.0M
        blob_box.rotate(block->re_rotation());
105
44.0M
      }
106
44.0M
      if (blob_box.major_overlap(box)) {
107
1.42M
        original_box += blob_box;
108
1.42M
      }
109
44.0M
    }
110
934k
    if (!original_box.null_box()) {
111
934k
      if (NearlyEqual<int>(original_box.left(), box.left(),
112
934k
                           kBoxClipTolerance)) {
113
903k
        box.set_left(original_box.left());
114
903k
      }
115
934k
      if (NearlyEqual<int>(original_box.right(), box.right(),
116
934k
                           kBoxClipTolerance)) {
117
887k
        box.set_right(original_box.right());
118
887k
      }
119
934k
      if (NearlyEqual<int>(original_box.top(), box.top(), kBoxClipTolerance)) {
120
828k
        box.set_top(original_box.top());
121
828k
      }
122
934k
      if (NearlyEqual<int>(original_box.bottom(), box.bottom(),
123
934k
                           kBoxClipTolerance)) {
124
814k
        box.set_bottom(original_box.bottom());
125
814k
      }
126
934k
    }
127
934k
    original_box = original_word->bounding_box();
128
934k
    if (block != nullptr) {
129
934k
      original_box.rotate(block->re_rotation());
130
934k
    }
131
934k
    boxes_[i] = box.intersection(original_box);
132
934k
  }
133
122k
  ComputeBoundingBox();
134
122k
}
135
136
// Merges the boxes from start to end, not including end, and deletes
137
// the boxes between start and end.
138
1.51k
void BoxWord::MergeBoxes(unsigned start, unsigned end) {
139
1.51k
  start = ClipToRange(start, 0U, length_);
140
1.51k
  end = ClipToRange(end, 0U, length_);
141
1.51k
  if (end <= start + 1) {
142
0
    return;
143
0
  }
144
3.03k
  for (unsigned i = start + 1; i < end; ++i) {
145
1.51k
    boxes_[start] += boxes_[i];
146
1.51k
  }
147
1.51k
  int shrinkage = end - 1 - start;
148
1.51k
  length_ -= shrinkage;
149
38.6k
  for (unsigned i = start + 1; i < length_; ++i) {
150
37.0k
    boxes_[i] = boxes_[i + shrinkage];
151
37.0k
  }
152
1.51k
  boxes_.resize(length_);
153
1.51k
}
154
155
// Inserts a new box before the given index.
156
// Recomputes the bounding box.
157
162k
void BoxWord::InsertBox(unsigned index, const TBOX &box) {
158
162k
  if (index < length_) {
159
0
    boxes_.insert(boxes_.begin() + index, box);
160
162k
  } else {
161
162k
    boxes_.push_back(box);
162
162k
  }
163
162k
  length_ = boxes_.size();
164
162k
  ComputeBoundingBox();
165
162k
}
166
167
// Changes the box at the given index to the new box.
168
// Recomputes the bounding box.
169
0
void BoxWord::ChangeBox(unsigned index, const TBOX &box) {
170
0
  boxes_[index] = box;
171
0
  ComputeBoundingBox();
172
0
}
173
174
// Deletes the box with the given index, and shuffles up the rest.
175
// Recomputes the bounding box.
176
0
void BoxWord::DeleteBox(unsigned index) {
177
0
  ASSERT_HOST(index < length_);
178
0
  boxes_.erase(boxes_.begin() + index);
179
0
  --length_;
180
0
  ComputeBoundingBox();
181
0
}
182
183
// Deletes all the boxes stored in BoxWord.
184
0
void BoxWord::DeleteAllBoxes() {
185
0
  length_ = 0;
186
0
  boxes_.clear();
187
0
  bbox_ = TBOX();
188
0
}
189
190
// Computes the bounding box of the word.
191
856k
void BoxWord::ComputeBoundingBox() {
192
856k
  bbox_ = TBOX();
193
5.47M
  for (unsigned i = 0; i < length_; ++i) {
194
4.61M
    bbox_ += boxes_[i];
195
4.61M
  }
196
856k
}
197
198
// This and other putatively are the same, so call the (permanent) callback
199
// for each blob index where the bounding boxes match.
200
// The callback is deleted on completion.
201
void BoxWord::ProcessMatchedBlobs(const TWERD &other,
202
0
                                  const std::function<void(int)> &cb) const {
203
0
  for (unsigned i = 0; i < length_ && i < other.NumBlobs(); ++i) {
204
0
    TBOX blob_box = other.blobs[i]->bounding_box();
205
0
    if (blob_box == boxes_[i]) {
206
0
      cb(i);
207
0
    }
208
0
  }
209
0
}
210
211
} // namespace tesseract.