comparison mupdf-source/thirdparty/tesseract/src/classify/intproto.cpp @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
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 */
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.
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. */
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 */
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 */
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 */
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 */
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 */
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 */
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 }
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 */
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 */
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 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 */
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 */
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 unsigned fs_size = FClass->font_set.size();
509 FontSet fs;
510 fs.reserve(fs_size);
511 for (unsigned i = 0; i < fs_size; ++i) {
512 fs.push_back(FClass->font_set[i]);
513 }
514 IClass->font_set_id = this->fontset_table_.push_back(std::move(fs));
515 AddIntClass(IntTemplates, ClassId, IClass);
516
517 for (ProtoId = 0; ProtoId < FClass->NumProtos; ProtoId++) {
518 AddIntProto(IClass);
519 ConvertProto(ProtoIn(FClass, ProtoId), ProtoId, IClass);
520 AddProtoToProtoPruner(ProtoIn(FClass, ProtoId), ProtoId, IClass,
521 classify_learning_debug_level >= 2);
522 AddProtoToClassPruner(ProtoIn(FClass, ProtoId), ClassId, IntTemplates);
523 }
524
525 for (ConfigId = 0; ConfigId < FClass->NumConfigs; ConfigId++) {
526 AddIntConfig(IClass);
527 ConvertConfig(FClass->Configurations[ConfigId], ConfigId, IClass);
528 }
529 }
530 return (IntTemplates);
531 } /* 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 NumProtos(0),
577 NumProtoSets((MaxNumProtos + PROTOS_PER_PROTO_SET - 1) / PROTOS_PER_PROTO_SET),
578 NumConfigs(0),
579 ProtoLengths(MaxNumIntProtosIn(this))
580 {
581 assert(MaxNumConfigs <= MAX_NUM_CONFIGS);
582 assert(NumProtoSets <= MAX_NUM_PROTO_SETS);
583
584 for (int i = 0; i < NumProtoSets; i++) {
585 /* allocate space for a proto set, install in class, and initialize */
586 auto ProtoSet = new PROTO_SET_STRUCT;
587 memset(ProtoSet, 0, sizeof(*ProtoSet));
588 ProtoSets[i] = ProtoSet;
589
590 /* allocate space for the proto lengths and install in class */
591 }
592 memset(ConfigLengths, 0, sizeof(ConfigLengths));
593 }
594
595 INT_CLASS_STRUCT::~INT_CLASS_STRUCT() {
596 for (int i = 0; i < NumProtoSets; i++) {
597 delete ProtoSets[i];
598 }
599 }
600
601 /// This constructor allocates a new set of integer templates
602 /// initialized to hold 0 classes.
603 INT_TEMPLATES_STRUCT::INT_TEMPLATES_STRUCT() {
604 NumClasses = 0;
605 NumClassPruners = 0;
606
607 for (int i = 0; i < MAX_NUM_CLASSES; i++) {
608 ClassForClassId(this, i) = nullptr;
609 }
610 }
611
612 INT_TEMPLATES_STRUCT::~INT_TEMPLATES_STRUCT() {
613 for (unsigned i = 0; i < NumClasses; i++) {
614 delete Class[i];
615 }
616 for (unsigned i = 0; i < NumClassPruners; i++) {
617 delete ClassPruners[i];
618 }
619 }
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 INT_TEMPLATES_STRUCT *Classify::ReadIntTemplates(TFile *fp) {
630 int j, w, x, y, z;
631 INT_TEMPLATES_STRUCT *Templates;
632 CLASS_PRUNER_STRUCT *Pruner;
633 INT_CLASS_STRUCT *Class;
634
635 /* variables for conversion from older inttemp formats */
636 int b, bit_number, last_cp_bit_number, new_b, new_i, new_w;
637 CLASS_ID class_id, max_class_id;
638 std::vector<CLASS_ID> ClassIdFor(MAX_NUM_CLASSES);
639 std::vector<CLASS_PRUNER_STRUCT *> TempClassPruner(MAX_NUM_CLASS_PRUNERS);
640 uint32_t SetBitsForMask = // word with NUM_BITS_PER_CLASS
641 (1 << NUM_BITS_PER_CLASS) - 1; // set starting at bit 0
642 uint32_t Mask, NewMask, ClassBits;
643 unsigned MaxNumConfigs = MAX_NUM_CONFIGS;
644 unsigned WerdsPerConfigVec = WERDS_PER_CONFIG_VEC;
645
646 /* first read the high level template struct */
647 Templates = new INT_TEMPLATES_STRUCT;
648 // Read Templates in parts for 64 bit compatibility.
649 uint32_t unicharset_size;
650 if (fp->FReadEndian(&unicharset_size, sizeof(unicharset_size), 1) != 1) {
651 tprintf("Bad read of inttemp!\n");
652 }
653 int32_t version_id = 0;
654 if (fp->FReadEndian(&version_id, sizeof(version_id), 1) != 1 ||
655 fp->FReadEndian(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1) != 1) {
656 tprintf("Bad read of inttemp!\n");
657 }
658 if (version_id < 0) {
659 // This file has a version id!
660 version_id = -version_id;
661 if (fp->FReadEndian(&Templates->NumClasses, sizeof(Templates->NumClasses), 1) != 1) {
662 tprintf("Bad read of inttemp!\n");
663 }
664 } else {
665 Templates->NumClasses = version_id;
666 }
667
668 if (version_id < 3) {
669 MaxNumConfigs = OLD_MAX_NUM_CONFIGS;
670 WerdsPerConfigVec = OLD_WERDS_PER_CONFIG_VEC;
671 }
672
673 if (version_id < 2) {
674 std::vector<int16_t> IndexFor(MAX_NUM_CLASSES);
675 if (fp->FReadEndian(&IndexFor[0], sizeof(IndexFor[0]), unicharset_size) != unicharset_size) {
676 tprintf("Bad read of inttemp!\n");
677 }
678 if (fp->FReadEndian(&ClassIdFor[0], sizeof(ClassIdFor[0]), Templates->NumClasses) !=
679 Templates->NumClasses) {
680 tprintf("Bad read of inttemp!\n");
681 }
682 }
683
684 /* then read in the class pruners */
685 const unsigned kNumBuckets = NUM_CP_BUCKETS * NUM_CP_BUCKETS * NUM_CP_BUCKETS * WERDS_PER_CP_VECTOR;
686 for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
687 Pruner = new CLASS_PRUNER_STRUCT;
688 if (fp->FReadEndian(Pruner, sizeof(Pruner->p[0][0][0][0]), kNumBuckets) != kNumBuckets) {
689 tprintf("Bad read of inttemp!\n");
690 }
691 if (version_id < 2) {
692 TempClassPruner[i] = Pruner;
693 } else {
694 Templates->ClassPruners[i] = Pruner;
695 }
696 }
697
698 /* fix class pruners if they came from an old version of inttemp */
699 if (version_id < 2) {
700 // Allocate enough class pruners to cover all the class ids.
701 max_class_id = 0;
702 for (unsigned i = 0; i < Templates->NumClasses; i++) {
703 if (ClassIdFor[i] > max_class_id) {
704 max_class_id = ClassIdFor[i];
705 }
706 }
707 for (int i = 0; i <= CPrunerIdFor(max_class_id); i++) {
708 Templates->ClassPruners[i] = new CLASS_PRUNER_STRUCT;
709 memset(Templates->ClassPruners[i], 0, sizeof(CLASS_PRUNER_STRUCT));
710 }
711 // Convert class pruners from the old format (indexed by class index)
712 // to the new format (indexed by class id).
713 last_cp_bit_number = NUM_BITS_PER_CLASS * Templates->NumClasses - 1;
714 for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
715 for (x = 0; x < NUM_CP_BUCKETS; x++) {
716 for (y = 0; y < NUM_CP_BUCKETS; y++) {
717 for (z = 0; z < NUM_CP_BUCKETS; z++) {
718 for (w = 0; w < WERDS_PER_CP_VECTOR; w++) {
719 if (TempClassPruner[i]->p[x][y][z][w] == 0) {
720 continue;
721 }
722 for (b = 0; b < BITS_PER_WERD; b += NUM_BITS_PER_CLASS) {
723 bit_number = i * BITS_PER_CP_VECTOR + w * BITS_PER_WERD + b;
724 if (bit_number > last_cp_bit_number) {
725 break; // the rest of the bits in this word are not used
726 }
727 class_id = ClassIdFor[bit_number / NUM_BITS_PER_CLASS];
728 // Single out NUM_BITS_PER_CLASS bits relating to class_id.
729 Mask = SetBitsForMask << b;
730 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 new_i = CPrunerIdFor(class_id);
734 new_w = CPrunerWordIndexFor(class_id);
735 new_b = CPrunerBitIndexFor(class_id) * NUM_BITS_PER_CLASS;
736 if (new_b > b) {
737 ClassBits <<= (new_b - b);
738 } else {
739 ClassBits >>= (b - new_b);
740 }
741 // Copy bits relating to class_id to the correct position
742 // in Templates->ClassPruner.
743 NewMask = SetBitsForMask << new_b;
744 Templates->ClassPruners[new_i]->p[x][y][z][new_w] &= ~NewMask;
745 Templates->ClassPruners[new_i]->p[x][y][z][new_w] |= ClassBits;
746 }
747 }
748 }
749 }
750 }
751 }
752 for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
753 delete TempClassPruner[i];
754 }
755 }
756
757 /* then read in each class */
758 for (unsigned i = 0; i < Templates->NumClasses; i++) {
759 /* first read in the high level struct for the class */
760 Class = new INT_CLASS_STRUCT;
761 if (fp->FReadEndian(&Class->NumProtos, sizeof(Class->NumProtos), 1) != 1 ||
762 fp->FRead(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1) != 1 ||
763 fp->FRead(&Class->NumConfigs, sizeof(Class->NumConfigs), 1) != 1) {
764 tprintf("Bad read of inttemp!\n");
765 }
766 if (version_id == 0) {
767 // Only version 0 writes 5 pointless pointers to the file.
768 for (j = 0; j < 5; ++j) {
769 int32_t junk;
770 if (fp->FRead(&junk, sizeof(junk), 1) != 1) {
771 tprintf("Bad read of inttemp!\n");
772 }
773 }
774 }
775 unsigned num_configs = version_id < 4 ? MaxNumConfigs : Class->NumConfigs;
776 ASSERT_HOST(num_configs <= MaxNumConfigs);
777 if (fp->FReadEndian(Class->ConfigLengths, sizeof(uint16_t), num_configs) != num_configs) {
778 tprintf("Bad read of inttemp!\n");
779 }
780 if (version_id < 2) {
781 ClassForClassId(Templates, ClassIdFor[i]) = Class;
782 } else {
783 ClassForClassId(Templates, i) = Class;
784 }
785
786 /* then read in the proto lengths */
787 Class->ProtoLengths.clear();
788 if (MaxNumIntProtosIn(Class) > 0) {
789 Class->ProtoLengths.resize(MaxNumIntProtosIn(Class));
790 if (fp->FRead(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class)) !=
791 MaxNumIntProtosIn(Class)) {
792 tprintf("Bad read of inttemp!\n");
793 }
794 }
795
796 /* then read in the proto sets */
797 for (j = 0; j < Class->NumProtoSets; j++) {
798 auto ProtoSet = new PROTO_SET_STRUCT;
799 unsigned num_buckets = NUM_PP_PARAMS * NUM_PP_BUCKETS * WERDS_PER_PP_VECTOR;
800 if (fp->FReadEndian(&ProtoSet->ProtoPruner, sizeof(ProtoSet->ProtoPruner[0][0][0]),
801 num_buckets) != num_buckets) {
802 tprintf("Bad read of inttemp!\n");
803 }
804 for (x = 0; x < PROTOS_PER_PROTO_SET; x++) {
805 if (fp->FRead(&ProtoSet->Protos[x].A, sizeof(ProtoSet->Protos[x].A), 1) != 1 ||
806 fp->FRead(&ProtoSet->Protos[x].B, sizeof(ProtoSet->Protos[x].B), 1) != 1 ||
807 fp->FRead(&ProtoSet->Protos[x].C, sizeof(ProtoSet->Protos[x].C), 1) != 1 ||
808 fp->FRead(&ProtoSet->Protos[x].Angle, sizeof(ProtoSet->Protos[x].Angle), 1) != 1) {
809 tprintf("Bad read of inttemp!\n");
810 }
811 if (fp->FReadEndian(&ProtoSet->Protos[x].Configs, sizeof(ProtoSet->Protos[x].Configs[0]),
812 WerdsPerConfigVec) != WerdsPerConfigVec) {
813 tprintf("Bad read of inttemp!\n");
814 }
815 }
816 Class->ProtoSets[j] = ProtoSet;
817 }
818 if (version_id < 4) {
819 Class->font_set_id = -1;
820 } else {
821 fp->FReadEndian(&Class->font_set_id, sizeof(Class->font_set_id), 1);
822 }
823 }
824
825 if (version_id < 2) {
826 /* add an empty nullptr class with class id 0 */
827 assert(UnusedClassIdIn(Templates, 0));
828 ClassForClassId(Templates, 0) = new INT_CLASS_STRUCT(1, 1);
829 ClassForClassId(Templates, 0)->font_set_id = -1;
830 Templates->NumClasses++;
831 /* make sure the classes are contiguous */
832 for (unsigned i = 0; i < MAX_NUM_CLASSES; i++) {
833 if (i < Templates->NumClasses) {
834 if (ClassForClassId(Templates, i) == nullptr) {
835 fprintf(stderr, "Non-contiguous class ids in inttemp\n");
836 exit(1);
837 }
838 } else {
839 if (ClassForClassId(Templates, i) != nullptr) {
840 fprintf(stderr, "Class id %u exceeds NumClassesIn (Templates) %u\n", i,
841 Templates->NumClasses);
842 exit(1);
843 }
844 }
845 }
846 }
847 if (version_id >= 4) {
848 using namespace std::placeholders; // for _1, _2
849 this->fontinfo_table_.read(fp, std::bind(read_info, _1, _2));
850 if (version_id >= 5) {
851 this->fontinfo_table_.read(fp, std::bind(read_spacing_info, _1, _2));
852 }
853 this->fontset_table_.read(fp, [](auto *f, auto *fs) { return f->DeSerialize(*fs); } );
854 }
855
856 return (Templates);
857 } /* 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 const UNICHARSET &target_unicharset) {
921 INT_CLASS_STRUCT *Class;
922 uint32_t unicharset_size = target_unicharset.size();
923 int version_id = -5; // When negated by the reader -1 becomes +1 etc.
924
925 if (Templates->NumClasses != unicharset_size) {
926 tprintf(
927 "Warning: executing WriteIntTemplates() with %d classes in"
928 " Templates, while target_unicharset size is %" PRIu32 "\n",
929 Templates->NumClasses, unicharset_size);
930 }
931
932 /* first write the high level template struct */
933 fwrite(&unicharset_size, sizeof(unicharset_size), 1, File);
934 fwrite(&version_id, sizeof(version_id), 1, File);
935 fwrite(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1, File);
936 fwrite(&Templates->NumClasses, sizeof(Templates->NumClasses), 1, File);
937
938 /* then write out the class pruners */
939 for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
940 fwrite(Templates->ClassPruners[i], sizeof(CLASS_PRUNER_STRUCT), 1, File);
941 }
942
943 /* then write out each class */
944 for (unsigned i = 0; i < Templates->NumClasses; i++) {
945 Class = Templates->Class[i];
946
947 /* first write out the high level struct for the class */
948 fwrite(&Class->NumProtos, sizeof(Class->NumProtos), 1, File);
949 fwrite(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1, File);
950 ASSERT_HOST(Class->NumConfigs == this->fontset_table_.at(Class->font_set_id).size());
951 fwrite(&Class->NumConfigs, sizeof(Class->NumConfigs), 1, File);
952 for (int j = 0; j < Class->NumConfigs; ++j) {
953 fwrite(&Class->ConfigLengths[j], sizeof(uint16_t), 1, File);
954 }
955
956 /* then write out the proto lengths */
957 if (MaxNumIntProtosIn(Class) > 0) {
958 fwrite(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class), File);
959 }
960
961 /* then write out the proto sets */
962 for (int j = 0; j < Class->NumProtoSets; j++) {
963 fwrite(Class->ProtoSets[j], sizeof(PROTO_SET_STRUCT), 1, File);
964 }
965
966 /* then write the fonts info */
967 fwrite(&Class->font_set_id, sizeof(int), 1, File);
968 }
969
970 /* Write the fonts info tables */
971 using namespace std::placeholders; // for _1, _2
972 this->fontinfo_table_.write(File, std::bind(write_info, _1, _2));
973 this->fontinfo_table_.write(File, std::bind(write_spacing_info, _1, _2));
974 this->fontset_table_.write(File, std::bind(write_set, _1, _2));
975 } /* 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 float BucketStart(int Bucket, float Offset, int NumBuckets) {
992 return static_cast<float>(Bucket) / NumBuckets - Offset;
993
994 } /* 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 float BucketEnd(int Bucket, float Offset, int NumBuckets) {
1008 return static_cast<float>(Bucket + 1) / NumBuckets - Offset;
1009 } /* 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 uint32_t ClassCount, uint32_t WordIndex) {
1023 int X, Y, Angle;
1024 uint32_t OldWord;
1025
1026 X = FillSpec->X;
1027 if (X < 0) {
1028 X = 0;
1029 }
1030 if (X >= NUM_CP_BUCKETS) {
1031 X = NUM_CP_BUCKETS - 1;
1032 }
1033
1034 if (FillSpec->YStart < 0) {
1035 FillSpec->YStart = 0;
1036 }
1037 if (FillSpec->YEnd >= NUM_CP_BUCKETS) {
1038 FillSpec->YEnd = NUM_CP_BUCKETS - 1;
1039 }
1040
1041 for (Y = FillSpec->YStart; Y <= FillSpec->YEnd; Y++) {
1042 for (Angle = FillSpec->AngleStart;; CircularIncrement(Angle, NUM_CP_BUCKETS)) {
1043 OldWord = Pruner->p[X][Y][Angle][WordIndex];
1044 if (ClassCount > (OldWord & ClassMask)) {
1045 OldWord &= ~ClassMask;
1046 OldWord |= ClassCount;
1047 Pruner->p[X][Y][Angle][WordIndex] = OldWord;
1048 }
1049 if (Angle == FillSpec->AngleEnd) {
1050 break;
1051 }
1052 }
1053 }
1054 } /* 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 bool FillerDone(TABLE_FILLER *Filler) {
1064 FILL_SWITCH *Next;
1065
1066 Next = &(Filler->Switch[Filler->NextSwitch]);
1067
1068 return Filler->X > Next->X && Next->Type == LastSwitch;
1069
1070 } /* 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 float Center, float Spread, bool debug) {
1087 int i, FirstBucket, LastBucket;
1088
1089 if (Spread > 0.5) {
1090 Spread = 0.5;
1091 }
1092
1093 FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
1094 if (FirstBucket < 0) {
1095 FirstBucket += NUM_PP_BUCKETS;
1096 }
1097
1098 LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
1099 if (LastBucket >= NUM_PP_BUCKETS) {
1100 LastBucket -= NUM_PP_BUCKETS;
1101 }
1102 if (debug) {
1103 tprintf("Circular fill from %d to %d", FirstBucket, LastBucket);
1104 }
1105 for (i = FirstBucket; true; CircularIncrement(i, NUM_PP_BUCKETS)) {
1106 SET_BIT(ParamTable[i], Bit);
1107
1108 /* exit loop after we have set the bit for the last bucket */
1109 if (i == LastBucket) {
1110 break;
1111 }
1112 }
1113
1114 } /* 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 float Center, float Spread, bool debug) {
1132 int i, FirstBucket, LastBucket;
1133
1134 FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
1135 if (FirstBucket < 0) {
1136 FirstBucket = 0;
1137 }
1138
1139 LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
1140 if (LastBucket >= NUM_PP_BUCKETS) {
1141 LastBucket = NUM_PP_BUCKETS - 1;
1142 }
1143
1144 if (debug) {
1145 tprintf("Linear fill from %d to %d", FirstBucket, LastBucket);
1146 }
1147 for (i = FirstBucket; i <= LastBucket; i++) {
1148 SET_BIT(ParamTable[i], Bit);
1149 }
1150
1151 } /* 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 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 */
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 */
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 */
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 */
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