summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'tesseract/src/classify/intproto.cpp')
-rw-r--r--tesseract/src/classify/intproto.cpp1743
1 files changed, 1743 insertions, 0 deletions
diff --git a/tesseract/src/classify/intproto.cpp b/tesseract/src/classify/intproto.cpp
new file mode 100644
index 00000000..37a92f7b
--- /dev/null
+++ b/tesseract/src/classify/intproto.cpp
@@ -0,0 +1,1743 @@
+/******************************************************************************
+ ** Filename: intproto.c
+ ** Purpose: Definition of data structures for integer protos.
+ ** Author: Dan Johnson
+ **
+ ** (c) Copyright Hewlett-Packard Company, 1988.
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ ******************************************************************************/
+/*-----------------------------------------------------------------------------
+ Include Files and Type Defines
+-----------------------------------------------------------------------------*/
+
+#define _USE_MATH_DEFINES // for M_PI
+
+// Include automatically generated configuration file if running autoconf.
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif
+
+#include "intproto.h"
+
+#include "classify.h"
+#include "fontinfo.h"
+#include "mfoutline.h"
+#include "picofeat.h"
+#include "points.h"
+#include "shapetable.h"
+#include "svmnode.h"
+
+#include "helpers.h"
+
+#include <algorithm>
+#include <cmath> // for M_PI, std::floor
+#include <cstdio>
+#include <cassert>
+
+namespace tesseract {
+
+/* match debug display constants*/
+#define PROTO_PRUNER_SCALE (4.0)
+
+#define INT_DESCENDER (0.0 * INT_CHAR_NORM_RANGE)
+#define INT_BASELINE (0.25 * INT_CHAR_NORM_RANGE)
+#define INT_XHEIGHT (0.75 * INT_CHAR_NORM_RANGE)
+#define INT_CAPHEIGHT (1.0 * INT_CHAR_NORM_RANGE)
+
+#define INT_XCENTER (0.5 * INT_CHAR_NORM_RANGE)
+#define INT_YCENTER (0.5 * INT_CHAR_NORM_RANGE)
+#define INT_XRADIUS (0.2 * INT_CHAR_NORM_RANGE)
+#define INT_YRADIUS (0.2 * INT_CHAR_NORM_RANGE)
+#define INT_MIN_X 0
+#define INT_MIN_Y 0
+#define INT_MAX_X INT_CHAR_NORM_RANGE
+#define INT_MAX_Y INT_CHAR_NORM_RANGE
+
+/** define pad used to snap near horiz/vertical protos to horiz/vertical */
+#define HV_TOLERANCE (0.0025) /* approx 0.9 degrees */
+
+typedef enum
+{ StartSwitch, EndSwitch, LastSwitch }
+SWITCH_TYPE;
+#define MAX_NUM_SWITCHES 3
+
+typedef struct
+{
+ SWITCH_TYPE Type;
+ int8_t X, Y;
+ int16_t YInit;
+ int16_t Delta;
+}
+FILL_SWITCH;
+
+typedef struct
+{
+ uint8_t NextSwitch;
+ uint8_t AngleStart, AngleEnd;
+ int8_t X;
+ int16_t YStart, YEnd;
+ int16_t StartDelta, EndDelta;
+ FILL_SWITCH Switch[MAX_NUM_SWITCHES];
+}
+TABLE_FILLER;
+
+typedef struct
+{
+ int8_t X;
+ int8_t YStart, YEnd;
+ uint8_t AngleStart, AngleEnd;
+}
+FILL_SPEC;
+
+
+/* constants for conversion from old inttemp format */
+#define OLD_MAX_NUM_CONFIGS 32
+#define OLD_WERDS_PER_CONFIG_VEC ((OLD_MAX_NUM_CONFIGS + BITS_PER_WERD - 1) /\
+ BITS_PER_WERD)
+
+/*-----------------------------------------------------------------------------
+ Macros
+-----------------------------------------------------------------------------*/
+/** macro for performing circular increments of bucket indices */
+#define CircularIncrement(i,r) (((i) < (r) - 1)?((i)++):((i) = 0))
+
+/** macro for mapping floats to ints without bounds checking */
+#define MapParam(P,O,N) (std::floor(((P) + (O)) * (N)))
+
+/*---------------------------------------------------------------------------
+ Private Function Prototypes
+----------------------------------------------------------------------------*/
+float BucketStart(int Bucket, float Offset, int NumBuckets);
+
+float BucketEnd(int Bucket, float Offset, int NumBuckets);
+
+void DoFill(FILL_SPEC *FillSpec,
+ CLASS_PRUNER_STRUCT* Pruner,
+ uint32_t ClassMask,
+ uint32_t ClassCount,
+ uint32_t WordIndex);
+
+bool FillerDone(TABLE_FILLER* Filler);
+
+void FillPPCircularBits(uint32_t
+ ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR],
+ int Bit, float Center, float Spread, bool debug);
+
+void FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR],
+ int Bit, float Center, float Spread, bool debug);
+
+void GetCPPadsForLevel(int Level,
+ float *EndPad,
+ float *SidePad,
+ float *AnglePad);
+
+ScrollView::Color GetMatchColorFor(float Evidence);
+
+void GetNextFill(TABLE_FILLER *Filler, FILL_SPEC *Fill);
+
+void InitTableFiller(float EndPad,
+ float SidePad,
+ float AnglePad,
+ PROTO Proto,
+ TABLE_FILLER *Filler);
+
+#ifndef GRAPHICS_DISABLED
+void RenderIntFeature(ScrollView *window, const INT_FEATURE_STRUCT* Feature,
+ ScrollView::Color color);
+
+void RenderIntProto(ScrollView *window,
+ INT_CLASS Class,
+ PROTO_ID ProtoId,
+ ScrollView::Color color);
+#endif // !GRAPHICS_DISABLED
+
+/*-----------------------------------------------------------------------------
+ Global Data Definitions and Declarations
+-----------------------------------------------------------------------------*/
+
+#ifndef GRAPHICS_DISABLED
+/* global display lists used to display proto and feature match information*/
+static ScrollView* IntMatchWindow = nullptr;
+static ScrollView* FeatureDisplayWindow = nullptr;
+static ScrollView* ProtoDisplayWindow = nullptr;
+#endif
+
+/*-----------------------------------------------------------------------------
+ Variables
+-----------------------------------------------------------------------------*/
+
+/* control knobs */
+static INT_VAR(classify_num_cp_levels, 3, "Number of Class Pruner Levels");
+static double_VAR(classify_cp_angle_pad_loose, 45.0,
+ "Class Pruner Angle Pad Loose");
+static double_VAR(classify_cp_angle_pad_medium, 20.0,
+ "Class Pruner Angle Pad Medium");
+static double_VAR(classify_cp_angle_pad_tight, 10.0,
+ "CLass Pruner Angle Pad Tight");
+static double_VAR(classify_cp_end_pad_loose, 0.5, "Class Pruner End Pad Loose");
+static double_VAR(classify_cp_end_pad_medium, 0.5, "Class Pruner End Pad Medium");
+static double_VAR(classify_cp_end_pad_tight, 0.5, "Class Pruner End Pad Tight");
+static double_VAR(classify_cp_side_pad_loose, 2.5, "Class Pruner Side Pad Loose");
+static double_VAR(classify_cp_side_pad_medium, 1.2, "Class Pruner Side Pad Medium");
+static double_VAR(classify_cp_side_pad_tight, 0.6, "Class Pruner Side Pad Tight");
+static double_VAR(classify_pp_angle_pad, 45.0, "Proto Pruner Angle Pad");
+static double_VAR(classify_pp_end_pad, 0.5, "Proto Prune End Pad");
+static double_VAR(classify_pp_side_pad, 2.5, "Proto Pruner Side Pad");
+
+/**
+ * This routine truncates Param to lie within the range
+ * of Min-Max inclusive.
+ *
+ * @param Param parameter value to be truncated
+ * @param Min, Max parameter limits (inclusive)
+ *
+ * @return Truncated parameter.
+ */
+static int TruncateParam(float Param, int Min, int Max) {
+ int result;
+ if (Param < Min) {
+ result = Min;
+ } else if (Param > Max) {
+ result = Max;
+ } else {
+ result = static_cast<int>(std::floor(Param));
+ }
+ return result;
+}
+
+/*-----------------------------------------------------------------------------
+ Public Code
+-----------------------------------------------------------------------------*/
+/// Builds a feature from an FCOORD for position with all the necessary
+/// clipping and rounding.
+INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(const FCOORD& pos, uint8_t theta)
+ : X(ClipToRange<int16_t>(static_cast<int16_t>(pos.x() + 0.5), 0, 255)),
+ Y(ClipToRange<int16_t>(static_cast<int16_t>(pos.y() + 0.5), 0, 255)),
+ Theta(theta),
+ CP_misses(0) {
+}
+/** Builds a feature from ints with all the necessary clipping and casting. */
+INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(int x, int y, int theta)
+ : X(static_cast<uint8_t>(ClipToRange<int>(x, 0, UINT8_MAX))),
+ Y(static_cast<uint8_t>(ClipToRange<int>(y, 0, UINT8_MAX))),
+ Theta(static_cast<uint8_t>(ClipToRange<int>(theta, 0, UINT8_MAX))),
+ CP_misses(0) {
+}
+
+/**
+ * This routine adds a new class structure to a set of
+ * templates. Classes have to be added to Templates in
+ * the order of increasing ClassIds.
+ *
+ * @param Templates templates to add new class to
+ * @param ClassId class id to associate new class with
+ * @param Class class data structure to add to templates
+ *
+ * Globals: none
+ */
+void AddIntClass(INT_TEMPLATES Templates, CLASS_ID ClassId, INT_CLASS Class) {
+ int Pruner;
+
+ assert (LegalClassId (ClassId));
+ if (ClassId != Templates->NumClasses) {
+ fprintf(stderr, "Please make sure that classes are added to templates");
+ fprintf(stderr, " in increasing order of ClassIds\n");
+ exit(1);
+ }
+ ClassForClassId (Templates, ClassId) = Class;
+ Templates->NumClasses++;
+
+ if (Templates->NumClasses > MaxNumClassesIn (Templates)) {
+ Pruner = Templates->NumClassPruners++;
+ Templates->ClassPruners[Pruner] = new CLASS_PRUNER_STRUCT;
+ memset(Templates->ClassPruners[Pruner], 0, sizeof(CLASS_PRUNER_STRUCT));
+ }
+} /* AddIntClass */
+
+
+/**
+ * This routine returns the index of the next free config
+ * in Class.
+ *
+ * @param Class class to add new configuration to
+ *
+ * Globals: none
+ *
+ * @return Index of next free config.
+ */
+int AddIntConfig(INT_CLASS Class) {
+ int Index;
+
+ assert(Class->NumConfigs < MAX_NUM_CONFIGS);
+
+ Index = Class->NumConfigs++;
+ Class->ConfigLengths[Index] = 0;
+ return Index;
+} /* AddIntConfig */
+
+
+/**
+ * This routine allocates the next free proto in Class and
+ * returns its index.
+ *
+ * @param Class class to add new proto to
+ *
+ * Globals: none
+ *
+ * @return Proto index of new proto.
+ */
+int AddIntProto(INT_CLASS Class) {
+ int Index;
+ int ProtoSetId;
+ PROTO_SET ProtoSet;
+ INT_PROTO Proto;
+ uint32_t *Word;
+
+ if (Class->NumProtos >= MAX_NUM_PROTOS)
+ return (NO_PROTO);
+
+ Index = Class->NumProtos++;
+
+ if (Class->NumProtos > MaxNumIntProtosIn(Class)) {
+ ProtoSetId = Class->NumProtoSets++;
+
+ ProtoSet = static_cast<PROTO_SET>(malloc(sizeof(PROTO_SET_STRUCT)));
+ Class->ProtoSets[ProtoSetId] = ProtoSet;
+ memset(ProtoSet, 0, sizeof(*ProtoSet));
+
+ /* reallocate space for the proto lengths and install in class */
+ Class->ProtoLengths =
+ static_cast<uint8_t *>(realloc(Class->ProtoLengths,
+ MaxNumIntProtosIn(Class) * sizeof(uint8_t)));
+ memset(&Class->ProtoLengths[Index], 0,
+ sizeof(*Class->ProtoLengths) * (MaxNumIntProtosIn(Class) - Index));
+ }
+
+ /* initialize proto so its length is zero and it isn't in any configs */
+ Class->ProtoLengths[Index] = 0;
+ Proto = ProtoForProtoId (Class, Index);
+ for (Word = Proto->Configs;
+ Word < Proto->Configs + WERDS_PER_CONFIG_VEC; *Word++ = 0);
+
+ return (Index);
+}
+
+/**
+ * This routine adds Proto to the class pruning tables
+ * for the specified class in Templates.
+ *
+ * Globals:
+ * - classify_num_cp_levels number of levels used in the class pruner
+ * @param Proto floating-pt proto to add to class pruner
+ * @param ClassId class id corresponding to Proto
+ * @param Templates set of templates containing class pruner
+ */
+void AddProtoToClassPruner (PROTO Proto, CLASS_ID ClassId,
+ INT_TEMPLATES Templates)
+#define MAX_LEVEL 2
+{
+ CLASS_PRUNER_STRUCT* Pruner;
+ uint32_t ClassMask;
+ uint32_t ClassCount;
+ uint32_t WordIndex;
+ int Level;
+ float EndPad, SidePad, AnglePad;
+ TABLE_FILLER TableFiller;
+ FILL_SPEC FillSpec;
+
+ Pruner = CPrunerFor (Templates, ClassId);
+ WordIndex = CPrunerWordIndexFor (ClassId);
+ ClassMask = CPrunerMaskFor (MAX_LEVEL, ClassId);
+
+ for (Level = classify_num_cp_levels - 1; Level >= 0; Level--) {
+ GetCPPadsForLevel(Level, &EndPad, &SidePad, &AnglePad);
+ ClassCount = CPrunerMaskFor (Level, ClassId);
+ InitTableFiller(EndPad, SidePad, AnglePad, Proto, &TableFiller);
+
+ while (!FillerDone (&TableFiller)) {
+ GetNextFill(&TableFiller, &FillSpec);
+ DoFill(&FillSpec, Pruner, ClassMask, ClassCount, WordIndex);
+ }
+ }
+} /* AddProtoToClassPruner */
+
+/**
+ * This routine updates the proto pruner lookup tables
+ * for Class to include a new proto identified by ProtoId
+ * and described by Proto.
+ * @param Proto floating-pt proto to be added to proto pruner
+ * @param ProtoId id of proto
+ * @param Class integer class that contains desired proto pruner
+ * @param debug debug flag
+ * @note Globals: none
+ */
+void AddProtoToProtoPruner(PROTO Proto, int ProtoId,
+ INT_CLASS Class, bool debug) {
+ float Angle, X, Y, Length;
+ float Pad;
+ int Index;
+ PROTO_SET ProtoSet;
+
+ if (ProtoId >= Class->NumProtos)
+ tprintf("AddProtoToProtoPruner:assert failed: %d < %d",
+ ProtoId, Class->NumProtos);
+ assert(ProtoId < Class->NumProtos);
+
+ Index = IndexForProto (ProtoId);
+ ProtoSet = Class->ProtoSets[SetForProto (ProtoId)];
+
+ Angle = Proto->Angle;
+#ifndef _WIN32
+ assert(!std::isnan(Angle));
+#endif
+
+ FillPPCircularBits (ProtoSet->ProtoPruner[PRUNER_ANGLE], Index,
+ Angle + ANGLE_SHIFT, classify_pp_angle_pad / 360.0,
+ debug);
+
+ Angle *= 2.0 * M_PI;
+ Length = Proto->Length;
+
+ X = Proto->X + X_SHIFT;
+ Pad = std::max(fabs (cos (Angle)) * (Length / 2.0 +
+ classify_pp_end_pad *
+ GetPicoFeatureLength ()),
+ fabs (sin (Angle)) * (classify_pp_side_pad *
+ GetPicoFeatureLength ()));
+
+ FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_X], Index, X, Pad, debug);
+
+ Y = Proto->Y + Y_SHIFT;
+ Pad = std::max(fabs (sin (Angle)) * (Length / 2.0 +
+ classify_pp_end_pad *
+ GetPicoFeatureLength ()),
+ fabs (cos (Angle)) * (classify_pp_side_pad *
+ GetPicoFeatureLength ()));
+
+ FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_Y], Index, Y, Pad, debug);
+} /* AddProtoToProtoPruner */
+
+/**
+ * Returns a quantized bucket for the given param shifted by offset,
+ * notionally (param + offset) * num_buckets, but clipped and casted to the
+ * appropriate type.
+ */
+uint8_t Bucket8For(float param, float offset, int num_buckets) {
+ int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
+ return static_cast<uint8_t>(ClipToRange<int>(bucket, 0, num_buckets - 1));
+}
+uint16_t Bucket16For(float param, float offset, int num_buckets) {
+ int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
+ return static_cast<uint16_t>(ClipToRange<int>(bucket, 0, num_buckets - 1));
+}
+
+/**
+ * Returns a quantized bucket for the given circular param shifted by offset,
+ * notionally (param + offset) * num_buckets, but modded and casted to the
+ * appropriate type.
+ */
+uint8_t CircBucketFor(float param, float offset, int num_buckets) {
+ int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
+ return static_cast<uint8_t>(Modulo(bucket, num_buckets));
+} /* CircBucketFor */
+
+
+#ifndef GRAPHICS_DISABLED
+/**
+ * This routine clears the global feature and proto
+ * display lists.
+ *
+ * Globals:
+ * - FeatureShapes display list for features
+ * - ProtoShapes display list for protos
+ */
+void UpdateMatchDisplay() {
+ if (IntMatchWindow != nullptr)
+ IntMatchWindow->Update();
+} /* ClearMatchDisplay */
+#endif
+
+/**
+ * This operation updates the config vectors of all protos
+ * in Class to indicate that the protos with 1's in Config
+ * belong to a new configuration identified by ConfigId.
+ * It is assumed that the length of the Config bit vector is
+ * equal to the number of protos in Class.
+ * @param Config config to be added to class
+ * @param ConfigId id to be used for new config
+ * @param Class class to add new config to
+ */
+void ConvertConfig(BIT_VECTOR Config, int ConfigId, INT_CLASS Class) {
+ int ProtoId;
+ INT_PROTO Proto;
+ int TotalLength;
+
+ for (ProtoId = 0, TotalLength = 0;
+ ProtoId < Class->NumProtos; ProtoId++) {
+ if (test_bit(Config, ProtoId)) {
+ Proto = ProtoForProtoId(Class, ProtoId);
+ SET_BIT(Proto->Configs, ConfigId);
+ TotalLength += Class->ProtoLengths[ProtoId];
+ }
+ }
+ Class->ConfigLengths[ConfigId] = TotalLength;
+} /* ConvertConfig */
+
+/**
+ * This routine converts Proto to integer format and
+ * installs it as ProtoId in Class.
+ * @param Proto floating-pt proto to be converted to integer format
+ * @param ProtoId id of proto
+ * @param Class integer class to add converted proto to
+ */
+void Classify::ConvertProto(PROTO Proto, int ProtoId, INT_CLASS Class) {
+ assert(ProtoId < Class->NumProtos);
+
+ INT_PROTO P = ProtoForProtoId(Class, ProtoId);
+
+ float Param = Proto->A * 128;
+ P->A = TruncateParam(Param, -128, 127);
+
+ Param = -Proto->B * 256;
+ P->B = TruncateParam(Param, 0, 255);
+
+ Param = Proto->C * 128;
+ P->C = TruncateParam(Param, -128, 127);
+
+ Param = Proto->Angle * 256;
+ if (Param < 0 || Param >= 256)
+ P->Angle = 0;
+ else
+ P->Angle = static_cast<uint8_t>(Param);
+
+ /* round proto length to nearest integer number of pico-features */
+ Param = (Proto->Length / GetPicoFeatureLength()) + 0.5;
+ Class->ProtoLengths[ProtoId] = TruncateParam(Param, 1, 255);
+ if (classify_learning_debug_level >= 2)
+ tprintf("Converted ffeat to (A=%d,B=%d,C=%d,L=%d)",
+ P->A, P->B, P->C, Class->ProtoLengths[ProtoId]);
+} /* ConvertProto */
+
+/**
+ * This routine converts from the old floating point format
+ * to the new integer format.
+ * @param FloatProtos prototypes in old floating pt format
+ * @param target_unicharset the UNICHARSET to use
+ * @return New set of training templates in integer format.
+ * @note Globals: none
+ */
+INT_TEMPLATES Classify::CreateIntTemplates(CLASSES FloatProtos,
+ const UNICHARSET&
+ target_unicharset) {
+ INT_TEMPLATES IntTemplates;
+ CLASS_TYPE FClass;
+ INT_CLASS IClass;
+ int ClassId;
+ int ProtoId;
+ int ConfigId;
+
+ IntTemplates = NewIntTemplates();
+
+ for (ClassId = 0; ClassId < target_unicharset.size(); ClassId++) {
+ FClass = &(FloatProtos[ClassId]);
+ if (FClass->NumProtos == 0 && FClass->NumConfigs == 0 &&
+ strcmp(target_unicharset.id_to_unichar(ClassId), " ") != 0) {
+ tprintf("Warning: no protos/configs for %s in CreateIntTemplates()\n",
+ target_unicharset.id_to_unichar(ClassId));
+ }
+ assert(UnusedClassIdIn(IntTemplates, ClassId));
+ IClass = NewIntClass(FClass->NumProtos, FClass->NumConfigs);
+ FontSet fs;
+ fs.size = FClass->font_set.size();
+ fs.configs = new int[fs.size];
+ for (int i = 0; i < fs.size; ++i) {
+ fs.configs[i] = FClass->font_set.get(i);
+ }
+ if (this->fontset_table_.contains(fs)) {
+ IClass->font_set_id = this->fontset_table_.get_id(fs);
+ delete[] fs.configs;
+ } else {
+ IClass->font_set_id = this->fontset_table_.push_back(fs);
+ }
+ AddIntClass(IntTemplates, ClassId, IClass);
+
+ for (ProtoId = 0; ProtoId < FClass->NumProtos; ProtoId++) {
+ AddIntProto(IClass);
+ ConvertProto(ProtoIn(FClass, ProtoId), ProtoId, IClass);
+ AddProtoToProtoPruner(ProtoIn(FClass, ProtoId), ProtoId, IClass,
+ classify_learning_debug_level >= 2);
+ AddProtoToClassPruner(ProtoIn(FClass, ProtoId), ClassId, IntTemplates);
+ }
+
+ for (ConfigId = 0; ConfigId < FClass->NumConfigs; ConfigId++) {
+ AddIntConfig(IClass);
+ ConvertConfig(FClass->Configurations[ConfigId], ConfigId, IClass);
+ }
+ }
+ return (IntTemplates);
+} /* CreateIntTemplates */
+
+#ifndef GRAPHICS_DISABLED
+/**
+ * This routine renders the specified feature into a
+ * global display list.
+ *
+ * Globals:
+ * - FeatureShapes global display list for features
+ * @param Feature pico-feature to be displayed
+ * @param Evidence best evidence for this feature (0-1)
+ */
+void DisplayIntFeature(const INT_FEATURE_STRUCT *Feature, float Evidence) {
+ ScrollView::Color color = GetMatchColorFor(Evidence);
+ RenderIntFeature(IntMatchWindow, Feature, color);
+ if (FeatureDisplayWindow) {
+ RenderIntFeature(FeatureDisplayWindow, Feature, color);
+ }
+} /* DisplayIntFeature */
+
+/**
+ * This routine renders the specified proto into a
+ * global display list.
+ *
+ * Globals:
+ * - ProtoShapes global display list for protos
+ * @param Class class to take proto from
+ * @param ProtoId id of proto in Class to be displayed
+ * @param Evidence total evidence for proto (0-1)
+ */
+void DisplayIntProto(INT_CLASS Class, PROTO_ID ProtoId, float Evidence) {
+ ScrollView::Color color = GetMatchColorFor(Evidence);
+ RenderIntProto(IntMatchWindow, Class, ProtoId, color);
+ if (ProtoDisplayWindow) {
+ RenderIntProto(ProtoDisplayWindow, Class, ProtoId, color);
+ }
+} /* DisplayIntProto */
+#endif
+
+/**
+ * This routine creates a new integer class data structure
+ * and returns it. Sufficient space is allocated
+ * to handle the specified number of protos and configs.
+ * @param MaxNumProtos number of protos to allocate space for
+ * @param MaxNumConfigs number of configs to allocate space for
+ * @return New class created.
+ * @note Globals: none
+ */
+INT_CLASS NewIntClass(int MaxNumProtos, int MaxNumConfigs) {
+ INT_CLASS Class;
+ PROTO_SET ProtoSet;
+ int i;
+
+ assert(MaxNumConfigs <= MAX_NUM_CONFIGS);
+
+ Class = static_cast<INT_CLASS>(malloc(sizeof(INT_CLASS_STRUCT)));
+ Class->NumProtoSets = ((MaxNumProtos + PROTOS_PER_PROTO_SET - 1) /
+ PROTOS_PER_PROTO_SET);
+
+ assert(Class->NumProtoSets <= MAX_NUM_PROTO_SETS);
+
+ Class->NumProtos = 0;
+ Class->NumConfigs = 0;
+
+ for (i = 0; i < Class->NumProtoSets; i++) {
+ /* allocate space for a proto set, install in class, and initialize */
+ ProtoSet = static_cast<PROTO_SET>(malloc(sizeof(PROTO_SET_STRUCT)));
+ memset(ProtoSet, 0, sizeof(*ProtoSet));
+ Class->ProtoSets[i] = ProtoSet;
+
+ /* allocate space for the proto lengths and install in class */
+ }
+ if (MaxNumIntProtosIn (Class) > 0) {
+ Class->ProtoLengths =
+ static_cast<uint8_t *>(malloc(MaxNumIntProtosIn (Class) * sizeof (uint8_t)));
+ memset(Class->ProtoLengths, 0,
+ MaxNumIntProtosIn(Class) * sizeof(*Class->ProtoLengths));
+ } else {
+ Class->ProtoLengths = nullptr;
+ }
+ memset(Class->ConfigLengths, 0, sizeof(Class->ConfigLengths));
+
+ return (Class);
+
+} /* NewIntClass */
+
+static void free_int_class(INT_CLASS int_class) {
+ int i;
+
+ for (i = 0; i < int_class->NumProtoSets; i++) {
+ free (int_class->ProtoSets[i]);
+ }
+ if (int_class->ProtoLengths != nullptr) {
+ free (int_class->ProtoLengths);
+ }
+ free(int_class);
+}
+
+/**
+ * This routine allocates a new set of integer templates
+ * initialized to hold 0 classes.
+ * @return The integer templates created.
+ * @note Globals: none
+ */
+INT_TEMPLATES NewIntTemplates() {
+ INT_TEMPLATES T;
+ int i;
+
+ T = static_cast<INT_TEMPLATES>(malloc (sizeof (INT_TEMPLATES_STRUCT)));
+ T->NumClasses = 0;
+ T->NumClassPruners = 0;
+
+ for (i = 0; i < MAX_NUM_CLASSES; i++)
+ ClassForClassId (T, i) = nullptr;
+
+ return (T);
+} /* NewIntTemplates */
+
+
+/*---------------------------------------------------------------------------*/
+void free_int_templates(INT_TEMPLATES templates) {
+ int i;
+
+ for (i = 0; i < templates->NumClasses; i++)
+ free_int_class(templates->Class[i]);
+ for (i = 0; i < templates->NumClassPruners; i++)
+ delete templates->ClassPruners[i];
+ free(templates);
+}
+
+/**
+ * This routine reads a set of integer templates from
+ * File. File must already be open and must be in the
+ * correct binary format.
+ * @param fp open file to read templates from
+ * @return Pointer to integer templates read from File.
+ * @note Globals: none
+ */
+INT_TEMPLATES Classify::ReadIntTemplates(TFile *fp) {
+ int i, j, w, x, y, z;
+ int unicharset_size;
+ int version_id = 0;
+ INT_TEMPLATES Templates;
+ CLASS_PRUNER_STRUCT* Pruner;
+ INT_CLASS Class;
+ uint8_t *Lengths;
+ PROTO_SET ProtoSet;
+
+ /* variables for conversion from older inttemp formats */
+ int b, bit_number, last_cp_bit_number, new_b, new_i, new_w;
+ CLASS_ID class_id, max_class_id;
+ auto *IndexFor = new int16_t[MAX_NUM_CLASSES];
+ auto *ClassIdFor = new CLASS_ID[MAX_NUM_CLASSES];
+ auto **TempClassPruner =
+ new CLASS_PRUNER_STRUCT*[MAX_NUM_CLASS_PRUNERS];
+ uint32_t SetBitsForMask = // word with NUM_BITS_PER_CLASS
+ (1 << NUM_BITS_PER_CLASS) - 1; // set starting at bit 0
+ uint32_t Mask, NewMask, ClassBits;
+ int MaxNumConfigs = MAX_NUM_CONFIGS;
+ int WerdsPerConfigVec = WERDS_PER_CONFIG_VEC;
+
+ /* first read the high level template struct */
+ Templates = NewIntTemplates();
+ // Read Templates in parts for 64 bit compatibility.
+ if (fp->FReadEndian(&unicharset_size, sizeof(unicharset_size), 1) != 1)
+ tprintf("Bad read of inttemp!\n");
+ if (fp->FReadEndian(&Templates->NumClasses, sizeof(Templates->NumClasses),
+ 1) != 1 ||
+ fp->FReadEndian(&Templates->NumClassPruners,
+ sizeof(Templates->NumClassPruners), 1) != 1)
+ tprintf("Bad read of inttemp!\n");
+ if (Templates->NumClasses < 0) {
+ // This file has a version id!
+ version_id = -Templates->NumClasses;
+ if (fp->FReadEndian(&Templates->NumClasses, sizeof(Templates->NumClasses),
+ 1) != 1)
+ tprintf("Bad read of inttemp!\n");
+ }
+
+ if (version_id < 3) {
+ MaxNumConfigs = OLD_MAX_NUM_CONFIGS;
+ WerdsPerConfigVec = OLD_WERDS_PER_CONFIG_VEC;
+ }
+
+ if (version_id < 2) {
+ if (fp->FReadEndian(IndexFor, sizeof(IndexFor[0]), unicharset_size) !=
+ unicharset_size) {
+ tprintf("Bad read of inttemp!\n");
+ }
+ if (fp->FReadEndian(ClassIdFor, sizeof(ClassIdFor[0]),
+ Templates->NumClasses) != Templates->NumClasses) {
+ tprintf("Bad read of inttemp!\n");
+ }
+ }
+
+ /* then read in the class pruners */
+ const int kNumBuckets =
+ NUM_CP_BUCKETS * NUM_CP_BUCKETS * NUM_CP_BUCKETS * WERDS_PER_CP_VECTOR;
+ for (i = 0; i < Templates->NumClassPruners; i++) {
+ Pruner = new CLASS_PRUNER_STRUCT;
+ if (fp->FReadEndian(Pruner, sizeof(Pruner->p[0][0][0][0]), kNumBuckets) !=
+ kNumBuckets) {
+ tprintf("Bad read of inttemp!\n");
+ }
+ if (version_id < 2) {
+ TempClassPruner[i] = Pruner;
+ } else {
+ Templates->ClassPruners[i] = Pruner;
+ }
+ }
+
+ /* fix class pruners if they came from an old version of inttemp */
+ if (version_id < 2) {
+ // Allocate enough class pruners to cover all the class ids.
+ max_class_id = 0;
+ for (i = 0; i < Templates->NumClasses; i++)
+ if (ClassIdFor[i] > max_class_id)
+ max_class_id = ClassIdFor[i];
+ for (i = 0; i <= CPrunerIdFor(max_class_id); i++) {
+ Templates->ClassPruners[i] = new CLASS_PRUNER_STRUCT;
+ memset(Templates->ClassPruners[i], 0, sizeof(CLASS_PRUNER_STRUCT));
+ }
+ // Convert class pruners from the old format (indexed by class index)
+ // to the new format (indexed by class id).
+ last_cp_bit_number = NUM_BITS_PER_CLASS * Templates->NumClasses - 1;
+ for (i = 0; i < Templates->NumClassPruners; i++) {
+ for (x = 0; x < NUM_CP_BUCKETS; x++)
+ for (y = 0; y < NUM_CP_BUCKETS; y++)
+ for (z = 0; z < NUM_CP_BUCKETS; z++)
+ for (w = 0; w < WERDS_PER_CP_VECTOR; w++) {
+ if (TempClassPruner[i]->p[x][y][z][w] == 0)
+ continue;
+ for (b = 0; b < BITS_PER_WERD; b += NUM_BITS_PER_CLASS) {
+ bit_number = i * BITS_PER_CP_VECTOR + w * BITS_PER_WERD + b;
+ if (bit_number > last_cp_bit_number)
+ break; // the rest of the bits in this word are not used
+ class_id = ClassIdFor[bit_number / NUM_BITS_PER_CLASS];
+ // Single out NUM_BITS_PER_CLASS bits relating to class_id.
+ Mask = SetBitsForMask << b;
+ ClassBits = TempClassPruner[i]->p[x][y][z][w] & Mask;
+ // Move these bits to the new position in which they should
+ // appear (indexed corresponding to the class_id).
+ new_i = CPrunerIdFor(class_id);
+ new_w = CPrunerWordIndexFor(class_id);
+ new_b = CPrunerBitIndexFor(class_id) * NUM_BITS_PER_CLASS;
+ if (new_b > b) {
+ ClassBits <<= (new_b - b);
+ } else {
+ ClassBits >>= (b - new_b);
+ }
+ // Copy bits relating to class_id to the correct position
+ // in Templates->ClassPruner.
+ NewMask = SetBitsForMask << new_b;
+ Templates->ClassPruners[new_i]->p[x][y][z][new_w] &= ~NewMask;
+ Templates->ClassPruners[new_i]->p[x][y][z][new_w] |= ClassBits;
+ }
+ }
+ }
+ for (i = 0; i < Templates->NumClassPruners; i++) {
+ delete TempClassPruner[i];
+ }
+ }
+
+ /* then read in each class */
+ for (i = 0; i < Templates->NumClasses; i++) {
+ /* first read in the high level struct for the class */
+ Class = static_cast<INT_CLASS>(malloc (sizeof (INT_CLASS_STRUCT)));
+ if (fp->FReadEndian(&Class->NumProtos, sizeof(Class->NumProtos), 1) != 1 ||
+ fp->FRead(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1) != 1 ||
+ fp->FRead(&Class->NumConfigs, sizeof(Class->NumConfigs), 1) != 1)
+ tprintf("Bad read of inttemp!\n");
+ if (version_id == 0) {
+ // Only version 0 writes 5 pointless pointers to the file.
+ for (j = 0; j < 5; ++j) {
+ int32_t junk;
+ if (fp->FRead(&junk, sizeof(junk), 1) != 1)
+ tprintf("Bad read of inttemp!\n");
+ }
+ }
+ int num_configs = version_id < 4 ? MaxNumConfigs : Class->NumConfigs;
+ ASSERT_HOST(num_configs <= MaxNumConfigs);
+ if (fp->FReadEndian(Class->ConfigLengths, sizeof(uint16_t), num_configs) !=
+ num_configs) {
+ tprintf("Bad read of inttemp!\n");
+ }
+ if (version_id < 2) {
+ ClassForClassId (Templates, ClassIdFor[i]) = Class;
+ } else {
+ ClassForClassId (Templates, i) = Class;
+ }
+
+ /* then read in the proto lengths */
+ Lengths = nullptr;
+ if (MaxNumIntProtosIn (Class) > 0) {
+ Lengths = static_cast<uint8_t *>(malloc(sizeof(uint8_t) * MaxNumIntProtosIn(Class)));
+ if (fp->FRead(Lengths, sizeof(uint8_t), MaxNumIntProtosIn(Class)) !=
+ MaxNumIntProtosIn(Class))
+ tprintf("Bad read of inttemp!\n");
+ }
+ Class->ProtoLengths = Lengths;
+
+ /* then read in the proto sets */
+ for (j = 0; j < Class->NumProtoSets; j++) {
+ ProtoSet = static_cast<PROTO_SET>(malloc(sizeof(PROTO_SET_STRUCT)));
+ int num_buckets = NUM_PP_PARAMS * NUM_PP_BUCKETS * WERDS_PER_PP_VECTOR;
+ if (fp->FReadEndian(&ProtoSet->ProtoPruner,
+ sizeof(ProtoSet->ProtoPruner[0][0][0]),
+ num_buckets) != num_buckets)
+ tprintf("Bad read of inttemp!\n");
+ for (x = 0; x < PROTOS_PER_PROTO_SET; x++) {
+ if (fp->FRead(&ProtoSet->Protos[x].A, sizeof(ProtoSet->Protos[x].A),
+ 1) != 1 ||
+ fp->FRead(&ProtoSet->Protos[x].B, sizeof(ProtoSet->Protos[x].B),
+ 1) != 1 ||
+ fp->FRead(&ProtoSet->Protos[x].C, sizeof(ProtoSet->Protos[x].C),
+ 1) != 1 ||
+ fp->FRead(&ProtoSet->Protos[x].Angle,
+ sizeof(ProtoSet->Protos[x].Angle), 1) != 1)
+ tprintf("Bad read of inttemp!\n");
+ if (fp->FReadEndian(&ProtoSet->Protos[x].Configs,
+ sizeof(ProtoSet->Protos[x].Configs[0]),
+ WerdsPerConfigVec) != WerdsPerConfigVec)
+ tprintf("Bad read of inttemp!\n");
+ }
+ Class->ProtoSets[j] = ProtoSet;
+ }
+ if (version_id < 4) {
+ Class->font_set_id = -1;
+ } else {
+ fp->FReadEndian(&Class->font_set_id, sizeof(Class->font_set_id), 1);
+ }
+ }
+
+ if (version_id < 2) {
+ /* add an empty nullptr class with class id 0 */
+ assert(UnusedClassIdIn (Templates, 0));
+ ClassForClassId (Templates, 0) = NewIntClass (1, 1);
+ ClassForClassId (Templates, 0)->font_set_id = -1;
+ Templates->NumClasses++;
+ /* make sure the classes are contiguous */
+ for (i = 0; i < MAX_NUM_CLASSES; i++) {
+ if (i < Templates->NumClasses) {
+ if (ClassForClassId (Templates, i) == nullptr) {
+ fprintf(stderr, "Non-contiguous class ids in inttemp\n");
+ exit(1);
+ }
+ } else {
+ if (ClassForClassId (Templates, i) != nullptr) {
+ fprintf(stderr, "Class id %d exceeds NumClassesIn (Templates) %d\n",
+ i, Templates->NumClasses);
+ exit(1);
+ }
+ }
+ }
+ }
+ if (version_id >= 4) {
+ using namespace std::placeholders; // for _1, _2
+ this->fontinfo_table_.read(fp, std::bind(read_info, _1, _2));
+ if (version_id >= 5) {
+ this->fontinfo_table_.read(fp,
+ std::bind(read_spacing_info, _1, _2));
+ }
+ this->fontset_table_.read(fp, std::bind(read_set, _1, _2));
+ }
+
+ // Clean up.
+ delete[] IndexFor;
+ delete[] ClassIdFor;
+ delete[] TempClassPruner;
+
+ return (Templates);
+} /* ReadIntTemplates */
+
+
+#ifndef GRAPHICS_DISABLED
+/**
+ * This routine sends the shapes in the global display
+ * lists to the match debugger window.
+ *
+ * Globals:
+ * - FeatureShapes display list containing feature matches
+ * - ProtoShapes display list containing proto matches
+ */
+void Classify::ShowMatchDisplay() {
+ InitIntMatchWindowIfReqd();
+ if (ProtoDisplayWindow) {
+ ProtoDisplayWindow->Clear();
+ }
+ if (FeatureDisplayWindow) {
+ FeatureDisplayWindow->Clear();
+ }
+ ClearFeatureSpaceWindow(
+ static_cast<NORM_METHOD>(static_cast<int>(classify_norm_method)),
+ IntMatchWindow);
+ IntMatchWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y,
+ INT_MAX_X, INT_MAX_Y);
+ if (ProtoDisplayWindow) {
+ ProtoDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y,
+ INT_MAX_X, INT_MAX_Y);
+ }
+ if (FeatureDisplayWindow) {
+ FeatureDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y,
+ INT_MAX_X, INT_MAX_Y);
+ }
+} /* ShowMatchDisplay */
+
+/// Clears the given window and draws the featurespace guides for the
+/// appropriate normalization method.
+void ClearFeatureSpaceWindow(NORM_METHOD norm_method, ScrollView* window) {
+ window->Clear();
+
+ window->Pen(ScrollView::GREY);
+ // Draw the feature space limit rectangle.
+ window->Rectangle(0, 0, INT_MAX_X, INT_MAX_Y);
+ if (norm_method == baseline) {
+ window->SetCursor(0, INT_DESCENDER);
+ window->DrawTo(INT_MAX_X, INT_DESCENDER);
+ window->SetCursor(0, INT_BASELINE);
+ window->DrawTo(INT_MAX_X, INT_BASELINE);
+ window->SetCursor(0, INT_XHEIGHT);
+ window->DrawTo(INT_MAX_X, INT_XHEIGHT);
+ window->SetCursor(0, INT_CAPHEIGHT);
+ window->DrawTo(INT_MAX_X, INT_CAPHEIGHT);
+ } else {
+ window->Rectangle(INT_XCENTER - INT_XRADIUS, INT_YCENTER - INT_YRADIUS,
+ INT_XCENTER + INT_XRADIUS, INT_YCENTER + INT_YRADIUS);
+ }
+}
+#endif
+
+/**
+ * This routine writes Templates to File. The format
+ * is an efficient binary format. File must already be open
+ * for writing.
+ * @param File open file to write templates to
+ * @param Templates templates to save into File
+ * @param target_unicharset the UNICHARSET to use
+ */
+void Classify::WriteIntTemplates(FILE *File, INT_TEMPLATES Templates,
+ const UNICHARSET& target_unicharset) {
+ int i, j;
+ INT_CLASS Class;
+ int unicharset_size = target_unicharset.size();
+ int version_id = -5; // When negated by the reader -1 becomes +1 etc.
+
+ if (Templates->NumClasses != unicharset_size) {
+ tprintf("Warning: executing WriteIntTemplates() with %d classes in"
+ " Templates, while target_unicharset size is %d\n",
+ Templates->NumClasses, unicharset_size);
+ }
+
+ /* first write the high level template struct */
+ fwrite(&unicharset_size, sizeof(unicharset_size), 1, File);
+ fwrite(&version_id, sizeof(version_id), 1, File);
+ fwrite(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners),
+ 1, File);
+ fwrite(&Templates->NumClasses, sizeof(Templates->NumClasses), 1, File);
+
+ /* then write out the class pruners */
+ for (i = 0; i < Templates->NumClassPruners; i++)
+ fwrite(Templates->ClassPruners[i],
+ sizeof(CLASS_PRUNER_STRUCT), 1, File);
+
+ /* then write out each class */
+ for (i = 0; i < Templates->NumClasses; i++) {
+ Class = Templates->Class[i];
+
+ /* first write out the high level struct for the class */
+ fwrite(&Class->NumProtos, sizeof(Class->NumProtos), 1, File);
+ fwrite(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1, File);
+ ASSERT_HOST(Class->NumConfigs == this->fontset_table_.get(Class->font_set_id).size);
+ fwrite(&Class->NumConfigs, sizeof(Class->NumConfigs), 1, File);
+ for (j = 0; j < Class->NumConfigs; ++j) {
+ fwrite(&Class->ConfigLengths[j], sizeof(uint16_t), 1, File);
+ }
+
+ /* then write out the proto lengths */
+ if (MaxNumIntProtosIn (Class) > 0) {
+ fwrite(Class->ProtoLengths, sizeof(uint8_t),
+ MaxNumIntProtosIn(Class), File);
+ }
+
+ /* then write out the proto sets */
+ for (j = 0; j < Class->NumProtoSets; j++)
+ fwrite(Class->ProtoSets[j], sizeof(PROTO_SET_STRUCT), 1, File);
+
+ /* then write the fonts info */
+ fwrite(&Class->font_set_id, sizeof(int), 1, File);
+ }
+
+ /* Write the fonts info tables */
+ using namespace std::placeholders; // for _1, _2
+ this->fontinfo_table_.write(File, std::bind(write_info, _1, _2));
+ this->fontinfo_table_.write(File,
+ std::bind(write_spacing_info, _1, _2));
+ this->fontset_table_.write(File, std::bind(write_set, _1, _2));
+} /* WriteIntTemplates */
+
+/*-----------------------------------------------------------------------------
+ Private Code
+-----------------------------------------------------------------------------*/
+/**
+ * This routine returns the parameter value which
+ * corresponds to the beginning of the specified bucket.
+ * The bucket number should have been generated using the
+ * BucketFor() function with parameters Offset and NumBuckets.
+ * @param Bucket bucket whose start is to be computed
+ * @param Offset offset used to map params to buckets
+ * @param NumBuckets total number of buckets
+ * @return Param value corresponding to start position of Bucket.
+ * @note Globals: none
+ */
+float BucketStart(int Bucket, float Offset, int NumBuckets) {
+ return ((static_cast<float>(Bucket) / NumBuckets) - Offset);
+
+} /* BucketStart */
+
+/**
+ * This routine returns the parameter value which
+ * corresponds to the end of the specified bucket.
+ * The bucket number should have been generated using the
+ * BucketFor() function with parameters Offset and NumBuckets.
+ * @param Bucket bucket whose end is to be computed
+ * @param Offset offset used to map params to buckets
+ * @param NumBuckets total number of buckets
+ * @return Param value corresponding to end position of Bucket.
+ * @note Globals: none
+ */
+float BucketEnd(int Bucket, float Offset, int NumBuckets) {
+ return ((static_cast<float>(Bucket + 1) / NumBuckets) - Offset);
+} /* BucketEnd */
+
+/**
+ * This routine fills in the section of a class pruner
+ * corresponding to a single x value for a single proto of
+ * a class.
+ * @param FillSpec specifies which bits to fill in pruner
+ * @param Pruner class pruner to be filled
+ * @param ClassMask indicates which bits to change in each word
+ * @param ClassCount indicates what to change bits to
+ * @param WordIndex indicates which word to change
+ */
+void DoFill(FILL_SPEC *FillSpec,
+ CLASS_PRUNER_STRUCT* Pruner,
+ uint32_t ClassMask,
+ uint32_t ClassCount,
+ uint32_t WordIndex) {
+ int X, Y, Angle;
+ uint32_t OldWord;
+
+ X = FillSpec->X;
+ if (X < 0)
+ X = 0;
+ if (X >= NUM_CP_BUCKETS)
+ X = NUM_CP_BUCKETS - 1;
+
+ if (FillSpec->YStart < 0)
+ FillSpec->YStart = 0;
+ if (FillSpec->YEnd >= NUM_CP_BUCKETS)
+ FillSpec->YEnd = NUM_CP_BUCKETS - 1;
+
+ for (Y = FillSpec->YStart; Y <= FillSpec->YEnd; Y++)
+ for (Angle = FillSpec->AngleStart; ;
+ CircularIncrement(Angle, NUM_CP_BUCKETS)) {
+ OldWord = Pruner->p[X][Y][Angle][WordIndex];
+ if (ClassCount > (OldWord & ClassMask)) {
+ OldWord &= ~ClassMask;
+ OldWord |= ClassCount;
+ Pruner->p[X][Y][Angle][WordIndex] = OldWord;
+ }
+ if (Angle == FillSpec->AngleEnd)
+ break;
+ }
+} /* DoFill */
+
+/**
+ * Return true if the specified table filler is done, i.e.
+ * if it has no more lines to fill.
+ * @param Filler table filler to check if done
+ * @return true if no more lines to fill, false otherwise.
+ * @note Globals: none
+ */
+bool FillerDone(TABLE_FILLER* Filler) {
+ FILL_SWITCH *Next;
+
+ Next = &(Filler->Switch[Filler->NextSwitch]);
+
+ return Filler->X > Next->X && Next->Type == LastSwitch;
+
+} /* FillerDone */
+
+/**
+ * This routine sets Bit in each bit vector whose
+ * bucket lies within the range Center +- Spread. The fill
+ * is done for a circular dimension, i.e. bucket 0 is adjacent
+ * to the last bucket. It is assumed that Center and Spread
+ * are expressed in a circular coordinate system whose range
+ * is 0 to 1.
+ * @param ParamTable table of bit vectors, one per param bucket
+ * @param Bit bit position in vectors to be filled
+ * @param Center center of filled area
+ * @param Spread spread of filled area
+ * @param debug debug flag
+ */
+void FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR],
+ int Bit, float Center, float Spread, bool debug) {
+ int i, FirstBucket, LastBucket;
+
+ if (Spread > 0.5)
+ Spread = 0.5;
+
+ FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
+ if (FirstBucket < 0)
+ FirstBucket += NUM_PP_BUCKETS;
+
+ LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
+ if (LastBucket >= NUM_PP_BUCKETS)
+ LastBucket -= NUM_PP_BUCKETS;
+ if (debug) tprintf("Circular fill from %d to %d", FirstBucket, LastBucket);
+ for (i = FirstBucket; true; CircularIncrement (i, NUM_PP_BUCKETS)) {
+ SET_BIT (ParamTable[i], Bit);
+
+ /* exit loop after we have set the bit for the last bucket */
+ if (i == LastBucket)
+ break;
+ }
+
+} /* FillPPCircularBits */
+
+/**
+ * This routine sets Bit in each bit vector whose
+ * bucket lies within the range Center +- Spread. The fill
+ * is done for a linear dimension, i.e. there is no wrap-around
+ * for this dimension. It is assumed that Center and Spread
+ * are expressed in a linear coordinate system whose range
+ * is approximately 0 to 1. Values outside this range will
+ * be clipped.
+ * @param ParamTable table of bit vectors, one per param bucket
+ * @param Bit bit number being filled
+ * @param Center center of filled area
+ * @param Spread spread of filled area
+ * @param debug debug flag
+ */
+void FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR],
+ int Bit, float Center, float Spread, bool debug) {
+ int i, FirstBucket, LastBucket;
+
+ FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
+ if (FirstBucket < 0)
+ FirstBucket = 0;
+
+ LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
+ if (LastBucket >= NUM_PP_BUCKETS)
+ LastBucket = NUM_PP_BUCKETS - 1;
+
+ if (debug) tprintf("Linear fill from %d to %d", FirstBucket, LastBucket);
+ for (i = FirstBucket; i <= LastBucket; i++)
+ SET_BIT (ParamTable[i], Bit);
+
+} /* FillPPLinearBits */
+
+
+/*---------------------------------------------------------------------------*/
+#ifndef GRAPHICS_DISABLED
+/**
+ * This routine prompts the user with Prompt and waits
+ * for the user to enter something in the debug window.
+ * @param Prompt prompt to print while waiting for input from window
+ * @param adaptive_on
+ * @param pretrained_on
+ * @param shape_id
+ * @return Character entered in the debug window.
+ * @note Globals: none
+ */
+CLASS_ID Classify::GetClassToDebug(const char *Prompt, bool* adaptive_on,
+ bool* pretrained_on, int* shape_id) {
+ tprintf("%s\n", Prompt);
+ SVEvent* ev;
+ SVEventType ev_type;
+ int unichar_id = INVALID_UNICHAR_ID;
+ // Wait until a click or popup event.
+ do {
+ ev = IntMatchWindow->AwaitEvent(SVET_ANY);
+ ev_type = ev->type;
+ if (ev_type == SVET_POPUP) {
+ if (ev->command_id == IDA_SHAPE_INDEX) {
+ if (shape_table_ != nullptr) {
+ *shape_id = atoi(ev->parameter);
+ *adaptive_on = false;
+ *pretrained_on = true;
+ if (*shape_id >= 0 && *shape_id < shape_table_->NumShapes()) {
+ int font_id;
+ shape_table_->GetFirstUnicharAndFont(*shape_id, &unichar_id,
+ &font_id);
+ tprintf("Shape %d, first unichar=%d, font=%d\n",
+ *shape_id, unichar_id, font_id);
+ return unichar_id;
+ }
+ tprintf("Shape index '%s' not found in shape table\n", ev->parameter);
+ } else {
+ tprintf("No shape table loaded!\n");
+ }
+ } else {
+ if (unicharset.contains_unichar(ev->parameter)) {
+ unichar_id = unicharset.unichar_to_id(ev->parameter);
+ if (ev->command_id == IDA_ADAPTIVE) {
+ *adaptive_on = true;
+ *pretrained_on = false;
+ *shape_id = -1;
+ } else if (ev->command_id == IDA_STATIC) {
+ *adaptive_on = false;
+ *pretrained_on = true;
+ } else {
+ *adaptive_on = true;
+ *pretrained_on = true;
+ }
+ if (ev->command_id == IDA_ADAPTIVE || shape_table_ == nullptr) {
+ *shape_id = -1;
+ return unichar_id;
+ }
+ for (int s = 0; s < shape_table_->NumShapes(); ++s) {
+ if (shape_table_->GetShape(s).ContainsUnichar(unichar_id)) {
+ tprintf("%s\n", shape_table_->DebugStr(s).c_str());
+ }
+ }
+ } else {
+ tprintf("Char class '%s' not found in unicharset",
+ ev->parameter);
+ }
+ }
+ }
+ delete ev;
+ } while (ev_type != SVET_CLICK);
+ return 0;
+} /* GetClassToDebug */
+
+#endif
+
+/**
+ * This routine copies the appropriate global pad variables
+ * into EndPad, SidePad, and AnglePad. This is a kludge used
+ * to get around the fact that global control variables cannot
+ * be arrays. If the specified level is illegal, the tightest
+ * possible pads are returned.
+ * @param Level "tightness" level to return pads for
+ * @param EndPad place to put end pad for Level
+ * @param SidePad place to put side pad for Level
+ * @param AnglePad place to put angle pad for Level
+ */
+void GetCPPadsForLevel(int Level,
+ float *EndPad,
+ float *SidePad,
+ float *AnglePad) {
+ switch (Level) {
+ case 0:
+ *EndPad = classify_cp_end_pad_loose * GetPicoFeatureLength ();
+ *SidePad = classify_cp_side_pad_loose * GetPicoFeatureLength ();
+ *AnglePad = classify_cp_angle_pad_loose / 360.0;
+ break;
+
+ case 1:
+ *EndPad = classify_cp_end_pad_medium * GetPicoFeatureLength ();
+ *SidePad = classify_cp_side_pad_medium * GetPicoFeatureLength ();
+ *AnglePad = classify_cp_angle_pad_medium / 360.0;
+ break;
+
+ case 2:
+ *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength ();
+ *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength ();
+ *AnglePad = classify_cp_angle_pad_tight / 360.0;
+ break;
+
+ default:
+ *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength ();
+ *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength ();
+ *AnglePad = classify_cp_angle_pad_tight / 360.0;
+ break;
+ }
+ if (*AnglePad > 0.5)
+ *AnglePad = 0.5;
+
+} /* GetCPPadsForLevel */
+
+/**
+ * @param Evidence evidence value to return color for
+ * @return Color which corresponds to specified Evidence value.
+ * @note Globals: none
+ */
+ScrollView::Color GetMatchColorFor(float Evidence) {
+ assert (Evidence >= 0.0);
+ assert (Evidence <= 1.0);
+
+ if (Evidence >= 0.90)
+ return ScrollView::WHITE;
+ else if (Evidence >= 0.75)
+ return ScrollView::GREEN;
+ else if (Evidence >= 0.50)
+ return ScrollView::RED;
+ else
+ return ScrollView::BLUE;
+} /* GetMatchColorFor */
+
+/**
+ * This routine returns (in Fill) the specification of
+ * the next line to be filled from Filler. FillerDone() should
+ * always be called before GetNextFill() to ensure that we
+ * do not run past the end of the fill table.
+ * @param Filler filler to get next fill spec from
+ * @param Fill place to put spec for next fill
+ */
+void GetNextFill(TABLE_FILLER *Filler, FILL_SPEC *Fill) {
+ FILL_SWITCH *Next;
+
+ /* compute the fill assuming no switches will be encountered */
+ Fill->AngleStart = Filler->AngleStart;
+ Fill->AngleEnd = Filler->AngleEnd;
+ Fill->X = Filler->X;
+ Fill->YStart = Filler->YStart >> 8;
+ Fill->YEnd = Filler->YEnd >> 8;
+
+ /* update the fill info and the filler for ALL switches at this X value */
+ Next = &(Filler->Switch[Filler->NextSwitch]);
+ while (Filler->X >= Next->X) {
+ Fill->X = Filler->X = Next->X;
+ if (Next->Type == StartSwitch) {
+ Fill->YStart = Next->Y;
+ Filler->StartDelta = Next->Delta;
+ Filler->YStart = Next->YInit;
+ }
+ else if (Next->Type == EndSwitch) {
+ Fill->YEnd = Next->Y;
+ Filler->EndDelta = Next->Delta;
+ Filler->YEnd = Next->YInit;
+ }
+ else { /* Type must be LastSwitch */
+ break;
+ }
+ Filler->NextSwitch++;
+ Next = &(Filler->Switch[Filler->NextSwitch]);
+ }
+
+ /* prepare the filler for the next call to this routine */
+ Filler->X++;
+ Filler->YStart += Filler->StartDelta;
+ Filler->YEnd += Filler->EndDelta;
+
+} /* GetNextFill */
+
+/**
+ * This routine computes a data structure (Filler)
+ * which can be used to fill in a rectangle surrounding
+ * the specified Proto. Results are returned in Filler.
+ *
+ * @param EndPad, SidePad, AnglePad padding to add to proto
+ * @param Proto proto to create a filler for
+ * @param Filler place to put table filler
+ */
+void InitTableFiller (float EndPad, float SidePad,
+ float AnglePad, PROTO Proto, TABLE_FILLER * Filler)
+#define XS X_SHIFT
+#define YS Y_SHIFT
+#define AS ANGLE_SHIFT
+#define NB NUM_CP_BUCKETS
+{
+ float Angle;
+ float X, Y, HalfLength;
+ float Cos, Sin;
+ float XAdjust, YAdjust;
+ FPOINT Start, Switch1, Switch2, End;
+ int S1 = 0;
+ int S2 = 1;
+
+ Angle = Proto->Angle;
+ X = Proto->X;
+ Y = Proto->Y;
+ HalfLength = Proto->Length / 2.0;
+
+ Filler->AngleStart = CircBucketFor(Angle - AnglePad, AS, NB);
+ Filler->AngleEnd = CircBucketFor(Angle + AnglePad, AS, NB);
+ Filler->NextSwitch = 0;
+
+ if (fabs (Angle - 0.0) < HV_TOLERANCE || fabs (Angle - 0.5) < HV_TOLERANCE) {
+ /* horizontal proto - handle as special case */
+ Filler->X = Bucket8For(X - HalfLength - EndPad, XS, NB);
+ Filler->YStart = Bucket16For(Y - SidePad, YS, NB * 256);
+ Filler->YEnd = Bucket16For(Y + SidePad, YS, NB * 256);
+ Filler->StartDelta = 0;
+ Filler->EndDelta = 0;
+ Filler->Switch[0].Type = LastSwitch;
+ Filler->Switch[0].X = Bucket8For(X + HalfLength + EndPad, XS, NB);
+ } else if (fabs(Angle - 0.25) < HV_TOLERANCE ||
+ fabs(Angle - 0.75) < HV_TOLERANCE) {
+ /* vertical proto - handle as special case */
+ Filler->X = Bucket8For(X - SidePad, XS, NB);
+ Filler->YStart = Bucket16For(Y - HalfLength - EndPad, YS, NB * 256);
+ Filler->YEnd = Bucket16For(Y + HalfLength + EndPad, YS, NB * 256);
+ Filler->StartDelta = 0;
+ Filler->EndDelta = 0;
+ Filler->Switch[0].Type = LastSwitch;
+ Filler->Switch[0].X = Bucket8For(X + SidePad, XS, NB);
+ } else {
+ /* diagonal proto */
+
+ if ((Angle > 0.0 && Angle < 0.25) || (Angle > 0.5 && Angle < 0.75)) {
+ /* rising diagonal proto */
+ Angle *= 2.0 * M_PI;
+ Cos = fabs(cos(Angle));
+ Sin = fabs(sin(Angle));
+
+ /* compute the positions of the corners of the acceptance region */
+ Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin;
+ Start.y = Y - (HalfLength + EndPad) * Sin + SidePad * Cos;
+ End.x = 2.0 * X - Start.x;
+ End.y = 2.0 * Y - Start.y;
+ Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin;
+ Switch1.y = Y - (HalfLength + EndPad) * Sin - SidePad * Cos;
+ Switch2.x = 2.0 * X - Switch1.x;
+ Switch2.y = 2.0 * Y - Switch1.y;
+
+ if (Switch1.x > Switch2.x) {
+ S1 = 1;
+ S2 = 0;
+ }
+
+ /* translate into bucket positions and deltas */
+ Filler->X = Bucket8For(Start.x, XS, NB);
+ Filler->StartDelta = -static_cast<int16_t>((Cos / Sin) * 256);
+ Filler->EndDelta = static_cast<int16_t>((Sin / Cos) * 256);
+
+ XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
+ YAdjust = XAdjust * Cos / Sin;
+ Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
+ YAdjust = XAdjust * Sin / Cos;
+ Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
+
+ Filler->Switch[S1].Type = StartSwitch;
+ Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
+ Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
+ XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
+ YAdjust = XAdjust * Sin / Cos;
+ Filler->Switch[S1].YInit = Bucket16For(Switch1.y - YAdjust, YS, NB * 256);
+ Filler->Switch[S1].Delta = Filler->EndDelta;
+
+ Filler->Switch[S2].Type = EndSwitch;
+ Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
+ Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
+ XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
+ YAdjust = XAdjust * Cos / Sin;
+ Filler->Switch[S2].YInit = Bucket16For(Switch2.y + YAdjust, YS, NB * 256);
+ Filler->Switch[S2].Delta = Filler->StartDelta;
+
+ Filler->Switch[2].Type = LastSwitch;
+ Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
+ } else {
+ /* falling diagonal proto */
+ Angle *= 2.0 * M_PI;
+ Cos = fabs(cos(Angle));
+ Sin = fabs(sin(Angle));
+
+ /* compute the positions of the corners of the acceptance region */
+ Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin;
+ Start.y = Y + (HalfLength + EndPad) * Sin - SidePad * Cos;
+ End.x = 2.0 * X - Start.x;
+ End.y = 2.0 * Y - Start.y;
+ Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin;
+ Switch1.y = Y + (HalfLength + EndPad) * Sin + SidePad * Cos;
+ Switch2.x = 2.0 * X - Switch1.x;
+ Switch2.y = 2.0 * Y - Switch1.y;
+
+ if (Switch1.x > Switch2.x) {
+ S1 = 1;
+ S2 = 0;
+ }
+
+ /* translate into bucket positions and deltas */
+ Filler->X = Bucket8For(Start.x, XS, NB);
+ Filler->StartDelta = static_cast<int16_t>(ClipToRange<int>(
+ -IntCastRounded((Sin / Cos) * 256), INT16_MIN, INT16_MAX));
+ Filler->EndDelta = static_cast<int16_t>(ClipToRange<int>(
+ IntCastRounded((Cos / Sin) * 256), INT16_MIN, INT16_MAX));
+
+ XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
+ YAdjust = XAdjust * Sin / Cos;
+ Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
+ YAdjust = XAdjust * Cos / Sin;
+ Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
+
+ Filler->Switch[S1].Type = EndSwitch;
+ Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
+ Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
+ XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
+ YAdjust = XAdjust * Sin / Cos;
+ Filler->Switch[S1].YInit = Bucket16For(Switch1.y + YAdjust, YS, NB * 256);
+ Filler->Switch[S1].Delta = Filler->StartDelta;
+
+ Filler->Switch[S2].Type = StartSwitch;
+ Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
+ Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
+ XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
+ YAdjust = XAdjust * Cos / Sin;
+ Filler->Switch[S2].YInit = Bucket16For(Switch2.y - YAdjust, YS, NB * 256);
+ Filler->Switch[S2].Delta = Filler->EndDelta;
+
+ Filler->Switch[2].Type = LastSwitch;
+ Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
+ }
+ }
+} /* InitTableFiller */
+
+
+/*---------------------------------------------------------------------------*/
+#ifndef GRAPHICS_DISABLED
+/**
+ * This routine renders the specified feature into ShapeList.
+ * @param window to add feature rendering to
+ * @param Feature feature to be rendered
+ * @param color color to use for feature rendering
+ * @return New shape list with rendering of Feature added.
+ * @note Globals: none
+ */
+void RenderIntFeature(ScrollView *window, const INT_FEATURE_STRUCT* Feature,
+ ScrollView::Color color) {
+ float X, Y, Dx, Dy, Length;
+
+ window->Pen(color);
+ assert(Feature != nullptr);
+ assert(color != 0);
+
+ X = Feature->X;
+ Y = Feature->Y;
+ Length = GetPicoFeatureLength() * 0.7 * INT_CHAR_NORM_RANGE;
+ // The -PI has no significant effect here, but the value of Theta is computed
+ // using BinaryAnglePlusPi in intfx.cpp.
+ Dx = (Length / 2.0) * cos((Feature->Theta / 256.0) * 2.0 * M_PI - M_PI);
+ Dy = (Length / 2.0) * sin((Feature->Theta / 256.0) * 2.0 * M_PI - M_PI);
+
+ window->SetCursor(X, Y);
+ window->DrawTo(X + Dx, Y + Dy);
+} /* RenderIntFeature */
+
+/**
+ * This routine extracts the parameters of the specified
+ * proto from the class description and adds a rendering of
+ * the proto onto the ShapeList.
+ *
+ * @param window ScrollView instance
+ * @param Class class that proto is contained in
+ * @param ProtoId id of proto to be rendered
+ * @param color color to render proto in
+ *
+ * Globals: none
+ *
+ * @return New shape list with a rendering of one proto added.
+ */
+void RenderIntProto(ScrollView *window,
+ INT_CLASS Class,
+ PROTO_ID ProtoId,
+ ScrollView::Color color) {
+ PROTO_SET ProtoSet;
+ INT_PROTO Proto;
+ int ProtoSetIndex;
+ int ProtoWordIndex;
+ float Length;
+ int Xmin, Xmax, Ymin, Ymax;
+ float X, Y, Dx, Dy;
+ uint32_t ProtoMask;
+ int Bucket;
+
+ assert(ProtoId >= 0);
+ assert(Class != nullptr);
+ assert(ProtoId < Class->NumProtos);
+ assert(color != 0);
+ window->Pen(color);
+
+ ProtoSet = Class->ProtoSets[SetForProto(ProtoId)];
+ ProtoSetIndex = IndexForProto(ProtoId);
+ Proto = &(ProtoSet->Protos[ProtoSetIndex]);
+ Length = (Class->ProtoLengths[ProtoId] *
+ GetPicoFeatureLength() * INT_CHAR_NORM_RANGE);
+ ProtoMask = PPrunerMaskFor(ProtoId);
+ ProtoWordIndex = PPrunerWordIndexFor(ProtoId);
+
+ // find the x and y extent of the proto from the proto pruning table
+ Xmin = Ymin = NUM_PP_BUCKETS;
+ Xmax = Ymax = 0;
+ for (Bucket = 0; Bucket < NUM_PP_BUCKETS; Bucket++) {
+ if (ProtoMask & ProtoSet->ProtoPruner[PRUNER_X][Bucket][ProtoWordIndex]) {
+ UpdateRange(Bucket, &Xmin, &Xmax);
+ }
+
+ if (ProtoMask & ProtoSet->ProtoPruner[PRUNER_Y][Bucket][ProtoWordIndex]) {
+ UpdateRange(Bucket, &Ymin, &Ymax);
+ }
+ }
+ X = (Xmin + Xmax + 1) / 2.0 * PROTO_PRUNER_SCALE;
+ Y = (Ymin + Ymax + 1) / 2.0 * PROTO_PRUNER_SCALE;
+ // The -PI has no significant effect here, but the value of Theta is computed
+ // using BinaryAnglePlusPi in intfx.cpp.
+ Dx = (Length / 2.0) * cos((Proto->Angle / 256.0) * 2.0 * M_PI - M_PI);
+ Dy = (Length / 2.0) * sin((Proto->Angle / 256.0) * 2.0 * M_PI - M_PI);
+
+ window->SetCursor(X - Dx, Y - Dy);
+ window->DrawTo(X + Dx, Y + Dy);
+} /* RenderIntProto */
+#endif
+
+#ifndef GRAPHICS_DISABLED
+/**
+ * Initializes the int matcher window if it is not already
+ * initialized.
+ */
+void InitIntMatchWindowIfReqd() {
+ if (IntMatchWindow == nullptr) {
+ IntMatchWindow = CreateFeatureSpaceWindow("IntMatchWindow", 50, 200);
+ auto* popup_menu = new SVMenuNode();
+
+ popup_menu->AddChild("Debug Adapted classes", IDA_ADAPTIVE,
+ "x", "Class to debug");
+ popup_menu->AddChild("Debug Static classes", IDA_STATIC,
+ "x", "Class to debug");
+ popup_menu->AddChild("Debug Both", IDA_BOTH,
+ "x", "Class to debug");
+ popup_menu->AddChild("Debug Shape Index", IDA_SHAPE_INDEX,
+ "0", "Index to debug");
+ popup_menu->BuildMenu(IntMatchWindow, false);
+ }
+}
+
+/**
+ * Initializes the proto display window if it is not already
+ * initialized.
+ */
+void InitProtoDisplayWindowIfReqd() {
+ if (ProtoDisplayWindow == nullptr) {
+ ProtoDisplayWindow = CreateFeatureSpaceWindow("ProtoDisplayWindow",
+ 550, 200);
+ }
+}
+
+/**
+ * Initializes the feature display window if it is not already
+ * initialized.
+ */
+void InitFeatureDisplayWindowIfReqd() {
+ if (FeatureDisplayWindow == nullptr) {
+ FeatureDisplayWindow = CreateFeatureSpaceWindow("FeatureDisplayWindow",
+ 50, 700);
+ }
+}
+
+/// Creates a window of the appropriate size for displaying elements
+/// in feature space.
+ScrollView* CreateFeatureSpaceWindow(const char* name, int xpos, int ypos) {
+ return new ScrollView(name, xpos, ypos, 520, 520, 260, 260, true);
+}
+#endif // !GRAPHICS_DISABLED
+
+} // namespace tesseract