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