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 #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 #define OLD_MAX_NUM_CONFIGS 32
96 #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 #define CircularIncrement(i, r) (((i) < (r)-1) ? ((i)++) : ((i) = 0))
103
104 /** macro for mapping floats to ints without bounds checking */
105 #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 */
TruncateParam(float Param,int Min,int Max)180 static int TruncateParam(float Param, int Min, int Max) {
181 int result;
182 if (Param < Min) {
183 result = Min;
184 } else if (Param > Max) {
185 result = Max;
186 } else {
187 result = static_cast<int>(std::floor(Param));
188 }
189 return result;
190 }
191
192 /*-----------------------------------------------------------------------------
193 Public Code
194 -----------------------------------------------------------------------------*/
195 /// Builds a feature from an FCOORD for position with all the necessary
196 /// clipping and rounding.
INT_FEATURE_STRUCT(const FCOORD & pos,uint8_t theta)197 INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(const FCOORD &pos, uint8_t theta)
198 : X(ClipToRange<int16_t>(static_cast<int16_t>(pos.x() + 0.5), 0, 255))
199 , Y(ClipToRange<int16_t>(static_cast<int16_t>(pos.y() + 0.5), 0, 255))
200 , Theta(theta)
201 , CP_misses(0) {}
202 /** Builds a feature from ints with all the necessary clipping and casting. */
INT_FEATURE_STRUCT(int x,int y,int theta)203 INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(int x, int y, int theta)
204 : X(static_cast<uint8_t>(ClipToRange<int>(x, 0, UINT8_MAX)))
205 , Y(static_cast<uint8_t>(ClipToRange<int>(y, 0, UINT8_MAX)))
206 , Theta(static_cast<uint8_t>(ClipToRange<int>(theta, 0, UINT8_MAX)))
207 , 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 */
AddIntClass(INT_TEMPLATES_STRUCT * Templates,CLASS_ID ClassId,INT_CLASS_STRUCT * Class)220 void AddIntClass(INT_TEMPLATES_STRUCT *Templates, CLASS_ID ClassId, INT_CLASS_STRUCT *Class) {
221 int Pruner;
222
223 assert(LegalClassId(ClassId));
224 if (static_cast<unsigned>(ClassId) != Templates->NumClasses) {
225 fprintf(stderr,
226 "Please make sure that classes are added to templates"
227 " in increasing order of ClassIds\n");
228 exit(1);
229 }
230 ClassForClassId(Templates, ClassId) = Class;
231 Templates->NumClasses++;
232
233 if (Templates->NumClasses > MaxNumClassesIn(Templates)) {
234 Pruner = Templates->NumClassPruners++;
235 Templates->ClassPruners[Pruner] = new CLASS_PRUNER_STRUCT;
236 memset(Templates->ClassPruners[Pruner], 0, sizeof(CLASS_PRUNER_STRUCT));
237 }
238 } /* 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 */
AddIntConfig(INT_CLASS_STRUCT * Class)250 int AddIntConfig(INT_CLASS_STRUCT *Class) {
251 int Index;
252
253 assert(Class->NumConfigs < MAX_NUM_CONFIGS);
254
255 Index = Class->NumConfigs++;
256 Class->ConfigLengths[Index] = 0;
257 return Index;
258 } /* 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 */
AddIntProto(INT_CLASS_STRUCT * Class)270 int AddIntProto(INT_CLASS_STRUCT *Class) {
271 if (Class->NumProtos >= MAX_NUM_PROTOS) {
272 return (NO_PROTO);
273 }
274
275 int Index = Class->NumProtos++;
276
277 if (Class->NumProtos > MaxNumIntProtosIn(Class)) {
278 int ProtoSetId = Class->NumProtoSets++;
279 auto ProtoSet = new PROTO_SET_STRUCT;
280 Class->ProtoSets[ProtoSetId] = ProtoSet;
281 memset(ProtoSet, 0, sizeof(*ProtoSet));
282
283 /* reallocate space for the proto lengths and install in class */
284 Class->ProtoLengths.resize(MaxNumIntProtosIn(Class));
285 }
286
287 /* initialize proto so its length is zero and it isn't in any configs */
288 Class->ProtoLengths[Index] = 0;
289 auto Proto = ProtoForProtoId(Class, Index);
290 for (uint32_t *Word = Proto->Configs; Word < Proto->Configs + WERDS_PER_CONFIG_VEC; *Word++ = 0) {
291 }
292
293 return (Index);
294 }
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 */
AddProtoToClassPruner(PROTO_STRUCT * Proto,CLASS_ID ClassId,INT_TEMPLATES_STRUCT * Templates)306 void AddProtoToClassPruner(PROTO_STRUCT *Proto, CLASS_ID ClassId, INT_TEMPLATES_STRUCT *Templates)
307 #define MAX_LEVEL 2
308 {
309 CLASS_PRUNER_STRUCT *Pruner;
310 uint32_t ClassMask;
311 uint32_t ClassCount;
312 uint32_t WordIndex;
313 int Level;
314 float EndPad, SidePad, AnglePad;
315 TABLE_FILLER TableFiller;
316 FILL_SPEC FillSpec;
317
318 Pruner = CPrunerFor(Templates, ClassId);
319 WordIndex = CPrunerWordIndexFor(ClassId);
320 ClassMask = CPrunerMaskFor(MAX_LEVEL, ClassId);
321
322 for (Level = classify_num_cp_levels - 1; Level >= 0; Level--) {
323 GetCPPadsForLevel(Level, &EndPad, &SidePad, &AnglePad);
324 ClassCount = CPrunerMaskFor(Level, ClassId);
325 InitTableFiller(EndPad, SidePad, AnglePad, Proto, &TableFiller);
326
327 while (!FillerDone(&TableFiller)) {
328 GetNextFill(&TableFiller, &FillSpec);
329 DoFill(&FillSpec, Pruner, ClassMask, ClassCount, WordIndex);
330 }
331 }
332 } /* 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 */
AddProtoToProtoPruner(PROTO_STRUCT * Proto,int ProtoId,INT_CLASS_STRUCT * Class,bool debug)344 void AddProtoToProtoPruner(PROTO_STRUCT *Proto, int ProtoId, INT_CLASS_STRUCT *Class, bool debug) {
345 float X, Y, Length;
346 float Pad;
347
348 if (ProtoId >= Class->NumProtos) {
349 tprintf("AddProtoToProtoPruner:assert failed: %d < %d", ProtoId, Class->NumProtos);
350 }
351 assert(ProtoId < Class->NumProtos);
352
353 int Index = IndexForProto(ProtoId);
354 auto ProtoSet = Class->ProtoSets[SetForProto(ProtoId)];
355
356 float Angle = Proto->Angle;
357 #ifndef _WIN32
358 assert(!std::isnan(Angle));
359 #endif
360
361 FillPPCircularBits(ProtoSet->ProtoPruner[PRUNER_ANGLE], Index, Angle + ANGLE_SHIFT,
362 classify_pp_angle_pad / 360.0, debug);
363
364 Angle *= 2.0 * M_PI;
365 Length = Proto->Length;
366
367 X = Proto->X + X_SHIFT;
368 Pad = std::max(fabs(std::cos(Angle)) * (Length / 2.0 + classify_pp_end_pad * GetPicoFeatureLength()),
369 fabs(std::sin(Angle)) * (classify_pp_side_pad * GetPicoFeatureLength()));
370
371 FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_X], Index, X, Pad, debug);
372
373 Y = Proto->Y + Y_SHIFT;
374 Pad = std::max(fabs(std::sin(Angle)) * (Length / 2.0 + classify_pp_end_pad * GetPicoFeatureLength()),
375 fabs(std::cos(Angle)) * (classify_pp_side_pad * GetPicoFeatureLength()));
376
377 FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_Y], Index, Y, Pad, debug);
378 } /* 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 */
Bucket8For(float param,float offset,int num_buckets)385 uint8_t Bucket8For(float param, float offset, int num_buckets) {
386 int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
387 return static_cast<uint8_t>(ClipToRange<int>(bucket, 0, num_buckets - 1));
388 }
Bucket16For(float param,float offset,int num_buckets)389 uint16_t Bucket16For(float param, float offset, int num_buckets) {
390 int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
391 return static_cast<uint16_t>(ClipToRange<int>(bucket, 0, num_buckets - 1));
392 }
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 */
CircBucketFor(float param,float offset,int num_buckets)399 uint8_t CircBucketFor(float param, float offset, int num_buckets) {
400 int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
401 return static_cast<uint8_t>(Modulo(bucket, num_buckets));
402 } /* 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 */
UpdateMatchDisplay()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 */
ConvertConfig(BIT_VECTOR Config,int ConfigId,INT_CLASS_STRUCT * Class)430 void ConvertConfig(BIT_VECTOR Config, int ConfigId, INT_CLASS_STRUCT *Class) {
431 int ProtoId;
432 INT_PROTO_STRUCT *Proto;
433 int TotalLength;
434
435 for (ProtoId = 0, TotalLength = 0; ProtoId < Class->NumProtos; ProtoId++) {
436 if (test_bit(Config, ProtoId)) {
437 Proto = ProtoForProtoId(Class, ProtoId);
438 SET_BIT(Proto->Configs, ConfigId);
439 TotalLength += Class->ProtoLengths[ProtoId];
440 }
441 }
442 Class->ConfigLengths[ConfigId] = TotalLength;
443 } /* 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 */
ConvertProto(PROTO_STRUCT * Proto,int ProtoId,INT_CLASS_STRUCT * Class)452 void Classify::ConvertProto(PROTO_STRUCT *Proto, int ProtoId, INT_CLASS_STRUCT *Class) {
453 assert(ProtoId < Class->NumProtos);
454
455 INT_PROTO_STRUCT *P = ProtoForProtoId(Class, ProtoId);
456
457 float Param = Proto->A * 128;
458 P->A = TruncateParam(Param, -128, 127);
459
460 Param = -Proto->B * 256;
461 P->B = TruncateParam(Param, 0, 255);
462
463 Param = Proto->C * 128;
464 P->C = TruncateParam(Param, -128, 127);
465
466 Param = Proto->Angle * 256;
467 if (Param < 0 || Param >= 256) {
468 P->Angle = 0;
469 } else {
470 P->Angle = static_cast<uint8_t>(Param);
471 }
472
473 /* round proto length to nearest integer number of pico-features */
474 Param = (Proto->Length / GetPicoFeatureLength()) + 0.5;
475 Class->ProtoLengths[ProtoId] = TruncateParam(Param, 1, 255);
476 if (classify_learning_debug_level >= 2) {
477 tprintf("Converted ffeat to (A=%d,B=%d,C=%d,L=%d)", P->A, P->B, P->C,
478 Class->ProtoLengths[ProtoId]);
479 }
480 } /* 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 */
CreateIntTemplates(CLASSES FloatProtos,const UNICHARSET & target_unicharset)490 INT_TEMPLATES_STRUCT *Classify::CreateIntTemplates(CLASSES FloatProtos,
491 const UNICHARSET &target_unicharset) {
492 CLASS_TYPE FClass;
493 INT_CLASS_STRUCT *IClass;
494 int ProtoId;
495 int ConfigId;
496
497 auto IntTemplates = new INT_TEMPLATES_STRUCT;
498
499 for (unsigned ClassId = 0; ClassId < target_unicharset.size(); ClassId++) {
500 FClass = &(FloatProtos[ClassId]);
501 if (FClass->NumProtos == 0 && FClass->NumConfigs == 0 &&
502 strcmp(target_unicharset.id_to_unichar(ClassId), " ") != 0) {
503 tprintf("Warning: no protos/configs for %s in CreateIntTemplates()\n",
504 target_unicharset.id_to_unichar(ClassId));
505 }
506 assert(UnusedClassIdIn(IntTemplates, ClassId));
507 IClass = new INT_CLASS_STRUCT(FClass->NumProtos, FClass->NumConfigs);
508 FontSet fs{FClass->font_set.size()};
509 for (unsigned i = 0; i < fs.size(); ++i) {
510 fs[i] = FClass->font_set.at(i);
511 }
512 IClass->font_set_id = this->fontset_table_.push_back(fs);
513 AddIntClass(IntTemplates, ClassId, IClass);
514
515 for (ProtoId = 0; ProtoId < FClass->NumProtos; ProtoId++) {
516 AddIntProto(IClass);
517 ConvertProto(ProtoIn(FClass, ProtoId), ProtoId, IClass);
518 AddProtoToProtoPruner(ProtoIn(FClass, ProtoId), ProtoId, IClass,
519 classify_learning_debug_level >= 2);
520 AddProtoToClassPruner(ProtoIn(FClass, ProtoId), ClassId, IntTemplates);
521 }
522
523 for (ConfigId = 0; ConfigId < FClass->NumConfigs; ConfigId++) {
524 AddIntConfig(IClass);
525 ConvertConfig(FClass->Configurations[ConfigId], ConfigId, IClass);
526 }
527 }
528 return (IntTemplates);
529 } /* CreateIntTemplates */
530
531 #ifndef GRAPHICS_DISABLED
532 /**
533 * This routine renders the specified feature into a
534 * global display list.
535 *
536 * Globals:
537 * - FeatureShapes global display list for features
538 * @param Feature pico-feature to be displayed
539 * @param Evidence best evidence for this feature (0-1)
540 */
DisplayIntFeature(const INT_FEATURE_STRUCT * Feature,float Evidence)541 void DisplayIntFeature(const INT_FEATURE_STRUCT *Feature, float Evidence) {
542 ScrollView::Color color = GetMatchColorFor(Evidence);
543 RenderIntFeature(IntMatchWindow, Feature, color);
544 if (FeatureDisplayWindow) {
545 RenderIntFeature(FeatureDisplayWindow, Feature, color);
546 }
547 } /* DisplayIntFeature */
548
549 /**
550 * This routine renders the specified proto into a
551 * global display list.
552 *
553 * Globals:
554 * - ProtoShapes global display list for protos
555 * @param Class class to take proto from
556 * @param ProtoId id of proto in Class to be displayed
557 * @param Evidence total evidence for proto (0-1)
558 */
DisplayIntProto(INT_CLASS_STRUCT * Class,PROTO_ID ProtoId,float Evidence)559 void DisplayIntProto(INT_CLASS_STRUCT *Class, PROTO_ID ProtoId, float Evidence) {
560 ScrollView::Color color = GetMatchColorFor(Evidence);
561 RenderIntProto(IntMatchWindow, Class, ProtoId, color);
562 if (ProtoDisplayWindow) {
563 RenderIntProto(ProtoDisplayWindow, Class, ProtoId, color);
564 }
565 } /* DisplayIntProto */
566 #endif
567
568 /// This constructor creates a new integer class data structure
569 /// and returns it. Sufficient space is allocated
570 /// to handle the specified number of protos and configs.
571 /// @param MaxNumProtos number of protos to allocate space for
572 /// @param MaxNumConfigs number of configs to allocate space for
INT_CLASS_STRUCT(int MaxNumProtos,int MaxNumConfigs)573 INT_CLASS_STRUCT::INT_CLASS_STRUCT(int MaxNumProtos, int MaxNumConfigs) :
574 NumProtos(0),
575 NumProtoSets((MaxNumProtos + PROTOS_PER_PROTO_SET - 1) / PROTOS_PER_PROTO_SET),
576 NumConfigs(0),
577 ProtoLengths(MaxNumIntProtosIn(this))
578 {
579 assert(MaxNumConfigs <= MAX_NUM_CONFIGS);
580 assert(NumProtoSets <= MAX_NUM_PROTO_SETS);
581
582 for (int i = 0; i < NumProtoSets; i++) {
583 /* allocate space for a proto set, install in class, and initialize */
584 auto ProtoSet = new PROTO_SET_STRUCT;
585 memset(ProtoSet, 0, sizeof(*ProtoSet));
586 ProtoSets[i] = ProtoSet;
587
588 /* allocate space for the proto lengths and install in class */
589 }
590 memset(ConfigLengths, 0, sizeof(ConfigLengths));
591 }
592
~INT_CLASS_STRUCT()593 INT_CLASS_STRUCT::~INT_CLASS_STRUCT() {
594 for (int i = 0; i < NumProtoSets; i++) {
595 delete ProtoSets[i];
596 }
597 }
598
599 /// This constructor allocates a new set of integer templates
600 /// initialized to hold 0 classes.
INT_TEMPLATES_STRUCT()601 INT_TEMPLATES_STRUCT::INT_TEMPLATES_STRUCT() {
602 NumClasses = 0;
603 NumClassPruners = 0;
604
605 for (int i = 0; i < MAX_NUM_CLASSES; i++) {
606 ClassForClassId(this, i) = nullptr;
607 }
608 }
609
~INT_TEMPLATES_STRUCT()610 INT_TEMPLATES_STRUCT::~INT_TEMPLATES_STRUCT() {
611 for (unsigned i = 0; i < NumClasses; i++) {
612 delete Class[i];
613 }
614 for (unsigned i = 0; i < NumClassPruners; i++) {
615 delete ClassPruners[i];
616 }
617 }
618
619 /**
620 * This routine reads a set of integer templates from
621 * File. File must already be open and must be in the
622 * correct binary format.
623 * @param fp open file to read templates from
624 * @return Pointer to integer templates read from File.
625 * @note Globals: none
626 */
ReadIntTemplates(TFile * fp)627 INT_TEMPLATES_STRUCT *Classify::ReadIntTemplates(TFile *fp) {
628 int j, w, x, y, z;
629 INT_TEMPLATES_STRUCT *Templates;
630 CLASS_PRUNER_STRUCT *Pruner;
631 INT_CLASS_STRUCT *Class;
632
633 /* variables for conversion from older inttemp formats */
634 int b, bit_number, last_cp_bit_number, new_b, new_i, new_w;
635 CLASS_ID class_id, max_class_id;
636 std::vector<CLASS_ID> ClassIdFor(MAX_NUM_CLASSES);
637 std::vector<CLASS_PRUNER_STRUCT *> TempClassPruner(MAX_NUM_CLASS_PRUNERS);
638 uint32_t SetBitsForMask = // word with NUM_BITS_PER_CLASS
639 (1 << NUM_BITS_PER_CLASS) - 1; // set starting at bit 0
640 uint32_t Mask, NewMask, ClassBits;
641 unsigned MaxNumConfigs = MAX_NUM_CONFIGS;
642 unsigned WerdsPerConfigVec = WERDS_PER_CONFIG_VEC;
643
644 /* first read the high level template struct */
645 Templates = new INT_TEMPLATES_STRUCT;
646 // Read Templates in parts for 64 bit compatibility.
647 uint32_t unicharset_size;
648 if (fp->FReadEndian(&unicharset_size, sizeof(unicharset_size), 1) != 1) {
649 tprintf("Bad read of inttemp!\n");
650 }
651 int32_t version_id = 0;
652 if (fp->FReadEndian(&version_id, sizeof(version_id), 1) != 1 ||
653 fp->FReadEndian(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1) != 1) {
654 tprintf("Bad read of inttemp!\n");
655 }
656 if (version_id < 0) {
657 // This file has a version id!
658 version_id = -version_id;
659 if (fp->FReadEndian(&Templates->NumClasses, sizeof(Templates->NumClasses), 1) != 1) {
660 tprintf("Bad read of inttemp!\n");
661 }
662 } else {
663 Templates->NumClasses = version_id;
664 }
665
666 if (version_id < 3) {
667 MaxNumConfigs = OLD_MAX_NUM_CONFIGS;
668 WerdsPerConfigVec = OLD_WERDS_PER_CONFIG_VEC;
669 }
670
671 if (version_id < 2) {
672 std::vector<int16_t> IndexFor(MAX_NUM_CLASSES);
673 if (fp->FReadEndian(&IndexFor[0], sizeof(IndexFor[0]), unicharset_size) != unicharset_size) {
674 tprintf("Bad read of inttemp!\n");
675 }
676 if (fp->FReadEndian(&ClassIdFor[0], sizeof(ClassIdFor[0]), Templates->NumClasses) !=
677 Templates->NumClasses) {
678 tprintf("Bad read of inttemp!\n");
679 }
680 }
681
682 /* then read in the class pruners */
683 const unsigned kNumBuckets = NUM_CP_BUCKETS * NUM_CP_BUCKETS * NUM_CP_BUCKETS * WERDS_PER_CP_VECTOR;
684 for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
685 Pruner = new CLASS_PRUNER_STRUCT;
686 if (fp->FReadEndian(Pruner, sizeof(Pruner->p[0][0][0][0]), kNumBuckets) != kNumBuckets) {
687 tprintf("Bad read of inttemp!\n");
688 }
689 if (version_id < 2) {
690 TempClassPruner[i] = Pruner;
691 } else {
692 Templates->ClassPruners[i] = Pruner;
693 }
694 }
695
696 /* fix class pruners if they came from an old version of inttemp */
697 if (version_id < 2) {
698 // Allocate enough class pruners to cover all the class ids.
699 max_class_id = 0;
700 for (unsigned i = 0; i < Templates->NumClasses; i++) {
701 if (ClassIdFor[i] > max_class_id) {
702 max_class_id = ClassIdFor[i];
703 }
704 }
705 for (int i = 0; i <= CPrunerIdFor(max_class_id); i++) {
706 Templates->ClassPruners[i] = new CLASS_PRUNER_STRUCT;
707 memset(Templates->ClassPruners[i], 0, sizeof(CLASS_PRUNER_STRUCT));
708 }
709 // Convert class pruners from the old format (indexed by class index)
710 // to the new format (indexed by class id).
711 last_cp_bit_number = NUM_BITS_PER_CLASS * Templates->NumClasses - 1;
712 for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
713 for (x = 0; x < NUM_CP_BUCKETS; x++) {
714 for (y = 0; y < NUM_CP_BUCKETS; y++) {
715 for (z = 0; z < NUM_CP_BUCKETS; z++) {
716 for (w = 0; w < WERDS_PER_CP_VECTOR; w++) {
717 if (TempClassPruner[i]->p[x][y][z][w] == 0) {
718 continue;
719 }
720 for (b = 0; b < BITS_PER_WERD; b += NUM_BITS_PER_CLASS) {
721 bit_number = i * BITS_PER_CP_VECTOR + w * BITS_PER_WERD + b;
722 if (bit_number > last_cp_bit_number) {
723 break; // the rest of the bits in this word are not used
724 }
725 class_id = ClassIdFor[bit_number / NUM_BITS_PER_CLASS];
726 // Single out NUM_BITS_PER_CLASS bits relating to class_id.
727 Mask = SetBitsForMask << b;
728 ClassBits = TempClassPruner[i]->p[x][y][z][w] & Mask;
729 // Move these bits to the new position in which they should
730 // appear (indexed corresponding to the class_id).
731 new_i = CPrunerIdFor(class_id);
732 new_w = CPrunerWordIndexFor(class_id);
733 new_b = CPrunerBitIndexFor(class_id) * NUM_BITS_PER_CLASS;
734 if (new_b > b) {
735 ClassBits <<= (new_b - b);
736 } else {
737 ClassBits >>= (b - new_b);
738 }
739 // Copy bits relating to class_id to the correct position
740 // in Templates->ClassPruner.
741 NewMask = SetBitsForMask << new_b;
742 Templates->ClassPruners[new_i]->p[x][y][z][new_w] &= ~NewMask;
743 Templates->ClassPruners[new_i]->p[x][y][z][new_w] |= ClassBits;
744 }
745 }
746 }
747 }
748 }
749 }
750 for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
751 delete TempClassPruner[i];
752 }
753 }
754
755 /* then read in each class */
756 for (unsigned i = 0; i < Templates->NumClasses; i++) {
757 /* first read in the high level struct for the class */
758 Class = new INT_CLASS_STRUCT;
759 if (fp->FReadEndian(&Class->NumProtos, sizeof(Class->NumProtos), 1) != 1 ||
760 fp->FRead(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1) != 1 ||
761 fp->FRead(&Class->NumConfigs, sizeof(Class->NumConfigs), 1) != 1) {
762 tprintf("Bad read of inttemp!\n");
763 }
764 if (version_id == 0) {
765 // Only version 0 writes 5 pointless pointers to the file.
766 for (j = 0; j < 5; ++j) {
767 int32_t junk;
768 if (fp->FRead(&junk, sizeof(junk), 1) != 1) {
769 tprintf("Bad read of inttemp!\n");
770 }
771 }
772 }
773 unsigned num_configs = version_id < 4 ? MaxNumConfigs : Class->NumConfigs;
774 ASSERT_HOST(num_configs <= MaxNumConfigs);
775 if (fp->FReadEndian(Class->ConfigLengths, sizeof(uint16_t), num_configs) != num_configs) {
776 tprintf("Bad read of inttemp!\n");
777 }
778 if (version_id < 2) {
779 ClassForClassId(Templates, ClassIdFor[i]) = Class;
780 } else {
781 ClassForClassId(Templates, i) = Class;
782 }
783
784 /* then read in the proto lengths */
785 Class->ProtoLengths.clear();
786 if (MaxNumIntProtosIn(Class) > 0) {
787 Class->ProtoLengths.resize(MaxNumIntProtosIn(Class));
788 if (fp->FRead(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class)) !=
789 MaxNumIntProtosIn(Class)) {
790 tprintf("Bad read of inttemp!\n");
791 }
792 }
793
794 /* then read in the proto sets */
795 for (j = 0; j < Class->NumProtoSets; j++) {
796 auto ProtoSet = new PROTO_SET_STRUCT;
797 unsigned num_buckets = NUM_PP_PARAMS * NUM_PP_BUCKETS * WERDS_PER_PP_VECTOR;
798 if (fp->FReadEndian(&ProtoSet->ProtoPruner, sizeof(ProtoSet->ProtoPruner[0][0][0]),
799 num_buckets) != num_buckets) {
800 tprintf("Bad read of inttemp!\n");
801 }
802 for (x = 0; x < PROTOS_PER_PROTO_SET; x++) {
803 if (fp->FRead(&ProtoSet->Protos[x].A, sizeof(ProtoSet->Protos[x].A), 1) != 1 ||
804 fp->FRead(&ProtoSet->Protos[x].B, sizeof(ProtoSet->Protos[x].B), 1) != 1 ||
805 fp->FRead(&ProtoSet->Protos[x].C, sizeof(ProtoSet->Protos[x].C), 1) != 1 ||
806 fp->FRead(&ProtoSet->Protos[x].Angle, sizeof(ProtoSet->Protos[x].Angle), 1) != 1) {
807 tprintf("Bad read of inttemp!\n");
808 }
809 if (fp->FReadEndian(&ProtoSet->Protos[x].Configs, sizeof(ProtoSet->Protos[x].Configs[0]),
810 WerdsPerConfigVec) != WerdsPerConfigVec) {
811 tprintf("Bad read of inttemp!\n");
812 }
813 }
814 Class->ProtoSets[j] = ProtoSet;
815 }
816 if (version_id < 4) {
817 Class->font_set_id = -1;
818 } else {
819 fp->FReadEndian(&Class->font_set_id, sizeof(Class->font_set_id), 1);
820 }
821 }
822
823 if (version_id < 2) {
824 /* add an empty nullptr class with class id 0 */
825 assert(UnusedClassIdIn(Templates, 0));
826 ClassForClassId(Templates, 0) = new INT_CLASS_STRUCT(1, 1);
827 ClassForClassId(Templates, 0)->font_set_id = -1;
828 Templates->NumClasses++;
829 /* make sure the classes are contiguous */
830 for (unsigned i = 0; i < MAX_NUM_CLASSES; i++) {
831 if (i < Templates->NumClasses) {
832 if (ClassForClassId(Templates, i) == nullptr) {
833 fprintf(stderr, "Non-contiguous class ids in inttemp\n");
834 exit(1);
835 }
836 } else {
837 if (ClassForClassId(Templates, i) != nullptr) {
838 fprintf(stderr, "Class id %u exceeds NumClassesIn (Templates) %u\n", i,
839 Templates->NumClasses);
840 exit(1);
841 }
842 }
843 }
844 }
845 if (version_id >= 4) {
846 using namespace std::placeholders; // for _1, _2
847 this->fontinfo_table_.read(fp, std::bind(read_info, _1, _2));
848 if (version_id >= 5) {
849 this->fontinfo_table_.read(fp, std::bind(read_spacing_info, _1, _2));
850 }
851 this->fontset_table_.read(fp, [](auto *f, auto *fs) { return f->DeSerialize(*fs); } );
852 }
853
854 return (Templates);
855 } /* ReadIntTemplates */
856
857 #ifndef GRAPHICS_DISABLED
858 /**
859 * This routine sends the shapes in the global display
860 * lists to the match debugger window.
861 *
862 * Globals:
863 * - FeatureShapes display list containing feature matches
864 * - ProtoShapes display list containing proto matches
865 */
ShowMatchDisplay()866 void Classify::ShowMatchDisplay() {
867 InitIntMatchWindowIfReqd();
868 if (ProtoDisplayWindow) {
869 ProtoDisplayWindow->Clear();
870 }
871 if (FeatureDisplayWindow) {
872 FeatureDisplayWindow->Clear();
873 }
874 ClearFeatureSpaceWindow(static_cast<NORM_METHOD>(static_cast<int>(classify_norm_method)),
875 IntMatchWindow);
876 IntMatchWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
877 if (ProtoDisplayWindow) {
878 ProtoDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
879 }
880 if (FeatureDisplayWindow) {
881 FeatureDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
882 }
883 } /* ShowMatchDisplay */
884
885 /// Clears the given window and draws the featurespace guides for the
886 /// appropriate normalization method.
ClearFeatureSpaceWindow(NORM_METHOD norm_method,ScrollView * window)887 void ClearFeatureSpaceWindow(NORM_METHOD norm_method, ScrollView *window) {
888 window->Clear();
889
890 window->Pen(ScrollView::GREY);
891 // Draw the feature space limit rectangle.
892 window->Rectangle(0, 0, INT_MAX_X, INT_MAX_Y);
893 if (norm_method == baseline) {
894 window->SetCursor(0, INT_DESCENDER);
895 window->DrawTo(INT_MAX_X, INT_DESCENDER);
896 window->SetCursor(0, INT_BASELINE);
897 window->DrawTo(INT_MAX_X, INT_BASELINE);
898 window->SetCursor(0, INT_XHEIGHT);
899 window->DrawTo(INT_MAX_X, INT_XHEIGHT);
900 window->SetCursor(0, INT_CAPHEIGHT);
901 window->DrawTo(INT_MAX_X, INT_CAPHEIGHT);
902 } else {
903 window->Rectangle(INT_XCENTER - INT_XRADIUS, INT_YCENTER - INT_YRADIUS,
904 INT_XCENTER + INT_XRADIUS, INT_YCENTER + INT_YRADIUS);
905 }
906 }
907 #endif
908
909 /**
910 * This routine writes Templates to File. The format
911 * is an efficient binary format. File must already be open
912 * for writing.
913 * @param File open file to write templates to
914 * @param Templates templates to save into File
915 * @param target_unicharset the UNICHARSET to use
916 */
WriteIntTemplates(FILE * File,INT_TEMPLATES_STRUCT * Templates,const UNICHARSET & target_unicharset)917 void Classify::WriteIntTemplates(FILE *File, INT_TEMPLATES_STRUCT *Templates,
918 const UNICHARSET &target_unicharset) {
919 INT_CLASS_STRUCT *Class;
920 auto unicharset_size = target_unicharset.size();
921 int version_id = -5; // When negated by the reader -1 becomes +1 etc.
922
923 if (Templates->NumClasses != unicharset_size) {
924 tprintf(
925 "Warning: executing WriteIntTemplates() with %d classes in"
926 " Templates, while target_unicharset size is %zu\n",
927 Templates->NumClasses, unicharset_size);
928 }
929
930 /* first write the high level template struct */
931 fwrite(&unicharset_size, sizeof(unicharset_size), 1, File);
932 fwrite(&version_id, sizeof(version_id), 1, File);
933 fwrite(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1, File);
934 fwrite(&Templates->NumClasses, sizeof(Templates->NumClasses), 1, File);
935
936 /* then write out the class pruners */
937 for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
938 fwrite(Templates->ClassPruners[i], sizeof(CLASS_PRUNER_STRUCT), 1, File);
939 }
940
941 /* then write out each class */
942 for (unsigned i = 0; i < Templates->NumClasses; i++) {
943 Class = Templates->Class[i];
944
945 /* first write out the high level struct for the class */
946 fwrite(&Class->NumProtos, sizeof(Class->NumProtos), 1, File);
947 fwrite(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1, File);
948 ASSERT_HOST(Class->NumConfigs == this->fontset_table_.at(Class->font_set_id).size());
949 fwrite(&Class->NumConfigs, sizeof(Class->NumConfigs), 1, File);
950 for (int j = 0; j < Class->NumConfigs; ++j) {
951 fwrite(&Class->ConfigLengths[j], sizeof(uint16_t), 1, File);
952 }
953
954 /* then write out the proto lengths */
955 if (MaxNumIntProtosIn(Class) > 0) {
956 fwrite(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class), File);
957 }
958
959 /* then write out the proto sets */
960 for (int j = 0; j < Class->NumProtoSets; j++) {
961 fwrite(Class->ProtoSets[j], sizeof(PROTO_SET_STRUCT), 1, File);
962 }
963
964 /* then write the fonts info */
965 fwrite(&Class->font_set_id, sizeof(int), 1, File);
966 }
967
968 /* Write the fonts info tables */
969 using namespace std::placeholders; // for _1, _2
970 this->fontinfo_table_.write(File, std::bind(write_info, _1, _2));
971 this->fontinfo_table_.write(File, std::bind(write_spacing_info, _1, _2));
972 this->fontset_table_.write(File, std::bind(write_set, _1, _2));
973 } /* WriteIntTemplates */
974
975 /*-----------------------------------------------------------------------------
976 Private Code
977 -----------------------------------------------------------------------------*/
978 /**
979 * This routine returns the parameter value which
980 * corresponds to the beginning of the specified bucket.
981 * The bucket number should have been generated using the
982 * BucketFor() function with parameters Offset and NumBuckets.
983 * @param Bucket bucket whose start is to be computed
984 * @param Offset offset used to map params to buckets
985 * @param NumBuckets total number of buckets
986 * @return Param value corresponding to start position of Bucket.
987 * @note Globals: none
988 */
BucketStart(int Bucket,float Offset,int NumBuckets)989 float BucketStart(int Bucket, float Offset, int NumBuckets) {
990 return static_cast<float>(Bucket) / NumBuckets - Offset;
991
992 } /* BucketStart */
993
994 /**
995 * This routine returns the parameter value which
996 * corresponds to the end of the specified bucket.
997 * The bucket number should have been generated using the
998 * BucketFor() function with parameters Offset and NumBuckets.
999 * @param Bucket bucket whose end is to be computed
1000 * @param Offset offset used to map params to buckets
1001 * @param NumBuckets total number of buckets
1002 * @return Param value corresponding to end position of Bucket.
1003 * @note Globals: none
1004 */
BucketEnd(int Bucket,float Offset,int NumBuckets)1005 float BucketEnd(int Bucket, float Offset, int NumBuckets) {
1006 return static_cast<float>(Bucket + 1) / NumBuckets - Offset;
1007 } /* BucketEnd */
1008
1009 /**
1010 * This routine fills in the section of a class pruner
1011 * corresponding to a single x value for a single proto of
1012 * a class.
1013 * @param FillSpec specifies which bits to fill in pruner
1014 * @param Pruner class pruner to be filled
1015 * @param ClassMask indicates which bits to change in each word
1016 * @param ClassCount indicates what to change bits to
1017 * @param WordIndex indicates which word to change
1018 */
DoFill(FILL_SPEC * FillSpec,CLASS_PRUNER_STRUCT * Pruner,uint32_t ClassMask,uint32_t ClassCount,uint32_t WordIndex)1019 void DoFill(FILL_SPEC *FillSpec, CLASS_PRUNER_STRUCT *Pruner, uint32_t ClassMask,
1020 uint32_t ClassCount, uint32_t WordIndex) {
1021 int X, Y, Angle;
1022 uint32_t OldWord;
1023
1024 X = FillSpec->X;
1025 if (X < 0) {
1026 X = 0;
1027 }
1028 if (X >= NUM_CP_BUCKETS) {
1029 X = NUM_CP_BUCKETS - 1;
1030 }
1031
1032 if (FillSpec->YStart < 0) {
1033 FillSpec->YStart = 0;
1034 }
1035 if (FillSpec->YEnd >= NUM_CP_BUCKETS) {
1036 FillSpec->YEnd = NUM_CP_BUCKETS - 1;
1037 }
1038
1039 for (Y = FillSpec->YStart; Y <= FillSpec->YEnd; Y++) {
1040 for (Angle = FillSpec->AngleStart;; CircularIncrement(Angle, NUM_CP_BUCKETS)) {
1041 OldWord = Pruner->p[X][Y][Angle][WordIndex];
1042 if (ClassCount > (OldWord & ClassMask)) {
1043 OldWord &= ~ClassMask;
1044 OldWord |= ClassCount;
1045 Pruner->p[X][Y][Angle][WordIndex] = OldWord;
1046 }
1047 if (Angle == FillSpec->AngleEnd) {
1048 break;
1049 }
1050 }
1051 }
1052 } /* DoFill */
1053
1054 /**
1055 * Return true if the specified table filler is done, i.e.
1056 * if it has no more lines to fill.
1057 * @param Filler table filler to check if done
1058 * @return true if no more lines to fill, false otherwise.
1059 * @note Globals: none
1060 */
FillerDone(TABLE_FILLER * Filler)1061 bool FillerDone(TABLE_FILLER *Filler) {
1062 FILL_SWITCH *Next;
1063
1064 Next = &(Filler->Switch[Filler->NextSwitch]);
1065
1066 return Filler->X > Next->X && Next->Type == LastSwitch;
1067
1068 } /* FillerDone */
1069
1070 /**
1071 * This routine sets Bit in each bit vector whose
1072 * bucket lies within the range Center +- Spread. The fill
1073 * is done for a circular dimension, i.e. bucket 0 is adjacent
1074 * to the last bucket. It is assumed that Center and Spread
1075 * are expressed in a circular coordinate system whose range
1076 * is 0 to 1.
1077 * @param ParamTable table of bit vectors, one per param bucket
1078 * @param Bit bit position in vectors to be filled
1079 * @param Center center of filled area
1080 * @param Spread spread of filled area
1081 * @param debug debug flag
1082 */
FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR],int Bit,float Center,float Spread,bool debug)1083 void FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
1084 float Center, float Spread, bool debug) {
1085 int i, FirstBucket, LastBucket;
1086
1087 if (Spread > 0.5) {
1088 Spread = 0.5;
1089 }
1090
1091 FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
1092 if (FirstBucket < 0) {
1093 FirstBucket += NUM_PP_BUCKETS;
1094 }
1095
1096 LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
1097 if (LastBucket >= NUM_PP_BUCKETS) {
1098 LastBucket -= NUM_PP_BUCKETS;
1099 }
1100 if (debug) {
1101 tprintf("Circular fill from %d to %d", FirstBucket, LastBucket);
1102 }
1103 for (i = FirstBucket; true; CircularIncrement(i, NUM_PP_BUCKETS)) {
1104 SET_BIT(ParamTable[i], Bit);
1105
1106 /* exit loop after we have set the bit for the last bucket */
1107 if (i == LastBucket) {
1108 break;
1109 }
1110 }
1111
1112 } /* FillPPCircularBits */
1113
1114 /**
1115 * This routine sets Bit in each bit vector whose
1116 * bucket lies within the range Center +- Spread. The fill
1117 * is done for a linear dimension, i.e. there is no wrap-around
1118 * for this dimension. It is assumed that Center and Spread
1119 * are expressed in a linear coordinate system whose range
1120 * is approximately 0 to 1. Values outside this range will
1121 * be clipped.
1122 * @param ParamTable table of bit vectors, one per param bucket
1123 * @param Bit bit number being filled
1124 * @param Center center of filled area
1125 * @param Spread spread of filled area
1126 * @param debug debug flag
1127 */
FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR],int Bit,float Center,float Spread,bool debug)1128 void FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
1129 float Center, float Spread, bool debug) {
1130 int i, FirstBucket, LastBucket;
1131
1132 FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
1133 if (FirstBucket < 0) {
1134 FirstBucket = 0;
1135 }
1136
1137 LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
1138 if (LastBucket >= NUM_PP_BUCKETS) {
1139 LastBucket = NUM_PP_BUCKETS - 1;
1140 }
1141
1142 if (debug) {
1143 tprintf("Linear fill from %d to %d", FirstBucket, LastBucket);
1144 }
1145 for (i = FirstBucket; i <= LastBucket; i++) {
1146 SET_BIT(ParamTable[i], Bit);
1147 }
1148
1149 } /* FillPPLinearBits */
1150
1151 /*---------------------------------------------------------------------------*/
1152 #ifndef GRAPHICS_DISABLED
1153 /**
1154 * This routine prompts the user with Prompt and waits
1155 * for the user to enter something in the debug window.
1156 * @param Prompt prompt to print while waiting for input from window
1157 * @param adaptive_on
1158 * @param pretrained_on
1159 * @param shape_id
1160 * @return Character entered in the debug window.
1161 * @note Globals: none
1162 */
GetClassToDebug(const char * Prompt,bool * adaptive_on,bool * pretrained_on,int * shape_id)1163 CLASS_ID Classify::GetClassToDebug(const char *Prompt, bool *adaptive_on, bool *pretrained_on,
1164 int *shape_id) {
1165 tprintf("%s\n", Prompt);
1166 SVEvent *ev;
1167 SVEventType ev_type;
1168 int unichar_id = INVALID_UNICHAR_ID;
1169 // Wait until a click or popup event.
1170 do {
1171 ev = IntMatchWindow->AwaitEvent(SVET_ANY);
1172 ev_type = ev->type;
1173 if (ev_type == SVET_POPUP) {
1174 if (ev->command_id == IDA_SHAPE_INDEX) {
1175 if (shape_table_ != nullptr) {
1176 *shape_id = atoi(ev->parameter);
1177 *adaptive_on = false;
1178 *pretrained_on = true;
1179 if (*shape_id >= 0 && static_cast<unsigned>(*shape_id) < shape_table_->NumShapes()) {
1180 int font_id;
1181 shape_table_->GetFirstUnicharAndFont(*shape_id, &unichar_id, &font_id);
1182 tprintf("Shape %d, first unichar=%d, font=%d\n", *shape_id, unichar_id, font_id);
1183 return unichar_id;
1184 }
1185 tprintf("Shape index '%s' not found in shape table\n", ev->parameter);
1186 } else {
1187 tprintf("No shape table loaded!\n");
1188 }
1189 } else {
1190 if (unicharset.contains_unichar(ev->parameter)) {
1191 unichar_id = unicharset.unichar_to_id(ev->parameter);
1192 if (ev->command_id == IDA_ADAPTIVE) {
1193 *adaptive_on = true;
1194 *pretrained_on = false;
1195 *shape_id = -1;
1196 } else if (ev->command_id == IDA_STATIC) {
1197 *adaptive_on = false;
1198 *pretrained_on = true;
1199 } else {
1200 *adaptive_on = true;
1201 *pretrained_on = true;
1202 }
1203 if (ev->command_id == IDA_ADAPTIVE || shape_table_ == nullptr) {
1204 *shape_id = -1;
1205 return unichar_id;
1206 }
1207 for (unsigned s = 0; s < shape_table_->NumShapes(); ++s) {
1208 if (shape_table_->GetShape(s).ContainsUnichar(unichar_id)) {
1209 tprintf("%s\n", shape_table_->DebugStr(s).c_str());
1210 }
1211 }
1212 } else {
1213 tprintf("Char class '%s' not found in unicharset", ev->parameter);
1214 }
1215 }
1216 }
1217 delete ev;
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 */
GetCPPadsForLevel(int Level,float * EndPad,float * SidePad,float * AnglePad)1235 void GetCPPadsForLevel(int Level, float *EndPad, float *SidePad, float *AnglePad) {
1236 switch (Level) {
1237 case 0:
1238 *EndPad = classify_cp_end_pad_loose * GetPicoFeatureLength();
1239 *SidePad = classify_cp_side_pad_loose * GetPicoFeatureLength();
1240 *AnglePad = classify_cp_angle_pad_loose / 360.0;
1241 break;
1242
1243 case 1:
1244 *EndPad = classify_cp_end_pad_medium * GetPicoFeatureLength();
1245 *SidePad = classify_cp_side_pad_medium * GetPicoFeatureLength();
1246 *AnglePad = classify_cp_angle_pad_medium / 360.0;
1247 break;
1248
1249 case 2:
1250 *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength();
1251 *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength();
1252 *AnglePad = classify_cp_angle_pad_tight / 360.0;
1253 break;
1254
1255 default:
1256 *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength();
1257 *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength();
1258 *AnglePad = classify_cp_angle_pad_tight / 360.0;
1259 break;
1260 }
1261 if (*AnglePad > 0.5) {
1262 *AnglePad = 0.5;
1263 }
1264
1265 } /* 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 */
GetMatchColorFor(float Evidence)1272 ScrollView::Color GetMatchColorFor(float Evidence) {
1273 assert(Evidence >= 0.0);
1274 assert(Evidence <= 1.0);
1275
1276 if (Evidence >= 0.90) {
1277 return ScrollView::WHITE;
1278 } else if (Evidence >= 0.75) {
1279 return ScrollView::GREEN;
1280 } else if (Evidence >= 0.50) {
1281 return ScrollView::RED;
1282 } else {
1283 return ScrollView::BLUE;
1284 }
1285 } /* 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 */
GetNextFill(TABLE_FILLER * Filler,FILL_SPEC * Fill)1295 void GetNextFill(TABLE_FILLER *Filler, FILL_SPEC *Fill) {
1296 FILL_SWITCH *Next;
1297
1298 /* compute the fill assuming no switches will be encountered */
1299 Fill->AngleStart = Filler->AngleStart;
1300 Fill->AngleEnd = Filler->AngleEnd;
1301 Fill->X = Filler->X;
1302 Fill->YStart = Filler->YStart >> 8;
1303 Fill->YEnd = Filler->YEnd >> 8;
1304
1305 /* update the fill info and the filler for ALL switches at this X value */
1306 Next = &(Filler->Switch[Filler->NextSwitch]);
1307 while (Filler->X >= Next->X) {
1308 Fill->X = Filler->X = Next->X;
1309 if (Next->Type == StartSwitch) {
1310 Fill->YStart = Next->Y;
1311 Filler->StartDelta = Next->Delta;
1312 Filler->YStart = Next->YInit;
1313 } else if (Next->Type == EndSwitch) {
1314 Fill->YEnd = Next->Y;
1315 Filler->EndDelta = Next->Delta;
1316 Filler->YEnd = Next->YInit;
1317 } else { /* Type must be LastSwitch */
1318 break;
1319 }
1320 Filler->NextSwitch++;
1321 Next = &(Filler->Switch[Filler->NextSwitch]);
1322 }
1323
1324 /* prepare the filler for the next call to this routine */
1325 Filler->X++;
1326 Filler->YStart += Filler->StartDelta;
1327 Filler->YEnd += Filler->EndDelta;
1328
1329 } /* 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 */
InitTableFiller(float EndPad,float SidePad,float AnglePad,PROTO_STRUCT * Proto,TABLE_FILLER * Filler)1340 void InitTableFiller(float EndPad, float SidePad, float AnglePad, PROTO_STRUCT *Proto, TABLE_FILLER *Filler)
1341 #define XS X_SHIFT
1342 #define YS Y_SHIFT
1343 #define AS ANGLE_SHIFT
1344 #define NB NUM_CP_BUCKETS
1345 {
1346 float Angle;
1347 float X, Y, HalfLength;
1348 float Cos, Sin;
1349 float XAdjust, YAdjust;
1350 FPOINT Start, Switch1, Switch2, End;
1351 int S1 = 0;
1352 int S2 = 1;
1353
1354 Angle = Proto->Angle;
1355 X = Proto->X;
1356 Y = Proto->Y;
1357 HalfLength = Proto->Length / 2.0;
1358
1359 Filler->AngleStart = CircBucketFor(Angle - AnglePad, AS, NB);
1360 Filler->AngleEnd = CircBucketFor(Angle + AnglePad, AS, NB);
1361 Filler->NextSwitch = 0;
1362
1363 if (fabs(Angle - 0.0) < HV_TOLERANCE || fabs(Angle - 0.5) < HV_TOLERANCE) {
1364 /* horizontal proto - handle as special case */
1365 Filler->X = Bucket8For(X - HalfLength - EndPad, XS, NB);
1366 Filler->YStart = Bucket16For(Y - SidePad, YS, NB * 256);
1367 Filler->YEnd = Bucket16For(Y + SidePad, YS, NB * 256);
1368 Filler->StartDelta = 0;
1369 Filler->EndDelta = 0;
1370 Filler->Switch[0].Type = LastSwitch;
1371 Filler->Switch[0].X = Bucket8For(X + HalfLength + EndPad, XS, NB);
1372 } else if (fabs(Angle - 0.25) < HV_TOLERANCE || fabs(Angle - 0.75) < HV_TOLERANCE) {
1373 /* vertical proto - handle as special case */
1374 Filler->X = Bucket8For(X - SidePad, XS, NB);
1375 Filler->YStart = Bucket16For(Y - HalfLength - EndPad, YS, NB * 256);
1376 Filler->YEnd = Bucket16For(Y + HalfLength + EndPad, YS, NB * 256);
1377 Filler->StartDelta = 0;
1378 Filler->EndDelta = 0;
1379 Filler->Switch[0].Type = LastSwitch;
1380 Filler->Switch[0].X = Bucket8For(X + SidePad, XS, NB);
1381 } else {
1382 /* diagonal proto */
1383
1384 if ((Angle > 0.0 && Angle < 0.25) || (Angle > 0.5 && Angle < 0.75)) {
1385 /* rising diagonal proto */
1386 Angle *= 2.0 * M_PI;
1387 Cos = fabs(std::cos(Angle));
1388 Sin = fabs(std::sin(Angle));
1389
1390 /* compute the positions of the corners of the acceptance region */
1391 Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin;
1392 Start.y = Y - (HalfLength + EndPad) * Sin + SidePad * Cos;
1393 End.x = 2.0 * X - Start.x;
1394 End.y = 2.0 * Y - Start.y;
1395 Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin;
1396 Switch1.y = Y - (HalfLength + EndPad) * Sin - SidePad * Cos;
1397 Switch2.x = 2.0 * X - Switch1.x;
1398 Switch2.y = 2.0 * Y - Switch1.y;
1399
1400 if (Switch1.x > Switch2.x) {
1401 S1 = 1;
1402 S2 = 0;
1403 }
1404
1405 /* translate into bucket positions and deltas */
1406 Filler->X = Bucket8For(Start.x, XS, NB);
1407 Filler->StartDelta = -static_cast<int16_t>((Cos / Sin) * 256);
1408 Filler->EndDelta = static_cast<int16_t>((Sin / Cos) * 256);
1409
1410 XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
1411 YAdjust = XAdjust * Cos / Sin;
1412 Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
1413 YAdjust = XAdjust * Sin / Cos;
1414 Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
1415
1416 Filler->Switch[S1].Type = StartSwitch;
1417 Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
1418 Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
1419 XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
1420 YAdjust = XAdjust * Sin / Cos;
1421 Filler->Switch[S1].YInit = Bucket16For(Switch1.y - YAdjust, YS, NB * 256);
1422 Filler->Switch[S1].Delta = Filler->EndDelta;
1423
1424 Filler->Switch[S2].Type = EndSwitch;
1425 Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
1426 Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
1427 XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
1428 YAdjust = XAdjust * Cos / Sin;
1429 Filler->Switch[S2].YInit = Bucket16For(Switch2.y + YAdjust, YS, NB * 256);
1430 Filler->Switch[S2].Delta = Filler->StartDelta;
1431
1432 Filler->Switch[2].Type = LastSwitch;
1433 Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
1434 } else {
1435 /* falling diagonal proto */
1436 Angle *= 2.0 * M_PI;
1437 Cos = fabs(std::cos(Angle));
1438 Sin = fabs(std::sin(Angle));
1439
1440 /* compute the positions of the corners of the acceptance region */
1441 Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin;
1442 Start.y = Y + (HalfLength + EndPad) * Sin - SidePad * Cos;
1443 End.x = 2.0 * X - Start.x;
1444 End.y = 2.0 * Y - Start.y;
1445 Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin;
1446 Switch1.y = Y + (HalfLength + EndPad) * Sin + SidePad * Cos;
1447 Switch2.x = 2.0 * X - Switch1.x;
1448 Switch2.y = 2.0 * Y - Switch1.y;
1449
1450 if (Switch1.x > Switch2.x) {
1451 S1 = 1;
1452 S2 = 0;
1453 }
1454
1455 /* translate into bucket positions and deltas */
1456 Filler->X = Bucket8For(Start.x, XS, NB);
1457 Filler->StartDelta = static_cast<int16_t>(
1458 ClipToRange<int>(-IntCastRounded((Sin / Cos) * 256), INT16_MIN, INT16_MAX));
1459 Filler->EndDelta = static_cast<int16_t>(
1460 ClipToRange<int>(IntCastRounded((Cos / Sin) * 256), INT16_MIN, INT16_MAX));
1461
1462 XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
1463 YAdjust = XAdjust * Sin / Cos;
1464 Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
1465 YAdjust = XAdjust * Cos / Sin;
1466 Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
1467
1468 Filler->Switch[S1].Type = EndSwitch;
1469 Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
1470 Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
1471 XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
1472 YAdjust = XAdjust * Sin / Cos;
1473 Filler->Switch[S1].YInit = Bucket16For(Switch1.y + YAdjust, YS, NB * 256);
1474 Filler->Switch[S1].Delta = Filler->StartDelta;
1475
1476 Filler->Switch[S2].Type = StartSwitch;
1477 Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
1478 Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
1479 XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
1480 YAdjust = XAdjust * Cos / Sin;
1481 Filler->Switch[S2].YInit = Bucket16For(Switch2.y - YAdjust, YS, NB * 256);
1482 Filler->Switch[S2].Delta = Filler->EndDelta;
1483
1484 Filler->Switch[2].Type = LastSwitch;
1485 Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
1486 }
1487 }
1488 } /* 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 */
RenderIntFeature(ScrollView * window,const INT_FEATURE_STRUCT * Feature,ScrollView::Color color)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 */
RenderIntProto(ScrollView * window,INT_CLASS_STRUCT * Class,PROTO_ID ProtoId,ScrollView::Color color)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 */
InitIntMatchWindowIfReqd()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 */
InitProtoDisplayWindowIfReqd()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 */
InitFeatureDisplayWindowIfReqd()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.
CreateFeatureSpaceWindow(const char * name,int xpos,int ypos)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
1628