Mercurial > hgrepos > Python2 > PyMuPDF
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 |
