/src/tesseract/src/classify/intproto.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | ** Filename: intproto.c |
3 | | ** Purpose: Definition of data structures for integer protos. |
4 | | ** Author: Dan Johnson |
5 | | ** |
6 | | ** (c) Copyright Hewlett-Packard Company, 1988. |
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 | | Include Files and Type Defines |
19 | | -----------------------------------------------------------------------------*/ |
20 | | |
21 | | #define _USE_MATH_DEFINES // for M_PI |
22 | | |
23 | | // Include automatically generated configuration file if running autoconf. |
24 | | #ifdef HAVE_CONFIG_H |
25 | | # include "config_auto.h" |
26 | | #endif |
27 | | |
28 | | #include "intproto.h" |
29 | | |
30 | | #include "classify.h" |
31 | | #include "fontinfo.h" |
32 | | #include "mfoutline.h" |
33 | | #include "picofeat.h" |
34 | | #include "points.h" |
35 | | #include "shapetable.h" |
36 | | #ifndef GRAPHICS_DISABLED |
37 | | #include "svmnode.h" |
38 | | #endif |
39 | | |
40 | | #include "helpers.h" |
41 | | |
42 | | #include <algorithm> |
43 | | #include <cassert> |
44 | | #include <cmath> // for M_PI, std::floor |
45 | | #include <cstdio> |
46 | | |
47 | | namespace tesseract { |
48 | | |
49 | | /* match debug display constants*/ |
50 | | #define PROTO_PRUNER_SCALE (4.0) |
51 | | |
52 | | #define INT_DESCENDER (0.0 * INT_CHAR_NORM_RANGE) |
53 | | #define INT_BASELINE (0.25 * INT_CHAR_NORM_RANGE) |
54 | | #define INT_XHEIGHT (0.75 * INT_CHAR_NORM_RANGE) |
55 | | #define INT_CAPHEIGHT (1.0 * INT_CHAR_NORM_RANGE) |
56 | | |
57 | | #define INT_XCENTER (0.5 * INT_CHAR_NORM_RANGE) |
58 | | #define INT_YCENTER (0.5 * INT_CHAR_NORM_RANGE) |
59 | | #define INT_XRADIUS (0.2 * INT_CHAR_NORM_RANGE) |
60 | | #define INT_YRADIUS (0.2 * INT_CHAR_NORM_RANGE) |
61 | | #define INT_MIN_X 0 |
62 | | #define INT_MIN_Y 0 |
63 | | #define INT_MAX_X INT_CHAR_NORM_RANGE |
64 | | #define INT_MAX_Y INT_CHAR_NORM_RANGE |
65 | | |
66 | | /** define pad used to snap near horiz/vertical protos to horiz/vertical */ |
67 | 9.77k | #define HV_TOLERANCE (0.0025) /* approx 0.9 degrees */ |
68 | | |
69 | | typedef enum { StartSwitch, EndSwitch, LastSwitch } SWITCH_TYPE; |
70 | | #define MAX_NUM_SWITCHES 3 |
71 | | |
72 | | struct FILL_SWITCH { |
73 | | SWITCH_TYPE Type; |
74 | | int8_t X, Y; |
75 | | int16_t YInit; |
76 | | int16_t Delta; |
77 | | }; |
78 | | |
79 | | struct TABLE_FILLER { |
80 | | uint8_t NextSwitch; |
81 | | uint8_t AngleStart, AngleEnd; |
82 | | int8_t X; |
83 | | int16_t YStart, YEnd; |
84 | | int16_t StartDelta, EndDelta; |
85 | | FILL_SWITCH Switch[MAX_NUM_SWITCHES]; |
86 | | }; |
87 | | |
88 | | struct FILL_SPEC { |
89 | | int8_t X; |
90 | | int8_t YStart, YEnd; |
91 | | uint8_t AngleStart, AngleEnd; |
92 | | }; |
93 | | |
94 | | /* constants for conversion from old inttemp format */ |
95 | 0 | #define OLD_MAX_NUM_CONFIGS 32 |
96 | 0 | #define OLD_WERDS_PER_CONFIG_VEC ((OLD_MAX_NUM_CONFIGS + BITS_PER_WERD - 1) / BITS_PER_WERD) |
97 | | |
98 | | /*----------------------------------------------------------------------------- |
99 | | Macros |
100 | | -----------------------------------------------------------------------------*/ |
101 | | /** macro for performing circular increments of bucket indices */ |
102 | 410k | #define CircularIncrement(i, r) (((i) < (r)-1) ? ((i)++) : ((i) = 0)) |
103 | | |
104 | | /** macro for mapping floats to ints without bounds checking */ |
105 | 190k | #define MapParam(P, O, N) (std::floor(((P) + (O)) * (N))) |
106 | | |
107 | | /*--------------------------------------------------------------------------- |
108 | | Private Function Prototypes |
109 | | ----------------------------------------------------------------------------*/ |
110 | | float BucketStart(int Bucket, float Offset, int NumBuckets); |
111 | | |
112 | | float BucketEnd(int Bucket, float Offset, int NumBuckets); |
113 | | |
114 | | void DoFill(FILL_SPEC *FillSpec, CLASS_PRUNER_STRUCT *Pruner, uint32_t ClassMask, |
115 | | uint32_t ClassCount, uint32_t WordIndex); |
116 | | |
117 | | bool FillerDone(TABLE_FILLER *Filler); |
118 | | |
119 | | void FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit, |
120 | | float Center, float Spread, bool debug); |
121 | | |
122 | | void FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit, |
123 | | float Center, float Spread, bool debug); |
124 | | |
125 | | void GetCPPadsForLevel(int Level, float *EndPad, float *SidePad, float *AnglePad); |
126 | | |
127 | | ScrollView::Color GetMatchColorFor(float Evidence); |
128 | | |
129 | | void GetNextFill(TABLE_FILLER *Filler, FILL_SPEC *Fill); |
130 | | |
131 | | void InitTableFiller(float EndPad, float SidePad, float AnglePad, PROTO_STRUCT *Proto, |
132 | | TABLE_FILLER *Filler); |
133 | | |
134 | | #ifndef GRAPHICS_DISABLED |
135 | | void RenderIntFeature(ScrollView *window, const INT_FEATURE_STRUCT *Feature, |
136 | | ScrollView::Color color); |
137 | | |
138 | | void RenderIntProto(ScrollView *window, INT_CLASS_STRUCT *Class, PROTO_ID ProtoId, ScrollView::Color color); |
139 | | #endif // !GRAPHICS_DISABLED |
140 | | |
141 | | /*----------------------------------------------------------------------------- |
142 | | Global Data Definitions and Declarations |
143 | | -----------------------------------------------------------------------------*/ |
144 | | |
145 | | #ifndef GRAPHICS_DISABLED |
146 | | /* global display lists used to display proto and feature match information*/ |
147 | | static ScrollView *IntMatchWindow = nullptr; |
148 | | static ScrollView *FeatureDisplayWindow = nullptr; |
149 | | static ScrollView *ProtoDisplayWindow = nullptr; |
150 | | #endif |
151 | | |
152 | | /*----------------------------------------------------------------------------- |
153 | | Variables |
154 | | -----------------------------------------------------------------------------*/ |
155 | | |
156 | | /* control knobs */ |
157 | | static INT_VAR(classify_num_cp_levels, 3, "Number of Class Pruner Levels"); |
158 | | static double_VAR(classify_cp_angle_pad_loose, 45.0, "Class Pruner Angle Pad Loose"); |
159 | | static double_VAR(classify_cp_angle_pad_medium, 20.0, "Class Pruner Angle Pad Medium"); |
160 | | static double_VAR(classify_cp_angle_pad_tight, 10.0, "CLass Pruner Angle Pad Tight"); |
161 | | static double_VAR(classify_cp_end_pad_loose, 0.5, "Class Pruner End Pad Loose"); |
162 | | static double_VAR(classify_cp_end_pad_medium, 0.5, "Class Pruner End Pad Medium"); |
163 | | static double_VAR(classify_cp_end_pad_tight, 0.5, "Class Pruner End Pad Tight"); |
164 | | static double_VAR(classify_cp_side_pad_loose, 2.5, "Class Pruner Side Pad Loose"); |
165 | | static double_VAR(classify_cp_side_pad_medium, 1.2, "Class Pruner Side Pad Medium"); |
166 | | static double_VAR(classify_cp_side_pad_tight, 0.6, "Class Pruner Side Pad Tight"); |
167 | | static double_VAR(classify_pp_angle_pad, 45.0, "Proto Pruner Angle Pad"); |
168 | | static double_VAR(classify_pp_end_pad, 0.5, "Proto Prune End Pad"); |
169 | | static double_VAR(classify_pp_side_pad, 2.5, "Proto Pruner Side Pad"); |
170 | | |
171 | | /** |
172 | | * This routine truncates Param to lie within the range |
173 | | * of Min-Max inclusive. |
174 | | * |
175 | | * @param Param parameter value to be truncated |
176 | | * @param Min, Max parameter limits (inclusive) |
177 | | * |
178 | | * @return Truncated parameter. |
179 | | */ |
180 | 47.6k | static int TruncateParam(float Param, int Min, int Max) { |
181 | 47.6k | int result; |
182 | 47.6k | if (Param < Min) { |
183 | 1.11k | result = Min; |
184 | 46.5k | } else if (Param > Max) { |
185 | 7.21k | result = Max; |
186 | 39.3k | } else { |
187 | 39.3k | result = static_cast<int>(std::floor(Param)); |
188 | 39.3k | } |
189 | 47.6k | return result; |
190 | 47.6k | } |
191 | | |
192 | | /*----------------------------------------------------------------------------- |
193 | | Public Code |
194 | | -----------------------------------------------------------------------------*/ |
195 | | /// Builds a feature from an FCOORD for position with all the necessary |
196 | | /// clipping and rounding. |
197 | | INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(const FCOORD &pos, uint8_t theta) |
198 | 228M | : X(ClipToRange<int16_t>(static_cast<int16_t>(pos.x() + 0.5), 0, 255)) |
199 | 228M | , Y(ClipToRange<int16_t>(static_cast<int16_t>(pos.y() + 0.5), 0, 255)) |
200 | 228M | , Theta(theta) |
201 | 228M | , CP_misses(0) {} |
202 | | /** Builds a feature from ints with all the necessary clipping and casting. */ |
203 | | INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(int x, int y, int theta) |
204 | 0 | : X(static_cast<uint8_t>(ClipToRange<int>(x, 0, UINT8_MAX))) |
205 | 0 | , Y(static_cast<uint8_t>(ClipToRange<int>(y, 0, UINT8_MAX))) |
206 | 0 | , Theta(static_cast<uint8_t>(ClipToRange<int>(theta, 0, UINT8_MAX))) |
207 | 0 | , CP_misses(0) {} |
208 | | |
209 | | /** |
210 | | * This routine adds a new class structure to a set of |
211 | | * templates. Classes have to be added to Templates in |
212 | | * the order of increasing ClassIds. |
213 | | * |
214 | | * @param Templates templates to add new class to |
215 | | * @param ClassId class id to associate new class with |
216 | | * @param Class class data structure to add to templates |
217 | | * |
218 | | * Globals: none |
219 | | */ |
220 | 3.21M | void AddIntClass(INT_TEMPLATES_STRUCT *Templates, CLASS_ID ClassId, INT_CLASS_STRUCT *Class) { |
221 | 3.21M | int Pruner; |
222 | | |
223 | 3.21M | assert(LegalClassId(ClassId)); |
224 | 3.21M | if (static_cast<unsigned>(ClassId) != Templates->NumClasses) { |
225 | 0 | fprintf(stderr, |
226 | 0 | "Please make sure that classes are added to templates" |
227 | 0 | " in increasing order of ClassIds\n"); |
228 | 0 | exit(1); |
229 | 0 | } |
230 | 3.21M | ClassForClassId(Templates, ClassId) = Class; |
231 | 3.21M | Templates->NumClasses++; |
232 | | |
233 | 3.21M | if (Templates->NumClasses > MaxNumClassesIn(Templates)) { |
234 | 100k | Pruner = Templates->NumClassPruners++; |
235 | 100k | Templates->ClassPruners[Pruner] = new CLASS_PRUNER_STRUCT; |
236 | 100k | memset(Templates->ClassPruners[Pruner], 0, sizeof(CLASS_PRUNER_STRUCT)); |
237 | 100k | } |
238 | 3.21M | } /* AddIntClass */ |
239 | | |
240 | | /** |
241 | | * This routine returns the index of the next free config |
242 | | * in Class. |
243 | | * |
244 | | * @param Class class to add new configuration to |
245 | | * |
246 | | * Globals: none |
247 | | * |
248 | | * @return Index of next free config. |
249 | | */ |
250 | 1.08k | int AddIntConfig(INT_CLASS_STRUCT *Class) { |
251 | 1.08k | int Index; |
252 | | |
253 | 1.08k | assert(Class->NumConfigs < MAX_NUM_CONFIGS); |
254 | | |
255 | 1.08k | Index = Class->NumConfigs++; |
256 | 1.08k | Class->ConfigLengths[Index] = 0; |
257 | 1.08k | return Index; |
258 | 1.08k | } /* AddIntConfig */ |
259 | | |
260 | | /** |
261 | | * This routine allocates the next free proto in Class and |
262 | | * returns its index. |
263 | | * |
264 | | * @param Class class to add new proto to |
265 | | * |
266 | | * Globals: none |
267 | | * |
268 | | * @return Proto index of new proto. |
269 | | */ |
270 | 11.9k | int AddIntProto(INT_CLASS_STRUCT *Class) { |
271 | 11.9k | if (Class->NumProtos >= MAX_NUM_PROTOS) { |
272 | 2 | return (NO_PROTO); |
273 | 2 | } |
274 | | |
275 | 11.9k | int Index = Class->NumProtos++; |
276 | | |
277 | 11.9k | if (Class->NumProtos > MaxNumIntProtosIn(Class)) { |
278 | 37 | int ProtoSetId = Class->NumProtoSets++; |
279 | 37 | auto ProtoSet = new PROTO_SET_STRUCT; |
280 | 37 | Class->ProtoSets[ProtoSetId] = ProtoSet; |
281 | 37 | memset(ProtoSet, 0, sizeof(*ProtoSet)); |
282 | | |
283 | | /* reallocate space for the proto lengths and install in class */ |
284 | 37 | Class->ProtoLengths.resize(MaxNumIntProtosIn(Class)); |
285 | 37 | } |
286 | | |
287 | | /* initialize proto so its length is zero and it isn't in any configs */ |
288 | 11.9k | Class->ProtoLengths[Index] = 0; |
289 | 11.9k | auto Proto = ProtoForProtoId(Class, Index); |
290 | 35.7k | for (uint32_t *Word = Proto->Configs; Word < Proto->Configs + WERDS_PER_CONFIG_VEC; *Word++ = 0) { |
291 | 23.8k | } |
292 | | |
293 | 11.9k | return (Index); |
294 | 11.9k | } |
295 | | |
296 | | /** |
297 | | * This routine adds Proto to the class pruning tables |
298 | | * for the specified class in Templates. |
299 | | * |
300 | | * Globals: |
301 | | * - classify_num_cp_levels number of levels used in the class pruner |
302 | | * @param Proto floating-pt proto to add to class pruner |
303 | | * @param ClassId class id corresponding to Proto |
304 | | * @param Templates set of templates containing class pruner |
305 | | */ |
306 | | void AddProtoToClassPruner(PROTO_STRUCT *Proto, CLASS_ID ClassId, INT_TEMPLATES_STRUCT *Templates) |
307 | | #define MAX_LEVEL 2 |
308 | 659 | { |
309 | 659 | CLASS_PRUNER_STRUCT *Pruner; |
310 | 659 | uint32_t ClassMask; |
311 | 659 | uint32_t ClassCount; |
312 | 659 | uint32_t WordIndex; |
313 | 659 | int Level; |
314 | 659 | float EndPad, SidePad, AnglePad; |
315 | 659 | TABLE_FILLER TableFiller; |
316 | 659 | FILL_SPEC FillSpec; |
317 | | |
318 | 659 | Pruner = CPrunerFor(Templates, ClassId); |
319 | 659 | WordIndex = CPrunerWordIndexFor(ClassId); |
320 | 659 | ClassMask = CPrunerMaskFor(MAX_LEVEL, ClassId); |
321 | | |
322 | 2.63k | for (Level = classify_num_cp_levels - 1; Level >= 0; Level--) { |
323 | 1.97k | GetCPPadsForLevel(Level, &EndPad, &SidePad, &AnglePad); |
324 | 1.97k | ClassCount = CPrunerMaskFor(Level, ClassId); |
325 | 1.97k | InitTableFiller(EndPad, SidePad, AnglePad, Proto, &TableFiller); |
326 | | |
327 | 11.2k | while (!FillerDone(&TableFiller)) { |
328 | 9.26k | GetNextFill(&TableFiller, &FillSpec); |
329 | 9.26k | DoFill(&FillSpec, Pruner, ClassMask, ClassCount, WordIndex); |
330 | 9.26k | } |
331 | 1.97k | } |
332 | 659 | } /* AddProtoToClassPruner */ |
333 | | |
334 | | /** |
335 | | * This routine updates the proto pruner lookup tables |
336 | | * for Class to include a new proto identified by ProtoId |
337 | | * and described by Proto. |
338 | | * @param Proto floating-pt proto to be added to proto pruner |
339 | | * @param ProtoId id of proto |
340 | | * @param Class integer class that contains desired proto pruner |
341 | | * @param debug debug flag |
342 | | * @note Globals: none |
343 | | */ |
344 | 11.9k | void AddProtoToProtoPruner(PROTO_STRUCT *Proto, int ProtoId, INT_CLASS_STRUCT *Class, bool debug) { |
345 | 11.9k | float X, Y, Length; |
346 | 11.9k | float Pad; |
347 | | |
348 | 11.9k | if (ProtoId >= Class->NumProtos) { |
349 | 0 | tprintf("AddProtoToProtoPruner:assert failed: %d < %d", ProtoId, Class->NumProtos); |
350 | 0 | } |
351 | 11.9k | assert(ProtoId < Class->NumProtos); |
352 | | |
353 | 11.9k | int Index = IndexForProto(ProtoId); |
354 | 11.9k | auto ProtoSet = Class->ProtoSets[SetForProto(ProtoId)]; |
355 | | |
356 | 11.9k | float Angle = Proto->Angle; |
357 | 11.9k | #ifndef _WIN32 |
358 | 11.9k | assert(!std::isnan(Angle)); |
359 | 11.9k | #endif |
360 | | |
361 | 11.9k | FillPPCircularBits(ProtoSet->ProtoPruner[PRUNER_ANGLE], Index, Angle + ANGLE_SHIFT, |
362 | 11.9k | classify_pp_angle_pad / 360.0, debug); |
363 | | |
364 | 11.9k | Angle *= 2.0 * M_PI; |
365 | 11.9k | Length = Proto->Length; |
366 | | |
367 | 11.9k | X = Proto->X + X_SHIFT; |
368 | 11.9k | Pad = std::max(fabs(std::cos(Angle)) * (Length / 2.0 + classify_pp_end_pad * GetPicoFeatureLength()), |
369 | 11.9k | fabs(std::sin(Angle)) * (classify_pp_side_pad * GetPicoFeatureLength())); |
370 | | |
371 | 11.9k | FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_X], Index, X, Pad, debug); |
372 | | |
373 | 11.9k | Y = Proto->Y + Y_SHIFT; |
374 | 11.9k | Pad = std::max(fabs(std::sin(Angle)) * (Length / 2.0 + classify_pp_end_pad * GetPicoFeatureLength()), |
375 | 11.9k | fabs(std::cos(Angle)) * (classify_pp_side_pad * GetPicoFeatureLength())); |
376 | | |
377 | 11.9k | FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_Y], Index, Y, Pad, debug); |
378 | 11.9k | } /* AddProtoToProtoPruner */ |
379 | | |
380 | | /** |
381 | | * Returns a quantized bucket for the given param shifted by offset, |
382 | | * notionally (param + offset) * num_buckets, but clipped and casted to the |
383 | | * appropriate type. |
384 | | */ |
385 | 122k | uint8_t Bucket8For(float param, float offset, int num_buckets) { |
386 | 122k | int bucket = IntCastRounded(MapParam(param, offset, num_buckets)); |
387 | 122k | return static_cast<uint8_t>(ClipToRange<int>(bucket, 0, num_buckets - 1)); |
388 | 122k | } |
389 | 5.40k | uint16_t Bucket16For(float param, float offset, int num_buckets) { |
390 | 5.40k | int bucket = IntCastRounded(MapParam(param, offset, num_buckets)); |
391 | 5.40k | return static_cast<uint16_t>(ClipToRange<int>(bucket, 0, num_buckets - 1)); |
392 | 5.40k | } |
393 | | |
394 | | /** |
395 | | * Returns a quantized bucket for the given circular param shifted by offset, |
396 | | * notionally (param + offset) * num_buckets, but modded and casted to the |
397 | | * appropriate type. |
398 | | */ |
399 | 61.9k | uint8_t CircBucketFor(float param, float offset, int num_buckets) { |
400 | 61.9k | int bucket = IntCastRounded(MapParam(param, offset, num_buckets)); |
401 | 61.9k | return static_cast<uint8_t>(Modulo(bucket, num_buckets)); |
402 | 61.9k | } /* CircBucketFor */ |
403 | | |
404 | | #ifndef GRAPHICS_DISABLED |
405 | | /** |
406 | | * This routine clears the global feature and proto |
407 | | * display lists. |
408 | | * |
409 | | * Globals: |
410 | | * - FeatureShapes display list for features |
411 | | * - ProtoShapes display list for protos |
412 | | */ |
413 | | void UpdateMatchDisplay() { |
414 | | if (IntMatchWindow != nullptr) { |
415 | | IntMatchWindow->Update(); |
416 | | } |
417 | | } /* ClearMatchDisplay */ |
418 | | #endif |
419 | | |
420 | | /** |
421 | | * This operation updates the config vectors of all protos |
422 | | * in Class to indicate that the protos with 1's in Config |
423 | | * belong to a new configuration identified by ConfigId. |
424 | | * It is assumed that the length of the Config bit vector is |
425 | | * equal to the number of protos in Class. |
426 | | * @param Config config to be added to class |
427 | | * @param ConfigId id to be used for new config |
428 | | * @param Class class to add new config to |
429 | | */ |
430 | 1.08k | void ConvertConfig(BIT_VECTOR Config, int ConfigId, INT_CLASS_STRUCT *Class) { |
431 | 1.08k | int ProtoId; |
432 | 1.08k | INT_PROTO_STRUCT *Proto; |
433 | 1.08k | int TotalLength; |
434 | | |
435 | 38.1k | for (ProtoId = 0, TotalLength = 0; ProtoId < Class->NumProtos; ProtoId++) { |
436 | 37.0k | if (test_bit(Config, ProtoId)) { |
437 | 16.4k | Proto = ProtoForProtoId(Class, ProtoId); |
438 | 16.4k | SET_BIT(Proto->Configs, ConfigId); |
439 | 16.4k | TotalLength += Class->ProtoLengths[ProtoId]; |
440 | 16.4k | } |
441 | 37.0k | } |
442 | 1.08k | Class->ConfigLengths[ConfigId] = TotalLength; |
443 | 1.08k | } /* ConvertConfig */ |
444 | | |
445 | | /** |
446 | | * This routine converts Proto to integer format and |
447 | | * installs it as ProtoId in Class. |
448 | | * @param Proto floating-pt proto to be converted to integer format |
449 | | * @param ProtoId id of proto |
450 | | * @param Class integer class to add converted proto to |
451 | | */ |
452 | 11.9k | void Classify::ConvertProto(PROTO_STRUCT *Proto, int ProtoId, INT_CLASS_STRUCT *Class) { |
453 | 11.9k | assert(ProtoId < Class->NumProtos); |
454 | | |
455 | 11.9k | INT_PROTO_STRUCT *P = ProtoForProtoId(Class, ProtoId); |
456 | | |
457 | 11.9k | float Param = Proto->A * 128; |
458 | 11.9k | P->A = TruncateParam(Param, -128, 127); |
459 | | |
460 | 11.9k | Param = -Proto->B * 256; |
461 | 11.9k | P->B = TruncateParam(Param, 0, 255); |
462 | | |
463 | 11.9k | Param = Proto->C * 128; |
464 | 11.9k | P->C = TruncateParam(Param, -128, 127); |
465 | | |
466 | 11.9k | Param = Proto->Angle * 256; |
467 | 11.9k | if (Param < 0 || Param >= 256) { |
468 | 0 | P->Angle = 0; |
469 | 11.9k | } else { |
470 | 11.9k | P->Angle = static_cast<uint8_t>(Param); |
471 | 11.9k | } |
472 | | |
473 | | /* round proto length to nearest integer number of pico-features */ |
474 | 11.9k | Param = (Proto->Length / GetPicoFeatureLength()) + 0.5; |
475 | 11.9k | Class->ProtoLengths[ProtoId] = TruncateParam(Param, 1, 255); |
476 | 11.9k | if (classify_learning_debug_level >= 2) { |
477 | 0 | tprintf("Converted ffeat to (A=%d,B=%d,C=%d,L=%d)", P->A, P->B, P->C, |
478 | 0 | Class->ProtoLengths[ProtoId]); |
479 | 0 | } |
480 | 11.9k | } /* ConvertProto */ |
481 | | |
482 | | /** |
483 | | * This routine converts from the old floating point format |
484 | | * to the new integer format. |
485 | | * @param FloatProtos prototypes in old floating pt format |
486 | | * @param target_unicharset the UNICHARSET to use |
487 | | * @return New set of training templates in integer format. |
488 | | * @note Globals: none |
489 | | */ |
490 | | INT_TEMPLATES_STRUCT *Classify::CreateIntTemplates(CLASSES FloatProtos, |
491 | 0 | const UNICHARSET &target_unicharset) { |
492 | 0 | CLASS_TYPE FClass; |
493 | 0 | INT_CLASS_STRUCT *IClass; |
494 | 0 | int ProtoId; |
495 | 0 | int ConfigId; |
496 | |
|
497 | 0 | auto IntTemplates = new INT_TEMPLATES_STRUCT; |
498 | |
|
499 | 0 | for (unsigned ClassId = 0; ClassId < target_unicharset.size(); ClassId++) { |
500 | 0 | FClass = &(FloatProtos[ClassId]); |
501 | 0 | if (FClass->NumProtos == 0 && FClass->NumConfigs == 0 && |
502 | 0 | strcmp(target_unicharset.id_to_unichar(ClassId), " ") != 0) { |
503 | 0 | tprintf("Warning: no protos/configs for %s in CreateIntTemplates()\n", |
504 | 0 | target_unicharset.id_to_unichar(ClassId)); |
505 | 0 | } |
506 | 0 | assert(UnusedClassIdIn(IntTemplates, ClassId)); |
507 | 0 | IClass = new INT_CLASS_STRUCT(FClass->NumProtos, FClass->NumConfigs); |
508 | 0 | unsigned fs_size = FClass->font_set.size(); |
509 | 0 | FontSet fs; |
510 | 0 | fs.reserve(fs_size); |
511 | 0 | for (unsigned i = 0; i < fs_size; ++i) { |
512 | 0 | fs.push_back(FClass->font_set[i]); |
513 | 0 | } |
514 | 0 | IClass->font_set_id = this->fontset_table_.push_back(std::move(fs)); |
515 | 0 | AddIntClass(IntTemplates, ClassId, IClass); |
516 | |
|
517 | 0 | for (ProtoId = 0; ProtoId < FClass->NumProtos; ProtoId++) { |
518 | 0 | AddIntProto(IClass); |
519 | 0 | ConvertProto(ProtoIn(FClass, ProtoId), ProtoId, IClass); |
520 | 0 | AddProtoToProtoPruner(ProtoIn(FClass, ProtoId), ProtoId, IClass, |
521 | 0 | classify_learning_debug_level >= 2); |
522 | 0 | AddProtoToClassPruner(ProtoIn(FClass, ProtoId), ClassId, IntTemplates); |
523 | 0 | } |
524 | |
|
525 | 0 | for (ConfigId = 0; ConfigId < FClass->NumConfigs; ConfigId++) { |
526 | 0 | AddIntConfig(IClass); |
527 | 0 | ConvertConfig(FClass->Configurations[ConfigId], ConfigId, IClass); |
528 | 0 | } |
529 | 0 | } |
530 | 0 | return (IntTemplates); |
531 | 0 | } /* CreateIntTemplates */ |
532 | | |
533 | | #ifndef GRAPHICS_DISABLED |
534 | | /** |
535 | | * This routine renders the specified feature into a |
536 | | * global display list. |
537 | | * |
538 | | * Globals: |
539 | | * - FeatureShapes global display list for features |
540 | | * @param Feature pico-feature to be displayed |
541 | | * @param Evidence best evidence for this feature (0-1) |
542 | | */ |
543 | | void DisplayIntFeature(const INT_FEATURE_STRUCT *Feature, float Evidence) { |
544 | | ScrollView::Color color = GetMatchColorFor(Evidence); |
545 | | RenderIntFeature(IntMatchWindow, Feature, color); |
546 | | if (FeatureDisplayWindow) { |
547 | | RenderIntFeature(FeatureDisplayWindow, Feature, color); |
548 | | } |
549 | | } /* DisplayIntFeature */ |
550 | | |
551 | | /** |
552 | | * This routine renders the specified proto into a |
553 | | * global display list. |
554 | | * |
555 | | * Globals: |
556 | | * - ProtoShapes global display list for protos |
557 | | * @param Class class to take proto from |
558 | | * @param ProtoId id of proto in Class to be displayed |
559 | | * @param Evidence total evidence for proto (0-1) |
560 | | */ |
561 | | void DisplayIntProto(INT_CLASS_STRUCT *Class, PROTO_ID ProtoId, float Evidence) { |
562 | | ScrollView::Color color = GetMatchColorFor(Evidence); |
563 | | RenderIntProto(IntMatchWindow, Class, ProtoId, color); |
564 | | if (ProtoDisplayWindow) { |
565 | | RenderIntProto(ProtoDisplayWindow, Class, ProtoId, color); |
566 | | } |
567 | | } /* DisplayIntProto */ |
568 | | #endif |
569 | | |
570 | | /// This constructor creates a new integer class data structure |
571 | | /// and returns it. Sufficient space is allocated |
572 | | /// to handle the specified number of protos and configs. |
573 | | /// @param MaxNumProtos number of protos to allocate space for |
574 | | /// @param MaxNumConfigs number of configs to allocate space for |
575 | | INT_CLASS_STRUCT::INT_CLASS_STRUCT(int MaxNumProtos, int MaxNumConfigs) : |
576 | 3.21M | NumProtos(0), |
577 | 3.21M | NumProtoSets((MaxNumProtos + PROTOS_PER_PROTO_SET - 1) / PROTOS_PER_PROTO_SET), |
578 | 3.21M | NumConfigs(0), |
579 | 3.21M | ProtoLengths(MaxNumIntProtosIn(this)) |
580 | 3.21M | { |
581 | 3.21M | assert(MaxNumConfigs <= MAX_NUM_CONFIGS); |
582 | 3.21M | assert(NumProtoSets <= MAX_NUM_PROTO_SETS); |
583 | | |
584 | 6.43M | for (int i = 0; i < NumProtoSets; i++) { |
585 | | /* allocate space for a proto set, install in class, and initialize */ |
586 | 3.21M | auto ProtoSet = new PROTO_SET_STRUCT; |
587 | 3.21M | memset(ProtoSet, 0, sizeof(*ProtoSet)); |
588 | 3.21M | ProtoSets[i] = ProtoSet; |
589 | | |
590 | | /* allocate space for the proto lengths and install in class */ |
591 | 3.21M | } |
592 | 3.21M | memset(ConfigLengths, 0, sizeof(ConfigLengths)); |
593 | 3.21M | } |
594 | | |
595 | 3.21M | INT_CLASS_STRUCT::~INT_CLASS_STRUCT() { |
596 | 6.43M | for (int i = 0; i < NumProtoSets; i++) { |
597 | 3.21M | delete ProtoSets[i]; |
598 | 3.21M | } |
599 | 3.21M | } |
600 | | |
601 | | /// This constructor allocates a new set of integer templates |
602 | | /// initialized to hold 0 classes. |
603 | 6.70k | INT_TEMPLATES_STRUCT::INT_TEMPLATES_STRUCT() { |
604 | 6.70k | NumClasses = 0; |
605 | 6.70k | NumClassPruners = 0; |
606 | | |
607 | 219M | for (int i = 0; i < MAX_NUM_CLASSES; i++) { |
608 | 219M | ClassForClassId(this, i) = nullptr; |
609 | 219M | } |
610 | 6.70k | } |
611 | | |
612 | 6.69k | INT_TEMPLATES_STRUCT::~INT_TEMPLATES_STRUCT() { |
613 | 3.22M | for (unsigned i = 0; i < NumClasses; i++) { |
614 | 3.21M | delete Class[i]; |
615 | 3.21M | } |
616 | 107k | for (unsigned i = 0; i < NumClassPruners; i++) { |
617 | 100k | delete ClassPruners[i]; |
618 | 100k | } |
619 | 6.69k | } |
620 | | |
621 | | /** |
622 | | * This routine reads a set of integer templates from |
623 | | * File. File must already be open and must be in the |
624 | | * correct binary format. |
625 | | * @param fp open file to read templates from |
626 | | * @return Pointer to integer templates read from File. |
627 | | * @note Globals: none |
628 | | */ |
629 | 4 | INT_TEMPLATES_STRUCT *Classify::ReadIntTemplates(TFile *fp) { |
630 | 4 | int j, w, x, y, z; |
631 | 4 | INT_TEMPLATES_STRUCT *Templates; |
632 | 4 | CLASS_PRUNER_STRUCT *Pruner; |
633 | 4 | INT_CLASS_STRUCT *Class; |
634 | | |
635 | | /* variables for conversion from older inttemp formats */ |
636 | 4 | int b, bit_number, last_cp_bit_number, new_b, new_i, new_w; |
637 | 4 | CLASS_ID class_id, max_class_id; |
638 | 4 | std::vector<CLASS_ID> ClassIdFor(MAX_NUM_CLASSES); |
639 | 4 | std::vector<CLASS_PRUNER_STRUCT *> TempClassPruner(MAX_NUM_CLASS_PRUNERS); |
640 | 4 | uint32_t SetBitsForMask = // word with NUM_BITS_PER_CLASS |
641 | 4 | (1 << NUM_BITS_PER_CLASS) - 1; // set starting at bit 0 |
642 | 4 | uint32_t Mask, NewMask, ClassBits; |
643 | 4 | unsigned MaxNumConfigs = MAX_NUM_CONFIGS; |
644 | 4 | unsigned WerdsPerConfigVec = WERDS_PER_CONFIG_VEC; |
645 | | |
646 | | /* first read the high level template struct */ |
647 | 4 | Templates = new INT_TEMPLATES_STRUCT; |
648 | | // Read Templates in parts for 64 bit compatibility. |
649 | 4 | uint32_t unicharset_size; |
650 | 4 | if (fp->FReadEndian(&unicharset_size, sizeof(unicharset_size), 1) != 1) { |
651 | 0 | tprintf("Bad read of inttemp!\n"); |
652 | 0 | } |
653 | 4 | int32_t version_id = 0; |
654 | 4 | if (fp->FReadEndian(&version_id, sizeof(version_id), 1) != 1 || |
655 | 4 | fp->FReadEndian(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1) != 1) { |
656 | 0 | tprintf("Bad read of inttemp!\n"); |
657 | 0 | } |
658 | 4 | if (version_id < 0) { |
659 | | // This file has a version id! |
660 | 4 | version_id = -version_id; |
661 | 4 | if (fp->FReadEndian(&Templates->NumClasses, sizeof(Templates->NumClasses), 1) != 1) { |
662 | 0 | tprintf("Bad read of inttemp!\n"); |
663 | 0 | } |
664 | 4 | } else { |
665 | 0 | Templates->NumClasses = version_id; |
666 | 0 | } |
667 | | |
668 | 4 | if (version_id < 3) { |
669 | 0 | MaxNumConfigs = OLD_MAX_NUM_CONFIGS; |
670 | 0 | WerdsPerConfigVec = OLD_WERDS_PER_CONFIG_VEC; |
671 | 0 | } |
672 | | |
673 | 4 | if (version_id < 2) { |
674 | 0 | std::vector<int16_t> IndexFor(MAX_NUM_CLASSES); |
675 | 0 | if (fp->FReadEndian(&IndexFor[0], sizeof(IndexFor[0]), unicharset_size) != unicharset_size) { |
676 | 0 | tprintf("Bad read of inttemp!\n"); |
677 | 0 | } |
678 | 0 | if (fp->FReadEndian(&ClassIdFor[0], sizeof(ClassIdFor[0]), Templates->NumClasses) != |
679 | 0 | Templates->NumClasses) { |
680 | 0 | tprintf("Bad read of inttemp!\n"); |
681 | 0 | } |
682 | 0 | } |
683 | | |
684 | | /* then read in the class pruners */ |
685 | 4 | const unsigned kNumBuckets = NUM_CP_BUCKETS * NUM_CP_BUCKETS * NUM_CP_BUCKETS * WERDS_PER_CP_VECTOR; |
686 | 20 | for (unsigned i = 0; i < Templates->NumClassPruners; i++) { |
687 | 16 | Pruner = new CLASS_PRUNER_STRUCT; |
688 | 16 | if (fp->FReadEndian(Pruner, sizeof(Pruner->p[0][0][0][0]), kNumBuckets) != kNumBuckets) { |
689 | 0 | tprintf("Bad read of inttemp!\n"); |
690 | 0 | } |
691 | 16 | if (version_id < 2) { |
692 | 0 | TempClassPruner[i] = Pruner; |
693 | 16 | } else { |
694 | 16 | Templates->ClassPruners[i] = Pruner; |
695 | 16 | } |
696 | 16 | } |
697 | | |
698 | | /* fix class pruners if they came from an old version of inttemp */ |
699 | 4 | if (version_id < 2) { |
700 | | // Allocate enough class pruners to cover all the class ids. |
701 | 0 | max_class_id = 0; |
702 | 0 | for (unsigned i = 0; i < Templates->NumClasses; i++) { |
703 | 0 | if (ClassIdFor[i] > max_class_id) { |
704 | 0 | max_class_id = ClassIdFor[i]; |
705 | 0 | } |
706 | 0 | } |
707 | 0 | for (int i = 0; i <= CPrunerIdFor(max_class_id); i++) { |
708 | 0 | Templates->ClassPruners[i] = new CLASS_PRUNER_STRUCT; |
709 | 0 | memset(Templates->ClassPruners[i], 0, sizeof(CLASS_PRUNER_STRUCT)); |
710 | 0 | } |
711 | | // Convert class pruners from the old format (indexed by class index) |
712 | | // to the new format (indexed by class id). |
713 | 0 | last_cp_bit_number = NUM_BITS_PER_CLASS * Templates->NumClasses - 1; |
714 | 0 | for (unsigned i = 0; i < Templates->NumClassPruners; i++) { |
715 | 0 | for (x = 0; x < NUM_CP_BUCKETS; x++) { |
716 | 0 | for (y = 0; y < NUM_CP_BUCKETS; y++) { |
717 | 0 | for (z = 0; z < NUM_CP_BUCKETS; z++) { |
718 | 0 | for (w = 0; w < WERDS_PER_CP_VECTOR; w++) { |
719 | 0 | if (TempClassPruner[i]->p[x][y][z][w] == 0) { |
720 | 0 | continue; |
721 | 0 | } |
722 | 0 | for (b = 0; b < BITS_PER_WERD; b += NUM_BITS_PER_CLASS) { |
723 | 0 | bit_number = i * BITS_PER_CP_VECTOR + w * BITS_PER_WERD + b; |
724 | 0 | if (bit_number > last_cp_bit_number) { |
725 | 0 | break; // the rest of the bits in this word are not used |
726 | 0 | } |
727 | 0 | class_id = ClassIdFor[bit_number / NUM_BITS_PER_CLASS]; |
728 | | // Single out NUM_BITS_PER_CLASS bits relating to class_id. |
729 | 0 | Mask = SetBitsForMask << b; |
730 | 0 | ClassBits = TempClassPruner[i]->p[x][y][z][w] & Mask; |
731 | | // Move these bits to the new position in which they should |
732 | | // appear (indexed corresponding to the class_id). |
733 | 0 | new_i = CPrunerIdFor(class_id); |
734 | 0 | new_w = CPrunerWordIndexFor(class_id); |
735 | 0 | new_b = CPrunerBitIndexFor(class_id) * NUM_BITS_PER_CLASS; |
736 | 0 | if (new_b > b) { |
737 | 0 | ClassBits <<= (new_b - b); |
738 | 0 | } else { |
739 | 0 | ClassBits >>= (b - new_b); |
740 | 0 | } |
741 | | // Copy bits relating to class_id to the correct position |
742 | | // in Templates->ClassPruner. |
743 | 0 | NewMask = SetBitsForMask << new_b; |
744 | 0 | Templates->ClassPruners[new_i]->p[x][y][z][new_w] &= ~NewMask; |
745 | 0 | Templates->ClassPruners[new_i]->p[x][y][z][new_w] |= ClassBits; |
746 | 0 | } |
747 | 0 | } |
748 | 0 | } |
749 | 0 | } |
750 | 0 | } |
751 | 0 | } |
752 | 0 | for (unsigned i = 0; i < Templates->NumClassPruners; i++) { |
753 | 0 | delete TempClassPruner[i]; |
754 | 0 | } |
755 | 0 | } |
756 | | |
757 | | /* then read in each class */ |
758 | 456 | for (unsigned i = 0; i < Templates->NumClasses; i++) { |
759 | | /* first read in the high level struct for the class */ |
760 | 452 | Class = new INT_CLASS_STRUCT; |
761 | 452 | if (fp->FReadEndian(&Class->NumProtos, sizeof(Class->NumProtos), 1) != 1 || |
762 | 452 | fp->FRead(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1) != 1 || |
763 | 452 | fp->FRead(&Class->NumConfigs, sizeof(Class->NumConfigs), 1) != 1) { |
764 | 0 | tprintf("Bad read of inttemp!\n"); |
765 | 0 | } |
766 | 452 | if (version_id == 0) { |
767 | | // Only version 0 writes 5 pointless pointers to the file. |
768 | 0 | for (j = 0; j < 5; ++j) { |
769 | 0 | int32_t junk; |
770 | 0 | if (fp->FRead(&junk, sizeof(junk), 1) != 1) { |
771 | 0 | tprintf("Bad read of inttemp!\n"); |
772 | 0 | } |
773 | 0 | } |
774 | 0 | } |
775 | 452 | unsigned num_configs = version_id < 4 ? MaxNumConfigs : Class->NumConfigs; |
776 | 452 | ASSERT_HOST(num_configs <= MaxNumConfigs); |
777 | 452 | if (fp->FReadEndian(Class->ConfigLengths, sizeof(uint16_t), num_configs) != num_configs) { |
778 | 0 | tprintf("Bad read of inttemp!\n"); |
779 | 0 | } |
780 | 452 | if (version_id < 2) { |
781 | 0 | ClassForClassId(Templates, ClassIdFor[i]) = Class; |
782 | 452 | } else { |
783 | 452 | ClassForClassId(Templates, i) = Class; |
784 | 452 | } |
785 | | |
786 | | /* then read in the proto lengths */ |
787 | 452 | Class->ProtoLengths.clear(); |
788 | 452 | if (MaxNumIntProtosIn(Class) > 0) { |
789 | 440 | Class->ProtoLengths.resize(MaxNumIntProtosIn(Class)); |
790 | 440 | if (fp->FRead(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class)) != |
791 | 440 | MaxNumIntProtosIn(Class)) { |
792 | 0 | tprintf("Bad read of inttemp!\n"); |
793 | 0 | } |
794 | 440 | } |
795 | | |
796 | | /* then read in the proto sets */ |
797 | 1.24k | for (j = 0; j < Class->NumProtoSets; j++) { |
798 | 796 | auto ProtoSet = new PROTO_SET_STRUCT; |
799 | 796 | unsigned num_buckets = NUM_PP_PARAMS * NUM_PP_BUCKETS * WERDS_PER_PP_VECTOR; |
800 | 796 | if (fp->FReadEndian(&ProtoSet->ProtoPruner, sizeof(ProtoSet->ProtoPruner[0][0][0]), |
801 | 796 | num_buckets) != num_buckets) { |
802 | 0 | tprintf("Bad read of inttemp!\n"); |
803 | 0 | } |
804 | 51.7k | for (x = 0; x < PROTOS_PER_PROTO_SET; x++) { |
805 | 50.9k | if (fp->FRead(&ProtoSet->Protos[x].A, sizeof(ProtoSet->Protos[x].A), 1) != 1 || |
806 | 50.9k | fp->FRead(&ProtoSet->Protos[x].B, sizeof(ProtoSet->Protos[x].B), 1) != 1 || |
807 | 50.9k | fp->FRead(&ProtoSet->Protos[x].C, sizeof(ProtoSet->Protos[x].C), 1) != 1 || |
808 | 50.9k | fp->FRead(&ProtoSet->Protos[x].Angle, sizeof(ProtoSet->Protos[x].Angle), 1) != 1) { |
809 | 0 | tprintf("Bad read of inttemp!\n"); |
810 | 0 | } |
811 | 50.9k | if (fp->FReadEndian(&ProtoSet->Protos[x].Configs, sizeof(ProtoSet->Protos[x].Configs[0]), |
812 | 50.9k | WerdsPerConfigVec) != WerdsPerConfigVec) { |
813 | 0 | tprintf("Bad read of inttemp!\n"); |
814 | 0 | } |
815 | 50.9k | } |
816 | 796 | Class->ProtoSets[j] = ProtoSet; |
817 | 796 | } |
818 | 452 | if (version_id < 4) { |
819 | 0 | Class->font_set_id = -1; |
820 | 452 | } else { |
821 | 452 | fp->FReadEndian(&Class->font_set_id, sizeof(Class->font_set_id), 1); |
822 | 452 | } |
823 | 452 | } |
824 | | |
825 | 4 | if (version_id < 2) { |
826 | | /* add an empty nullptr class with class id 0 */ |
827 | 0 | assert(UnusedClassIdIn(Templates, 0)); |
828 | 0 | ClassForClassId(Templates, 0) = new INT_CLASS_STRUCT(1, 1); |
829 | 0 | ClassForClassId(Templates, 0)->font_set_id = -1; |
830 | 0 | Templates->NumClasses++; |
831 | | /* make sure the classes are contiguous */ |
832 | 0 | for (unsigned i = 0; i < MAX_NUM_CLASSES; i++) { |
833 | 0 | if (i < Templates->NumClasses) { |
834 | 0 | if (ClassForClassId(Templates, i) == nullptr) { |
835 | 0 | fprintf(stderr, "Non-contiguous class ids in inttemp\n"); |
836 | 0 | exit(1); |
837 | 0 | } |
838 | 0 | } else { |
839 | 0 | if (ClassForClassId(Templates, i) != nullptr) { |
840 | 0 | fprintf(stderr, "Class id %u exceeds NumClassesIn (Templates) %u\n", i, |
841 | 0 | Templates->NumClasses); |
842 | 0 | exit(1); |
843 | 0 | } |
844 | 0 | } |
845 | 0 | } |
846 | 0 | } |
847 | 4 | if (version_id >= 4) { |
848 | 4 | using namespace std::placeholders; // for _1, _2 |
849 | 4 | this->fontinfo_table_.read(fp, std::bind(read_info, _1, _2)); |
850 | 4 | if (version_id >= 5) { |
851 | 4 | this->fontinfo_table_.read(fp, std::bind(read_spacing_info, _1, _2)); |
852 | 4 | } |
853 | 444 | this->fontset_table_.read(fp, [](auto *f, auto *fs) { return f->DeSerialize(*fs); } ); |
854 | 4 | } |
855 | | |
856 | 4 | return (Templates); |
857 | 4 | } /* ReadIntTemplates */ |
858 | | |
859 | | #ifndef GRAPHICS_DISABLED |
860 | | /** |
861 | | * This routine sends the shapes in the global display |
862 | | * lists to the match debugger window. |
863 | | * |
864 | | * Globals: |
865 | | * - FeatureShapes display list containing feature matches |
866 | | * - ProtoShapes display list containing proto matches |
867 | | */ |
868 | | void Classify::ShowMatchDisplay() { |
869 | | InitIntMatchWindowIfReqd(); |
870 | | if (ProtoDisplayWindow) { |
871 | | ProtoDisplayWindow->Clear(); |
872 | | } |
873 | | if (FeatureDisplayWindow) { |
874 | | FeatureDisplayWindow->Clear(); |
875 | | } |
876 | | ClearFeatureSpaceWindow(static_cast<NORM_METHOD>(static_cast<int>(classify_norm_method)), |
877 | | IntMatchWindow); |
878 | | IntMatchWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y); |
879 | | if (ProtoDisplayWindow) { |
880 | | ProtoDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y); |
881 | | } |
882 | | if (FeatureDisplayWindow) { |
883 | | FeatureDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y); |
884 | | } |
885 | | } /* ShowMatchDisplay */ |
886 | | |
887 | | /// Clears the given window and draws the featurespace guides for the |
888 | | /// appropriate normalization method. |
889 | | void ClearFeatureSpaceWindow(NORM_METHOD norm_method, ScrollView *window) { |
890 | | window->Clear(); |
891 | | |
892 | | window->Pen(ScrollView::GREY); |
893 | | // Draw the feature space limit rectangle. |
894 | | window->Rectangle(0, 0, INT_MAX_X, INT_MAX_Y); |
895 | | if (norm_method == baseline) { |
896 | | window->SetCursor(0, INT_DESCENDER); |
897 | | window->DrawTo(INT_MAX_X, INT_DESCENDER); |
898 | | window->SetCursor(0, INT_BASELINE); |
899 | | window->DrawTo(INT_MAX_X, INT_BASELINE); |
900 | | window->SetCursor(0, INT_XHEIGHT); |
901 | | window->DrawTo(INT_MAX_X, INT_XHEIGHT); |
902 | | window->SetCursor(0, INT_CAPHEIGHT); |
903 | | window->DrawTo(INT_MAX_X, INT_CAPHEIGHT); |
904 | | } else { |
905 | | window->Rectangle(INT_XCENTER - INT_XRADIUS, INT_YCENTER - INT_YRADIUS, |
906 | | INT_XCENTER + INT_XRADIUS, INT_YCENTER + INT_YRADIUS); |
907 | | } |
908 | | } |
909 | | #endif |
910 | | |
911 | | /** |
912 | | * This routine writes Templates to File. The format |
913 | | * is an efficient binary format. File must already be open |
914 | | * for writing. |
915 | | * @param File open file to write templates to |
916 | | * @param Templates templates to save into File |
917 | | * @param target_unicharset the UNICHARSET to use |
918 | | */ |
919 | | void Classify::WriteIntTemplates(FILE *File, INT_TEMPLATES_STRUCT *Templates, |
920 | 0 | const UNICHARSET &target_unicharset) { |
921 | 0 | INT_CLASS_STRUCT *Class; |
922 | 0 | uint32_t unicharset_size = target_unicharset.size(); |
923 | 0 | int version_id = -5; // When negated by the reader -1 becomes +1 etc. |
924 | |
|
925 | 0 | if (Templates->NumClasses != unicharset_size) { |
926 | 0 | tprintf( |
927 | 0 | "Warning: executing WriteIntTemplates() with %d classes in" |
928 | 0 | " Templates, while target_unicharset size is %" PRIu32 "\n", |
929 | 0 | Templates->NumClasses, unicharset_size); |
930 | 0 | } |
931 | | |
932 | | /* first write the high level template struct */ |
933 | 0 | fwrite(&unicharset_size, sizeof(unicharset_size), 1, File); |
934 | 0 | fwrite(&version_id, sizeof(version_id), 1, File); |
935 | 0 | fwrite(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1, File); |
936 | 0 | fwrite(&Templates->NumClasses, sizeof(Templates->NumClasses), 1, File); |
937 | | |
938 | | /* then write out the class pruners */ |
939 | 0 | for (unsigned i = 0; i < Templates->NumClassPruners; i++) { |
940 | 0 | fwrite(Templates->ClassPruners[i], sizeof(CLASS_PRUNER_STRUCT), 1, File); |
941 | 0 | } |
942 | | |
943 | | /* then write out each class */ |
944 | 0 | for (unsigned i = 0; i < Templates->NumClasses; i++) { |
945 | 0 | Class = Templates->Class[i]; |
946 | | |
947 | | /* first write out the high level struct for the class */ |
948 | 0 | fwrite(&Class->NumProtos, sizeof(Class->NumProtos), 1, File); |
949 | 0 | fwrite(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1, File); |
950 | 0 | ASSERT_HOST(Class->NumConfigs == this->fontset_table_.at(Class->font_set_id).size()); |
951 | 0 | fwrite(&Class->NumConfigs, sizeof(Class->NumConfigs), 1, File); |
952 | 0 | for (int j = 0; j < Class->NumConfigs; ++j) { |
953 | 0 | fwrite(&Class->ConfigLengths[j], sizeof(uint16_t), 1, File); |
954 | 0 | } |
955 | | |
956 | | /* then write out the proto lengths */ |
957 | 0 | if (MaxNumIntProtosIn(Class) > 0) { |
958 | 0 | fwrite(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class), File); |
959 | 0 | } |
960 | | |
961 | | /* then write out the proto sets */ |
962 | 0 | for (int j = 0; j < Class->NumProtoSets; j++) { |
963 | 0 | fwrite(Class->ProtoSets[j], sizeof(PROTO_SET_STRUCT), 1, File); |
964 | 0 | } |
965 | | |
966 | | /* then write the fonts info */ |
967 | 0 | fwrite(&Class->font_set_id, sizeof(int), 1, File); |
968 | 0 | } |
969 | | |
970 | | /* Write the fonts info tables */ |
971 | 0 | using namespace std::placeholders; // for _1, _2 |
972 | 0 | this->fontinfo_table_.write(File, std::bind(write_info, _1, _2)); |
973 | 0 | this->fontinfo_table_.write(File, std::bind(write_spacing_info, _1, _2)); |
974 | 0 | this->fontset_table_.write(File, std::bind(write_set, _1, _2)); |
975 | 0 | } /* WriteIntTemplates */ |
976 | | |
977 | | /*----------------------------------------------------------------------------- |
978 | | Private Code |
979 | | -----------------------------------------------------------------------------*/ |
980 | | /** |
981 | | * This routine returns the parameter value which |
982 | | * corresponds to the beginning of the specified bucket. |
983 | | * The bucket number should have been generated using the |
984 | | * BucketFor() function with parameters Offset and NumBuckets. |
985 | | * @param Bucket bucket whose start is to be computed |
986 | | * @param Offset offset used to map params to buckets |
987 | | * @param NumBuckets total number of buckets |
988 | | * @return Param value corresponding to start position of Bucket. |
989 | | * @note Globals: none |
990 | | */ |
991 | 1.45k | float BucketStart(int Bucket, float Offset, int NumBuckets) { |
992 | 1.45k | return static_cast<float>(Bucket) / NumBuckets - Offset; |
993 | | |
994 | 1.45k | } /* BucketStart */ |
995 | | |
996 | | /** |
997 | | * This routine returns the parameter value which |
998 | | * corresponds to the end of the specified bucket. |
999 | | * The bucket number should have been generated using the |
1000 | | * BucketFor() function with parameters Offset and NumBuckets. |
1001 | | * @param Bucket bucket whose end is to be computed |
1002 | | * @param Offset offset used to map params to buckets |
1003 | | * @param NumBuckets total number of buckets |
1004 | | * @return Param value corresponding to end position of Bucket. |
1005 | | * @note Globals: none |
1006 | | */ |
1007 | 726 | float BucketEnd(int Bucket, float Offset, int NumBuckets) { |
1008 | 726 | return static_cast<float>(Bucket + 1) / NumBuckets - Offset; |
1009 | 726 | } /* BucketEnd */ |
1010 | | |
1011 | | /** |
1012 | | * This routine fills in the section of a class pruner |
1013 | | * corresponding to a single x value for a single proto of |
1014 | | * a class. |
1015 | | * @param FillSpec specifies which bits to fill in pruner |
1016 | | * @param Pruner class pruner to be filled |
1017 | | * @param ClassMask indicates which bits to change in each word |
1018 | | * @param ClassCount indicates what to change bits to |
1019 | | * @param WordIndex indicates which word to change |
1020 | | */ |
1021 | | void DoFill(FILL_SPEC *FillSpec, CLASS_PRUNER_STRUCT *Pruner, uint32_t ClassMask, |
1022 | 9.26k | uint32_t ClassCount, uint32_t WordIndex) { |
1023 | 9.26k | int X, Y, Angle; |
1024 | 9.26k | uint32_t OldWord; |
1025 | | |
1026 | 9.26k | X = FillSpec->X; |
1027 | 9.26k | if (X < 0) { |
1028 | 0 | X = 0; |
1029 | 0 | } |
1030 | 9.26k | if (X >= NUM_CP_BUCKETS) { |
1031 | 0 | X = NUM_CP_BUCKETS - 1; |
1032 | 0 | } |
1033 | | |
1034 | 9.26k | if (FillSpec->YStart < 0) { |
1035 | 0 | FillSpec->YStart = 0; |
1036 | 0 | } |
1037 | 9.26k | if (FillSpec->YEnd >= NUM_CP_BUCKETS) { |
1038 | 215 | FillSpec->YEnd = NUM_CP_BUCKETS - 1; |
1039 | 215 | } |
1040 | | |
1041 | 61.4k | for (Y = FillSpec->YStart; Y <= FillSpec->YEnd; Y++) { |
1042 | 272k | for (Angle = FillSpec->AngleStart;; CircularIncrement(Angle, NUM_CP_BUCKETS)) { |
1043 | 272k | OldWord = Pruner->p[X][Y][Angle][WordIndex]; |
1044 | 272k | if (ClassCount > (OldWord & ClassMask)) { |
1045 | 145k | OldWord &= ~ClassMask; |
1046 | 145k | OldWord |= ClassCount; |
1047 | 145k | Pruner->p[X][Y][Angle][WordIndex] = OldWord; |
1048 | 145k | } |
1049 | 272k | if (Angle == FillSpec->AngleEnd) { |
1050 | 52.2k | break; |
1051 | 52.2k | } |
1052 | 272k | } |
1053 | 52.2k | } |
1054 | 9.26k | } /* DoFill */ |
1055 | | |
1056 | | /** |
1057 | | * Return true if the specified table filler is done, i.e. |
1058 | | * if it has no more lines to fill. |
1059 | | * @param Filler table filler to check if done |
1060 | | * @return true if no more lines to fill, false otherwise. |
1061 | | * @note Globals: none |
1062 | | */ |
1063 | 11.2k | bool FillerDone(TABLE_FILLER *Filler) { |
1064 | 11.2k | FILL_SWITCH *Next; |
1065 | | |
1066 | 11.2k | Next = &(Filler->Switch[Filler->NextSwitch]); |
1067 | | |
1068 | 11.2k | return Filler->X > Next->X && Next->Type == LastSwitch; |
1069 | | |
1070 | 11.2k | } /* FillerDone */ |
1071 | | |
1072 | | /** |
1073 | | * This routine sets Bit in each bit vector whose |
1074 | | * bucket lies within the range Center +- Spread. The fill |
1075 | | * is done for a circular dimension, i.e. bucket 0 is adjacent |
1076 | | * to the last bucket. It is assumed that Center and Spread |
1077 | | * are expressed in a circular coordinate system whose range |
1078 | | * is 0 to 1. |
1079 | | * @param ParamTable table of bit vectors, one per param bucket |
1080 | | * @param Bit bit position in vectors to be filled |
1081 | | * @param Center center of filled area |
1082 | | * @param Spread spread of filled area |
1083 | | * @param debug debug flag |
1084 | | */ |
1085 | | void FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit, |
1086 | 11.9k | float Center, float Spread, bool debug) { |
1087 | 11.9k | int i, FirstBucket, LastBucket; |
1088 | | |
1089 | 11.9k | if (Spread > 0.5) { |
1090 | 0 | Spread = 0.5; |
1091 | 0 | } |
1092 | | |
1093 | 11.9k | FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS)); |
1094 | 11.9k | if (FirstBucket < 0) { |
1095 | 1.38k | FirstBucket += NUM_PP_BUCKETS; |
1096 | 1.38k | } |
1097 | | |
1098 | 11.9k | LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS)); |
1099 | 11.9k | if (LastBucket >= NUM_PP_BUCKETS) { |
1100 | 277 | LastBucket -= NUM_PP_BUCKETS; |
1101 | 277 | } |
1102 | 11.9k | if (debug) { |
1103 | 0 | tprintf("Circular fill from %d to %d", FirstBucket, LastBucket); |
1104 | 0 | } |
1105 | 202k | for (i = FirstBucket; true; CircularIncrement(i, NUM_PP_BUCKETS)) { |
1106 | 202k | SET_BIT(ParamTable[i], Bit); |
1107 | | |
1108 | | /* exit loop after we have set the bit for the last bucket */ |
1109 | 202k | if (i == LastBucket) { |
1110 | 11.9k | break; |
1111 | 11.9k | } |
1112 | 202k | } |
1113 | | |
1114 | 11.9k | } /* FillPPCircularBits */ |
1115 | | |
1116 | | /** |
1117 | | * This routine sets Bit in each bit vector whose |
1118 | | * bucket lies within the range Center +- Spread. The fill |
1119 | | * is done for a linear dimension, i.e. there is no wrap-around |
1120 | | * for this dimension. It is assumed that Center and Spread |
1121 | | * are expressed in a linear coordinate system whose range |
1122 | | * is approximately 0 to 1. Values outside this range will |
1123 | | * be clipped. |
1124 | | * @param ParamTable table of bit vectors, one per param bucket |
1125 | | * @param Bit bit number being filled |
1126 | | * @param Center center of filled area |
1127 | | * @param Spread spread of filled area |
1128 | | * @param debug debug flag |
1129 | | */ |
1130 | | void FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit, |
1131 | 23.8k | float Center, float Spread, bool debug) { |
1132 | 23.8k | int i, FirstBucket, LastBucket; |
1133 | | |
1134 | 23.8k | FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS)); |
1135 | 23.8k | if (FirstBucket < 0) { |
1136 | 863 | FirstBucket = 0; |
1137 | 863 | } |
1138 | | |
1139 | 23.8k | LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS)); |
1140 | 23.8k | if (LastBucket >= NUM_PP_BUCKETS) { |
1141 | 3.86k | LastBucket = NUM_PP_BUCKETS - 1; |
1142 | 3.86k | } |
1143 | | |
1144 | 23.8k | if (debug) { |
1145 | 0 | tprintf("Linear fill from %d to %d", FirstBucket, LastBucket); |
1146 | 0 | } |
1147 | 342k | for (i = FirstBucket; i <= LastBucket; i++) { |
1148 | 318k | SET_BIT(ParamTable[i], Bit); |
1149 | 318k | } |
1150 | | |
1151 | 23.8k | } /* FillPPLinearBits */ |
1152 | | |
1153 | | /*---------------------------------------------------------------------------*/ |
1154 | | #ifndef GRAPHICS_DISABLED |
1155 | | /** |
1156 | | * This routine prompts the user with Prompt and waits |
1157 | | * for the user to enter something in the debug window. |
1158 | | * @param Prompt prompt to print while waiting for input from window |
1159 | | * @param adaptive_on |
1160 | | * @param pretrained_on |
1161 | | * @param shape_id |
1162 | | * @return Character entered in the debug window. |
1163 | | * @note Globals: none |
1164 | | */ |
1165 | | CLASS_ID Classify::GetClassToDebug(const char *Prompt, bool *adaptive_on, bool *pretrained_on, |
1166 | | int *shape_id) { |
1167 | | tprintf("%s\n", Prompt); |
1168 | | SVEventType ev_type; |
1169 | | int unichar_id = INVALID_UNICHAR_ID; |
1170 | | // Wait until a click or popup event. |
1171 | | do { |
1172 | | auto ev = IntMatchWindow->AwaitEvent(SVET_ANY); |
1173 | | ev_type = ev->type; |
1174 | | if (ev_type == SVET_POPUP) { |
1175 | | if (ev->command_id == IDA_SHAPE_INDEX) { |
1176 | | if (shape_table_ != nullptr) { |
1177 | | *shape_id = atoi(ev->parameter); |
1178 | | *adaptive_on = false; |
1179 | | *pretrained_on = true; |
1180 | | if (*shape_id >= 0 && static_cast<unsigned>(*shape_id) < shape_table_->NumShapes()) { |
1181 | | int font_id; |
1182 | | shape_table_->GetFirstUnicharAndFont(*shape_id, &unichar_id, &font_id); |
1183 | | tprintf("Shape %d, first unichar=%d, font=%d\n", *shape_id, unichar_id, font_id); |
1184 | | return unichar_id; |
1185 | | } |
1186 | | tprintf("Shape index '%s' not found in shape table\n", ev->parameter); |
1187 | | } else { |
1188 | | tprintf("No shape table loaded!\n"); |
1189 | | } |
1190 | | } else { |
1191 | | if (unicharset.contains_unichar(ev->parameter)) { |
1192 | | unichar_id = unicharset.unichar_to_id(ev->parameter); |
1193 | | if (ev->command_id == IDA_ADAPTIVE) { |
1194 | | *adaptive_on = true; |
1195 | | *pretrained_on = false; |
1196 | | *shape_id = -1; |
1197 | | } else if (ev->command_id == IDA_STATIC) { |
1198 | | *adaptive_on = false; |
1199 | | *pretrained_on = true; |
1200 | | } else { |
1201 | | *adaptive_on = true; |
1202 | | *pretrained_on = true; |
1203 | | } |
1204 | | if (ev->command_id == IDA_ADAPTIVE || shape_table_ == nullptr) { |
1205 | | *shape_id = -1; |
1206 | | return unichar_id; |
1207 | | } |
1208 | | for (unsigned s = 0; s < shape_table_->NumShapes(); ++s) { |
1209 | | if (shape_table_->GetShape(s).ContainsUnichar(unichar_id)) { |
1210 | | tprintf("%s\n", shape_table_->DebugStr(s).c_str()); |
1211 | | } |
1212 | | } |
1213 | | } else { |
1214 | | tprintf("Char class '%s' not found in unicharset", ev->parameter); |
1215 | | } |
1216 | | } |
1217 | | } |
1218 | | } while (ev_type != SVET_CLICK); |
1219 | | return 0; |
1220 | | } /* GetClassToDebug */ |
1221 | | |
1222 | | #endif |
1223 | | |
1224 | | /** |
1225 | | * This routine copies the appropriate global pad variables |
1226 | | * into EndPad, SidePad, and AnglePad. This is a kludge used |
1227 | | * to get around the fact that global control variables cannot |
1228 | | * be arrays. If the specified level is illegal, the tightest |
1229 | | * possible pads are returned. |
1230 | | * @param Level "tightness" level to return pads for |
1231 | | * @param EndPad place to put end pad for Level |
1232 | | * @param SidePad place to put side pad for Level |
1233 | | * @param AnglePad place to put angle pad for Level |
1234 | | */ |
1235 | 1.97k | void GetCPPadsForLevel(int Level, float *EndPad, float *SidePad, float *AnglePad) { |
1236 | 1.97k | switch (Level) { |
1237 | 659 | case 0: |
1238 | 659 | *EndPad = classify_cp_end_pad_loose * GetPicoFeatureLength(); |
1239 | 659 | *SidePad = classify_cp_side_pad_loose * GetPicoFeatureLength(); |
1240 | 659 | *AnglePad = classify_cp_angle_pad_loose / 360.0; |
1241 | 659 | break; |
1242 | | |
1243 | 659 | case 1: |
1244 | 659 | *EndPad = classify_cp_end_pad_medium * GetPicoFeatureLength(); |
1245 | 659 | *SidePad = classify_cp_side_pad_medium * GetPicoFeatureLength(); |
1246 | 659 | *AnglePad = classify_cp_angle_pad_medium / 360.0; |
1247 | 659 | break; |
1248 | | |
1249 | 659 | case 2: |
1250 | 659 | *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength(); |
1251 | 659 | *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength(); |
1252 | 659 | *AnglePad = classify_cp_angle_pad_tight / 360.0; |
1253 | 659 | break; |
1254 | | |
1255 | 0 | default: |
1256 | 0 | *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength(); |
1257 | 0 | *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength(); |
1258 | 0 | *AnglePad = classify_cp_angle_pad_tight / 360.0; |
1259 | 0 | break; |
1260 | 1.97k | } |
1261 | 1.97k | if (*AnglePad > 0.5) { |
1262 | 0 | *AnglePad = 0.5; |
1263 | 0 | } |
1264 | | |
1265 | 1.97k | } /* GetCPPadsForLevel */ |
1266 | | |
1267 | | /** |
1268 | | * @param Evidence evidence value to return color for |
1269 | | * @return Color which corresponds to specified Evidence value. |
1270 | | * @note Globals: none |
1271 | | */ |
1272 | 0 | ScrollView::Color GetMatchColorFor(float Evidence) { |
1273 | 0 | assert(Evidence >= 0.0); |
1274 | 0 | assert(Evidence <= 1.0); |
1275 | |
|
1276 | 0 | if (Evidence >= 0.90) { |
1277 | 0 | return ScrollView::WHITE; |
1278 | 0 | } else if (Evidence >= 0.75) { |
1279 | 0 | return ScrollView::GREEN; |
1280 | 0 | } else if (Evidence >= 0.50) { |
1281 | 0 | return ScrollView::RED; |
1282 | 0 | } else { |
1283 | 0 | return ScrollView::BLUE; |
1284 | 0 | } |
1285 | 0 | } /* GetMatchColorFor */ |
1286 | | |
1287 | | /** |
1288 | | * This routine returns (in Fill) the specification of |
1289 | | * the next line to be filled from Filler. FillerDone() should |
1290 | | * always be called before GetNextFill() to ensure that we |
1291 | | * do not run past the end of the fill table. |
1292 | | * @param Filler filler to get next fill spec from |
1293 | | * @param Fill place to put spec for next fill |
1294 | | */ |
1295 | 9.26k | void GetNextFill(TABLE_FILLER *Filler, FILL_SPEC *Fill) { |
1296 | 9.26k | FILL_SWITCH *Next; |
1297 | | |
1298 | | /* compute the fill assuming no switches will be encountered */ |
1299 | 9.26k | Fill->AngleStart = Filler->AngleStart; |
1300 | 9.26k | Fill->AngleEnd = Filler->AngleEnd; |
1301 | 9.26k | Fill->X = Filler->X; |
1302 | 9.26k | Fill->YStart = Filler->YStart >> 8; |
1303 | 9.26k | Fill->YEnd = Filler->YEnd >> 8; |
1304 | | |
1305 | | /* update the fill info and the filler for ALL switches at this X value */ |
1306 | 9.26k | Next = &(Filler->Switch[Filler->NextSwitch]); |
1307 | 10.7k | while (Filler->X >= Next->X) { |
1308 | 3.42k | Fill->X = Filler->X = Next->X; |
1309 | 3.42k | if (Next->Type == StartSwitch) { |
1310 | 726 | Fill->YStart = Next->Y; |
1311 | 726 | Filler->StartDelta = Next->Delta; |
1312 | 726 | Filler->YStart = Next->YInit; |
1313 | 2.70k | } else if (Next->Type == EndSwitch) { |
1314 | 726 | Fill->YEnd = Next->Y; |
1315 | 726 | Filler->EndDelta = Next->Delta; |
1316 | 726 | Filler->YEnd = Next->YInit; |
1317 | 1.97k | } else { /* Type must be LastSwitch */ |
1318 | 1.97k | break; |
1319 | 1.97k | } |
1320 | 1.45k | Filler->NextSwitch++; |
1321 | 1.45k | Next = &(Filler->Switch[Filler->NextSwitch]); |
1322 | 1.45k | } |
1323 | | |
1324 | | /* prepare the filler for the next call to this routine */ |
1325 | 9.26k | Filler->X++; |
1326 | 9.26k | Filler->YStart += Filler->StartDelta; |
1327 | 9.26k | Filler->YEnd += Filler->EndDelta; |
1328 | | |
1329 | 9.26k | } /* GetNextFill */ |
1330 | | |
1331 | | /** |
1332 | | * This routine computes a data structure (Filler) |
1333 | | * which can be used to fill in a rectangle surrounding |
1334 | | * the specified Proto. Results are returned in Filler. |
1335 | | * |
1336 | | * @param EndPad, SidePad, AnglePad padding to add to proto |
1337 | | * @param Proto proto to create a filler for |
1338 | | * @param Filler place to put table filler |
1339 | | */ |
1340 | | void InitTableFiller(float EndPad, float SidePad, float AnglePad, PROTO_STRUCT *Proto, TABLE_FILLER *Filler) |
1341 | 7.58k | #define XS X_SHIFT |
1342 | 6.85k | #define YS Y_SHIFT |
1343 | 3.95k | #define AS ANGLE_SHIFT |
1344 | 18.3k | #define NB NUM_CP_BUCKETS |
1345 | 1.97k | { |
1346 | 1.97k | float Angle; |
1347 | 1.97k | float X, Y, HalfLength; |
1348 | 1.97k | float Cos, Sin; |
1349 | 1.97k | float XAdjust, YAdjust; |
1350 | 1.97k | FPOINT Start, Switch1, Switch2, End; |
1351 | 1.97k | int S1 = 0; |
1352 | 1.97k | int S2 = 1; |
1353 | | |
1354 | 1.97k | Angle = Proto->Angle; |
1355 | 1.97k | X = Proto->X; |
1356 | 1.97k | Y = Proto->Y; |
1357 | 1.97k | HalfLength = Proto->Length / 2.0; |
1358 | | |
1359 | 1.97k | Filler->AngleStart = CircBucketFor(Angle - AnglePad, AS, NB); |
1360 | 1.97k | Filler->AngleEnd = CircBucketFor(Angle + AnglePad, AS, NB); |
1361 | 1.97k | Filler->NextSwitch = 0; |
1362 | | |
1363 | 1.97k | if (fabs(Angle - 0.0) < HV_TOLERANCE || fabs(Angle - 0.5) < HV_TOLERANCE) { |
1364 | | /* horizontal proto - handle as special case */ |
1365 | 489 | Filler->X = Bucket8For(X - HalfLength - EndPad, XS, NB); |
1366 | 489 | Filler->YStart = Bucket16For(Y - SidePad, YS, NB * 256); |
1367 | 489 | Filler->YEnd = Bucket16For(Y + SidePad, YS, NB * 256); |
1368 | 489 | Filler->StartDelta = 0; |
1369 | 489 | Filler->EndDelta = 0; |
1370 | 489 | Filler->Switch[0].Type = LastSwitch; |
1371 | 489 | Filler->Switch[0].X = Bucket8For(X + HalfLength + EndPad, XS, NB); |
1372 | 1.48k | } else if (fabs(Angle - 0.25) < HV_TOLERANCE || fabs(Angle - 0.75) < HV_TOLERANCE) { |
1373 | | /* vertical proto - handle as special case */ |
1374 | 762 | Filler->X = Bucket8For(X - SidePad, XS, NB); |
1375 | 762 | Filler->YStart = Bucket16For(Y - HalfLength - EndPad, YS, NB * 256); |
1376 | 762 | Filler->YEnd = Bucket16For(Y + HalfLength + EndPad, YS, NB * 256); |
1377 | 762 | Filler->StartDelta = 0; |
1378 | 762 | Filler->EndDelta = 0; |
1379 | 762 | Filler->Switch[0].Type = LastSwitch; |
1380 | 762 | Filler->Switch[0].X = Bucket8For(X + SidePad, XS, NB); |
1381 | 762 | } else { |
1382 | | /* diagonal proto */ |
1383 | | |
1384 | 726 | if ((Angle > 0.0 && Angle < 0.25) || (Angle > 0.5 && Angle < 0.75)) { |
1385 | | /* rising diagonal proto */ |
1386 | 351 | Angle *= 2.0 * M_PI; |
1387 | 351 | Cos = fabs(std::cos(Angle)); |
1388 | 351 | Sin = fabs(std::sin(Angle)); |
1389 | | |
1390 | | /* compute the positions of the corners of the acceptance region */ |
1391 | 351 | Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin; |
1392 | 351 | Start.y = Y - (HalfLength + EndPad) * Sin + SidePad * Cos; |
1393 | 351 | End.x = 2.0 * X - Start.x; |
1394 | 351 | End.y = 2.0 * Y - Start.y; |
1395 | 351 | Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin; |
1396 | 351 | Switch1.y = Y - (HalfLength + EndPad) * Sin - SidePad * Cos; |
1397 | 351 | Switch2.x = 2.0 * X - Switch1.x; |
1398 | 351 | Switch2.y = 2.0 * Y - Switch1.y; |
1399 | | |
1400 | 351 | if (Switch1.x > Switch2.x) { |
1401 | 232 | S1 = 1; |
1402 | 232 | S2 = 0; |
1403 | 232 | } |
1404 | | |
1405 | | /* translate into bucket positions and deltas */ |
1406 | 351 | Filler->X = Bucket8For(Start.x, XS, NB); |
1407 | 351 | Filler->StartDelta = -static_cast<int16_t>((Cos / Sin) * 256); |
1408 | 351 | Filler->EndDelta = static_cast<int16_t>((Sin / Cos) * 256); |
1409 | | |
1410 | 351 | XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x; |
1411 | 351 | YAdjust = XAdjust * Cos / Sin; |
1412 | 351 | Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256); |
1413 | 351 | YAdjust = XAdjust * Sin / Cos; |
1414 | 351 | Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256); |
1415 | | |
1416 | 351 | Filler->Switch[S1].Type = StartSwitch; |
1417 | 351 | Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB); |
1418 | 351 | Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB); |
1419 | 351 | XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB); |
1420 | 351 | YAdjust = XAdjust * Sin / Cos; |
1421 | 351 | Filler->Switch[S1].YInit = Bucket16For(Switch1.y - YAdjust, YS, NB * 256); |
1422 | 351 | Filler->Switch[S1].Delta = Filler->EndDelta; |
1423 | | |
1424 | 351 | Filler->Switch[S2].Type = EndSwitch; |
1425 | 351 | Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB); |
1426 | 351 | Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB); |
1427 | 351 | XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB); |
1428 | 351 | YAdjust = XAdjust * Cos / Sin; |
1429 | 351 | Filler->Switch[S2].YInit = Bucket16For(Switch2.y + YAdjust, YS, NB * 256); |
1430 | 351 | Filler->Switch[S2].Delta = Filler->StartDelta; |
1431 | | |
1432 | 351 | Filler->Switch[2].Type = LastSwitch; |
1433 | 351 | Filler->Switch[2].X = Bucket8For(End.x, XS, NB); |
1434 | 375 | } else { |
1435 | | /* falling diagonal proto */ |
1436 | 375 | Angle *= 2.0 * M_PI; |
1437 | 375 | Cos = fabs(std::cos(Angle)); |
1438 | 375 | Sin = fabs(std::sin(Angle)); |
1439 | | |
1440 | | /* compute the positions of the corners of the acceptance region */ |
1441 | 375 | Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin; |
1442 | 375 | Start.y = Y + (HalfLength + EndPad) * Sin - SidePad * Cos; |
1443 | 375 | End.x = 2.0 * X - Start.x; |
1444 | 375 | End.y = 2.0 * Y - Start.y; |
1445 | 375 | Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin; |
1446 | 375 | Switch1.y = Y + (HalfLength + EndPad) * Sin + SidePad * Cos; |
1447 | 375 | Switch2.x = 2.0 * X - Switch1.x; |
1448 | 375 | Switch2.y = 2.0 * Y - Switch1.y; |
1449 | | |
1450 | 375 | if (Switch1.x > Switch2.x) { |
1451 | 262 | S1 = 1; |
1452 | 262 | S2 = 0; |
1453 | 262 | } |
1454 | | |
1455 | | /* translate into bucket positions and deltas */ |
1456 | 375 | Filler->X = Bucket8For(Start.x, XS, NB); |
1457 | 375 | Filler->StartDelta = static_cast<int16_t>( |
1458 | 375 | ClipToRange<int>(-IntCastRounded((Sin / Cos) * 256), INT16_MIN, INT16_MAX)); |
1459 | 375 | Filler->EndDelta = static_cast<int16_t>( |
1460 | 375 | ClipToRange<int>(IntCastRounded((Cos / Sin) * 256), INT16_MIN, INT16_MAX)); |
1461 | | |
1462 | 375 | XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x; |
1463 | 375 | YAdjust = XAdjust * Sin / Cos; |
1464 | 375 | Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256); |
1465 | 375 | YAdjust = XAdjust * Cos / Sin; |
1466 | 375 | Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256); |
1467 | | |
1468 | 375 | Filler->Switch[S1].Type = EndSwitch; |
1469 | 375 | Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB); |
1470 | 375 | Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB); |
1471 | 375 | XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB); |
1472 | 375 | YAdjust = XAdjust * Sin / Cos; |
1473 | 375 | Filler->Switch[S1].YInit = Bucket16For(Switch1.y + YAdjust, YS, NB * 256); |
1474 | 375 | Filler->Switch[S1].Delta = Filler->StartDelta; |
1475 | | |
1476 | 375 | Filler->Switch[S2].Type = StartSwitch; |
1477 | 375 | Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB); |
1478 | 375 | Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB); |
1479 | 375 | XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB); |
1480 | 375 | YAdjust = XAdjust * Cos / Sin; |
1481 | 375 | Filler->Switch[S2].YInit = Bucket16For(Switch2.y - YAdjust, YS, NB * 256); |
1482 | 375 | Filler->Switch[S2].Delta = Filler->EndDelta; |
1483 | | |
1484 | 375 | Filler->Switch[2].Type = LastSwitch; |
1485 | 375 | Filler->Switch[2].X = Bucket8For(End.x, XS, NB); |
1486 | 375 | } |
1487 | 726 | } |
1488 | 1.97k | } /* InitTableFiller */ |
1489 | | |
1490 | | /*---------------------------------------------------------------------------*/ |
1491 | | #ifndef GRAPHICS_DISABLED |
1492 | | /** |
1493 | | * This routine renders the specified feature into ShapeList. |
1494 | | * @param window to add feature rendering to |
1495 | | * @param Feature feature to be rendered |
1496 | | * @param color color to use for feature rendering |
1497 | | * @return New shape list with rendering of Feature added. |
1498 | | * @note Globals: none |
1499 | | */ |
1500 | | void RenderIntFeature(ScrollView *window, const INT_FEATURE_STRUCT *Feature, |
1501 | | ScrollView::Color color) { |
1502 | | float X, Y, Dx, Dy, Length; |
1503 | | |
1504 | | window->Pen(color); |
1505 | | assert(Feature != nullptr); |
1506 | | assert(color != 0); |
1507 | | |
1508 | | X = Feature->X; |
1509 | | Y = Feature->Y; |
1510 | | Length = GetPicoFeatureLength() * 0.7 * INT_CHAR_NORM_RANGE; |
1511 | | // The -PI has no significant effect here, but the value of Theta is computed |
1512 | | // using BinaryAnglePlusPi in intfx.cpp. |
1513 | | Dx = (Length / 2.0) * cos((Feature->Theta / 256.0) * 2.0 * M_PI - M_PI); |
1514 | | Dy = (Length / 2.0) * sin((Feature->Theta / 256.0) * 2.0 * M_PI - M_PI); |
1515 | | |
1516 | | window->SetCursor(X, Y); |
1517 | | window->DrawTo(X + Dx, Y + Dy); |
1518 | | } /* RenderIntFeature */ |
1519 | | |
1520 | | /** |
1521 | | * This routine extracts the parameters of the specified |
1522 | | * proto from the class description and adds a rendering of |
1523 | | * the proto onto the ShapeList. |
1524 | | * |
1525 | | * @param window ScrollView instance |
1526 | | * @param Class class that proto is contained in |
1527 | | * @param ProtoId id of proto to be rendered |
1528 | | * @param color color to render proto in |
1529 | | * |
1530 | | * Globals: none |
1531 | | * |
1532 | | * @return New shape list with a rendering of one proto added. |
1533 | | */ |
1534 | | void RenderIntProto(ScrollView *window, INT_CLASS_STRUCT *Class, PROTO_ID ProtoId, |
1535 | | ScrollView::Color color) { |
1536 | | INT_PROTO_STRUCT *Proto; |
1537 | | int ProtoSetIndex; |
1538 | | int ProtoWordIndex; |
1539 | | float Length; |
1540 | | int Xmin, Xmax, Ymin, Ymax; |
1541 | | float X, Y, Dx, Dy; |
1542 | | uint32_t ProtoMask; |
1543 | | int Bucket; |
1544 | | |
1545 | | assert(ProtoId >= 0); |
1546 | | assert(Class != nullptr); |
1547 | | assert(ProtoId < Class->NumProtos); |
1548 | | assert(color != 0); |
1549 | | window->Pen(color); |
1550 | | |
1551 | | auto ProtoSet = Class->ProtoSets[SetForProto(ProtoId)]; |
1552 | | ProtoSetIndex = IndexForProto(ProtoId); |
1553 | | Proto = &(ProtoSet->Protos[ProtoSetIndex]); |
1554 | | Length = (Class->ProtoLengths[ProtoId] * GetPicoFeatureLength() * INT_CHAR_NORM_RANGE); |
1555 | | ProtoMask = PPrunerMaskFor(ProtoId); |
1556 | | ProtoWordIndex = PPrunerWordIndexFor(ProtoId); |
1557 | | |
1558 | | // find the x and y extent of the proto from the proto pruning table |
1559 | | Xmin = Ymin = NUM_PP_BUCKETS; |
1560 | | Xmax = Ymax = 0; |
1561 | | for (Bucket = 0; Bucket < NUM_PP_BUCKETS; Bucket++) { |
1562 | | if (ProtoMask & ProtoSet->ProtoPruner[PRUNER_X][Bucket][ProtoWordIndex]) { |
1563 | | UpdateRange(Bucket, &Xmin, &Xmax); |
1564 | | } |
1565 | | |
1566 | | if (ProtoMask & ProtoSet->ProtoPruner[PRUNER_Y][Bucket][ProtoWordIndex]) { |
1567 | | UpdateRange(Bucket, &Ymin, &Ymax); |
1568 | | } |
1569 | | } |
1570 | | X = (Xmin + Xmax + 1) / 2.0 * PROTO_PRUNER_SCALE; |
1571 | | Y = (Ymin + Ymax + 1) / 2.0 * PROTO_PRUNER_SCALE; |
1572 | | // The -PI has no significant effect here, but the value of Theta is computed |
1573 | | // using BinaryAnglePlusPi in intfx.cpp. |
1574 | | Dx = (Length / 2.0) * cos((Proto->Angle / 256.0) * 2.0 * M_PI - M_PI); |
1575 | | Dy = (Length / 2.0) * sin((Proto->Angle / 256.0) * 2.0 * M_PI - M_PI); |
1576 | | |
1577 | | window->SetCursor(X - Dx, Y - Dy); |
1578 | | window->DrawTo(X + Dx, Y + Dy); |
1579 | | } /* RenderIntProto */ |
1580 | | #endif |
1581 | | |
1582 | | #ifndef GRAPHICS_DISABLED |
1583 | | /** |
1584 | | * Initializes the int matcher window if it is not already |
1585 | | * initialized. |
1586 | | */ |
1587 | | void InitIntMatchWindowIfReqd() { |
1588 | | if (IntMatchWindow == nullptr) { |
1589 | | IntMatchWindow = CreateFeatureSpaceWindow("IntMatchWindow", 50, 200); |
1590 | | auto *popup_menu = new SVMenuNode(); |
1591 | | |
1592 | | popup_menu->AddChild("Debug Adapted classes", IDA_ADAPTIVE, "x", "Class to debug"); |
1593 | | popup_menu->AddChild("Debug Static classes", IDA_STATIC, "x", "Class to debug"); |
1594 | | popup_menu->AddChild("Debug Both", IDA_BOTH, "x", "Class to debug"); |
1595 | | popup_menu->AddChild("Debug Shape Index", IDA_SHAPE_INDEX, "0", "Index to debug"); |
1596 | | popup_menu->BuildMenu(IntMatchWindow, false); |
1597 | | } |
1598 | | } |
1599 | | |
1600 | | /** |
1601 | | * Initializes the proto display window if it is not already |
1602 | | * initialized. |
1603 | | */ |
1604 | | void InitProtoDisplayWindowIfReqd() { |
1605 | | if (ProtoDisplayWindow == nullptr) { |
1606 | | ProtoDisplayWindow = CreateFeatureSpaceWindow("ProtoDisplayWindow", 550, 200); |
1607 | | } |
1608 | | } |
1609 | | |
1610 | | /** |
1611 | | * Initializes the feature display window if it is not already |
1612 | | * initialized. |
1613 | | */ |
1614 | | void InitFeatureDisplayWindowIfReqd() { |
1615 | | if (FeatureDisplayWindow == nullptr) { |
1616 | | FeatureDisplayWindow = CreateFeatureSpaceWindow("FeatureDisplayWindow", 50, 700); |
1617 | | } |
1618 | | } |
1619 | | |
1620 | | /// Creates a window of the appropriate size for displaying elements |
1621 | | /// in feature space. |
1622 | | ScrollView *CreateFeatureSpaceWindow(const char *name, int xpos, int ypos) { |
1623 | | return new ScrollView(name, xpos, ypos, 520, 520, 260, 260, true); |
1624 | | } |
1625 | | #endif // !GRAPHICS_DISABLED |
1626 | | |
1627 | | } // namespace tesseract |