// Code for Sole shape match function #include #include #include "common.h" #include "cheby.h" #include "cowmath.h" #include "math16.h" #include "volcanop.h" #include "runNet.h" #include "nnet.h" GLYPH * GlyphFromStrokes(UINT cStrokes, STROKE *pStrokes); RECT GetGuideDrawnBox(HWXGUIDE *guide, int iBox); #define ABS(x) (((x) > 0) ? (x) : -(x)) #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MIN(x, y) (((x) < (y)) ? (x) : (y)) #define SIGN(x) ((x) > 0 ? 1 : ((x) < 0 ? -1 : 0)) #define PEN_UP_VALUE LSHFT(-1) #define PEN_DOWN_VALUE LSHFT(1) #define XCHB 10 #define YCHB 10 #define ZCHB 10 #define MAXTMP 3 // assumption: GRIDSIZE <= 256 //This is beacuse the pointers to the mapped values are defined as Byte pointers #define GRIDSIZE 32 typedef struct { int *xy; //Stores the sampled XY points int *z; //Stores the z points--values are either PEN_UP or PEN_DOWN int cPoint;//Stores the number of points that are there int cPointMax; //Stores the max number of points that can be allocated int iStepSize; //Stores the length of the step size--at present this is taken to be 1.5 % of the guide size int iStepSizeSqr; //Stroes the square of the step size } POINTINFO; //This macro is used for seeing if two points are neighbours to one another--ie the distance between them <=1 #define Neighbor(a, b) ((a)-(b) < 2 && (b)-(a) < 2) static int solve2(int m[][(IMAXCHB+1)/2], int c[], int n) { int i, j, k, t, tmp; for (i=0; i=0; --i) { for (k=i-1; k>=0; --k) { t = m[k][i]; for (j=0; j IMAXCHB || cfeats <= 0) return 0; memset(c, 0, cfeats*sizeof(*c)); n2 = n+n; nMin = cfeats + 2; if (n < nMin && n > 4) { cfeats = n - 2; nMin = cfeats + 2; } if (n < nMin) // approximate the stroke by a straight line { *c++ = (y[0] + y[n2-2]) >> 1; *c = (y[n2-2] - y[0]) >> 1; return 2; } memset(T, 0, sizeof(T)); memset(m, 0, sizeof(m)); meanGuess = y[0]; x = LSHFT(-1); dx = LSHFT(2)/(n-1); for (t = 0; t < n2; t += 2) { T[0] = LSHFT(1); T[1] = x; for (i = 2; i < cfeats; ++i) { Mul16(x, T[i-1], tmp) T[i] = (tmp<<1) - T[i-2]; } for (i = 0; i < cfeats; ++i) { for (j = 0; j < cfeats; ++j) { Mul16(T[i], T[j], tmp) m[i][j] += tmp; } Mul16(T[i], y[t] - meanGuess, tmp) c[i] += tmp; //c[i] += T[i]*(y[t] - meanGuess); } x += dx; } if (!solve(m, c, cfeats)) return 0; c[0] += meanGuess; return cfeats; } int ISqrt(int x) { int n, lastN; if (x <= 0) return 0; if (x==1) return 1; n = x >> 1; do { lastN = n; n = (n + x/n) >> 1; } while (n < lastN); return n; } /******************************Public*Routine******************************\ * YDeviation * * Function to compute average deviation of the y values in a sequence of * strokes (frames). * This is not exactly standard deviation. But it is a lot cheaper and * close enough. (See analysis and comments in Numerical Recipes in C). * * History: * 02-Sep-1999 -by- Angshuman Guha aguha * Wrote it. \**************************************************************************/ int YDeviation(GLYPH *pGlyph) { int count, ymin, sum, ymean; GLYPH *glyph; FRAME *frame; if (pGlyph && pGlyph->frame && RgrawxyFRAME(pGlyph->frame)) { ymin = RgrawxyFRAME(pGlyph->frame)->y; count = 0; sum = 0; } else return 1; // find min and mean in one scan for (glyph=pGlyph; glyph; glyph=glyph->next) { XY *xy; int cxy; frame = glyph->frame; xy = RgrawxyFRAME(frame); cxy = CrawxyFRAME(frame); for (; cxy; xy++, cxy--) { int y; y = xy->y; if (y < ymin) { sum += count*(ymin - y); ymin = y; } sum += y - ymin; count++; } } ASSERT(count > 0); ymean = sum/count + ymin; // find average deviation sum = 0; for (glyph=pGlyph; glyph; glyph=glyph->next) { XY *xy; int cxy; frame = glyph->frame; xy = RgrawxyFRAME(frame); cxy = CrawxyFRAME(frame); for (; cxy; xy++, cxy--) { int diff; diff = xy->y - ymean; if (diff < 0) sum -= diff; else sum += diff; } } sum = sum/count; if (sum < 1) sum = 1; return sum; } /******************************Private*Routine******************************\ * AddPoint * * Given a new point and a sequence of points so far, zero or more points * are added at the end of the sequence. The points are effectively resampled * at a pre-computed interval (a distance of pPointinfo->iStepSize between * successive points). This function also effectively does a linear interpolation * of a pen-upstroke between the last point of a pen-down stroke and the first * point of the next pen-down stroke. * * History: * 26-Sep-1997 -by- Angshuman Guha aguha Explanation of parameters pPointInfo--The pointer to the Point Info structure--The points are added to the xy array of this structure x--The x coordinate of the point to be added y--The y coordinate of the point to be added bFirstPointOfStroke--If true indicates that this is the first point of the stroke Else it it not the first point * Wrote this comment.Addtional comments added by Mango \**************************************************************************/ BOOL AddPoint(POINTINFO *pPointinfo, int x, int y, int bFirstPointOfStroke) { int dx, dy, dist2, dist; int bChangeLastPoint, x0, y0, zval; int *pTemp; if (!pPointinfo->cPoint) { pPointinfo->xy[0] = x; pPointinfo->xy[1] = y; pPointinfo->z[0] = PEN_DOWN_VALUE; pPointinfo->cPoint = 1; return TRUE; } bChangeLastPoint = 0; x0 = pPointinfo->xy[2*pPointinfo->cPoint-2]; y0 = pPointinfo->xy[2*pPointinfo->cPoint-1]; zval = bFirstPointOfStroke ? PEN_UP_VALUE : PEN_DOWN_VALUE; for (;;) { dx = x - x0; dy = y - y0; dist2 = dx*dx + dy*dy; if (dist2 < pPointinfo->iStepSizeSqr) break; // add a point at given step size dist = ISqrt(dist2); x0 += pPointinfo->iStepSize*dx/dist; y0 += pPointinfo->iStepSize*dy/dist; // a minimum iStepSize of 2 and the fact that ((float)dx/dist)^2 + ((float)dy/dist)^2 = 1 guarantees that // the previous two assignments change atleast one of x0 and y0 i.e. its not an infinite loop if (pPointinfo->cPoint == pPointinfo->cPointMax) { // need more space // hopefully we don't come here too often pPointinfo->cPointMax *= 2; pTemp = (int *) ExternRealloc(pPointinfo->xy, 2*pPointinfo->cPointMax*sizeof(int)); if (!pTemp) { return FALSE; } pPointinfo->xy = pTemp; pTemp = (int *) ExternRealloc(pPointinfo->z, 2*pPointinfo->cPointMax*sizeof(int)); if (!pTemp) { return FALSE; } pPointinfo->z = pTemp; } pPointinfo->xy[2*pPointinfo->cPoint] = x0; pPointinfo->xy[2*pPointinfo->cPoint+1] = y0; pPointinfo->z[2*pPointinfo->cPoint] = zval; pPointinfo->cPoint++; bChangeLastPoint = bFirstPointOfStroke; } // if we have interpolated from the last point of a stroke to the first point of another if (bChangeLastPoint) { ASSERT(pPointinfo->z[2*pPointinfo->cPoint - 2] == PEN_UP_VALUE); pPointinfo->z[2*pPointinfo->cPoint - 2] = PEN_DOWN_VALUE; } // this last "if" could be changed to a test on bFirstPointOfStroke and the assert can be removed return TRUE; } /******************************Private*Routine******************************\ * AddGuideFeatures * * Given a piece of ink in a box, compute five features related to the size * and position of ink in the box. * The five features are:- * //First feature--Top of the ink relative to the guide box height * //Second feature--Width of the ink relative to the guide box width * //Third feature--Bottom of the ink relative to the guide box height * //Fourth feature--The width of the ink relative to the sum of its width and height * //Fifth feature--the iYMean value relative to the guide box height * * History: * 26-Sep-1997 -by- Angshuman Guha aguha * Wrote this comment. \**************************************************************************/ int AddGuideFeatures(RECT *pGuide, RECT *pRect, int iYMean, unsigned short *rgFeat) { // get normalized ink size/position (box is 1000x1000 with top-left at 0,0) DRECTS drcs; RECT inkRect = *pRect; int x; //The x coordinate of the top left corner of the current box(note--you are adding the cxBase Value) drcs.x = pGuide->left; //The y coordinate of the top left corner of the current box drcs.y = pGuide->top; //This gives us the width of the current guide box drcs.w = pGuide->right - pGuide->left; //This gives us the height of the current guide box drcs.h = pGuide->bottom - pGuide->top; // Translate, convert to delta form //Stores the relative position w.rt. the top left of the guide box inkRect.left -= drcs.x; inkRect.top -= drcs.y; //Stores the width of the ink inkRect.right -= (drcs.x + inkRect.left); //Stores the height of the ink inkRect.bottom -= (drcs.y + inkRect.top); //Converts the yMean wrt a form relative to the top of the guide box iYMean -= drcs.y; // Scale. We do isotropic scaling and center the shorter dimension. //Y Mean as a fraction of the guide box size iYMean = ((1000 * iYMean) / drcs.h); //Sees where the top of the ink is relative to the guide box height drcs.y = ((1000 * inkRect.top) / drcs.h); //The width of the ink relative to the guide box width drcs.w = ((1000 * inkRect.right) / drcs.h); //The height of the ink relative to the guide box height drcs.h = ((1000 * inkRect.bottom) / drcs.h); //Why would any of these conditions happen if (drcs.y < 0) drcs.y = 0; else if (drcs.y > 1000) drcs.y = 1000; if (drcs.w < 0) drcs.w = 0; else if (drcs.w > 1000) drcs.w = 1000; if (drcs.h < 0) drcs.h = 0; else if (drcs.h > 1000) drcs.h = 1000; // 4 guide features //First feature--Top of the ink relative to the guide box height x = drcs.y; x = LSHFT(x)/1000; if (x >= 0x10000) x = 0xFFFF; *rgFeat++ = (unsigned short)x; //Second feature--Width of the ink relative to the guide box width x = drcs.w; x = LSHFT(x)/1000; if (x >= 0x10000) x = 0xFFFF; *rgFeat++ = (unsigned short)x; //Third feature--Bottom of the ink relative to the guide box height x = drcs.h; x = LSHFT(x)/1000; if (x >= 0x10000) x = 0xFFFF; *rgFeat++ = (unsigned short)x; //Fourth feature--The width of the ink relative to the sum of its width and height if (drcs.w <= 0) x = 0; else { x = drcs.w; x = LSHFT(x)/(drcs.w+drcs.h); if (x >= 0x10000) x = 0xFFFF; } *rgFeat++ = (unsigned short)x; //Fifth feature--the iYMean value relative to the guide box height // one more guide feature: y-CG if (iYMean < 0) iYMean = 0; else if (iYMean > 1000) iYMean = 1000; x = iYMean; x = LSHFT(x)/1000; if (x >= 0x10000) x = 0xFFFF; *rgFeat = (unsigned short)x; return 5; } /******************************Private*Routine******************************\ * SmoothPoints * * Given an array of points and a destination array, this function fills the * destination array a smoothed version of the raw points. Smoothing is * done by local averaging ona window of 5 points with weights 1/8 1/4 1/4 1/4 1/8. * * History: * 26-Sep-1997 -by- Angshuman Guha aguha * Wrote this comment. \**************************************************************************/ void SmoothPoints(XY *rgSrc, XY *rgDst, int cXY) { int i,j; for (i=0; ix = (int)(( (rgSrc-2)->x + ((rgSrc-1)->x <<1) + (rgSrc->x <<1) + ((rgSrc+1)->x <<1) + (rgSrc+2)->x + 4 ) >> 3); rgDst->y = (int)(( (rgSrc-2)->y + ((rgSrc-1)->y <<1) + (rgSrc->y <<1) + ((rgSrc+1)->y <<1) + (rgSrc+2)->y + 4 ) >> 3); break; } rgSrc++; rgDst++; } } /******************************Private*Routine******************************\ * ComputeCurvedness * * Given a stroke, computes three curvature features--namely *--The sum of the modular change in angle with respect to + and - for the angles *--The total curviness of the stoke--just directly measure the change in angles. *--The maximum change in angle that occurs in that stroke in one sampling distance * * History: * 26-Sep-1997 -by- Angshuman Guha aguha * Wrote this comment. \**************************************************************************/ void ComputeCurvedness(XY *rgXY, int cXY, int iStepSizeSqr, int *pSum1, int *pSum2, int *pMaxAngle) { int sum1, sum2; int x, y; XY *rgxy, *rgxySave; int ang, lastAng, diff, dx, dy; if (cXY <= 2) { *pSum1 = *pSum2 = 0; return; } // smooth points rgxySave = rgxy = (XY *) ExternAlloc(cXY*sizeof(XY)); if (!rgxy) { *pSum1 = *pSum2 = 0; return; } SmoothPoints(rgXY, rgxy, cXY); sum1 = sum2 = 0; x = rgxy->x; y = rgxy->y; rgxy++; cXY--; // find first angle while (cXY) { dy = rgxy->y - y; dx = rgxy->x - x; if (dx*dx+dy*dy >= iStepSizeSqr) { //Function from common/mathx--returns the integer approx in degrees lastAng = Arctan2(dy, dx); cXY--; x = rgxy->x; y = rgxy->y; rgxy++; break; } cXY--; rgxy++; } // now find difference of every subsequent angle with its previous angle while (cXY) { dy = rgxy->y - y; dx = rgxy->x - x; if (dx*dx+dy*dy >= iStepSizeSqr) { ang = Arctan2(dy, dx); ANGLEDIFF(lastAng, ang, diff) sum1 += diff; if (diff < 0) diff = -diff; sum2 += diff; lastAng = ang; x = rgxy->x; y = rgxy->y; if (diff > *pMaxAngle) *pMaxAngle = diff; } cXY--; rgxy++; } // clean up ExternFree(rgxySave); *pSum1 = sum1; *pSum2 = sum2; } /******************************Private*Routine******************************\ * AddCurveFeatures * * Given an ink (one or more strokes), computes three curvature features. * * History: * 26-Sep-1997 -by- Angshuman Guha aguha * Wrote this comment. \**************************************************************************/ int AddCurveFeatures(GLYPH *pGlyph, int iStepSizeSqr, unsigned short *rgFeat) { GLYPH *glyph; FRAME *frame; int cXY; XY *rgXY; int sum1=0, sum2=0, ang1, ang2, maxAngle=0; for (glyph=pGlyph; glyph; glyph=glyph->next) { frame = glyph->frame; if (!IsVisibleFRAME(frame)) continue; rgXY = RgrawxyFRAME(frame); cXY = CrawxyFRAME(frame); ASSERT(cXY > 0); //For each frame compute the curvedness ComputeCurvedness(rgXY, cXY, iStepSizeSqr, &ang1, &ang2, &maxAngle); //sum1 represents the sum of the modular change in angle(with respect to + and - for the angles sum1 += ang1; //sum2 represent the total curviness of the stoke--just directly measures the change in angles. sum2 += ang2; } // based on emperical obsevations, truncate sum1 between -1000 to 1000 // and sum2 between 0 and 1200 // (this results in no truncation in more than 99% cases) if (sum1 < -1000) sum1 = -1000; else if (sum1 > 1000) sum1 = 1000; if (sum2 < 0) sum2 = 0; else if (sum2 > 1200) sum2 = 1200; sum1 += 1000; // now between 0 and 2000 sum1 = LSHFT(sum1)/2000; if (sum1 > 0xFFFF) sum1 = 0xFFFF; sum2 = LSHFT(sum2)/1200; if (sum2 > 0xFFFF) sum2 = 0xFFFF; // maxAngle should be between 0 and 180 if (maxAngle < 0) maxAngle = 0; else if (maxAngle > 180) maxAngle = 180; maxAngle = LSHFT(maxAngle)/180; if (maxAngle > 0xFFFF) maxAngle = 0xFFFF; *rgFeat++ = (unsigned short) sum1; *rgFeat++ = (unsigned short) sum2; *rgFeat = (unsigned short) maxAngle; return 3; } /******************************Private*Routine******************************\ * AddStrokeCountFeature * * Defines a single feature derived from stroke count of a char. * * History: * 26-Sep-1997 -by- Angshuman Guha aguha * Wrote this comment. \**************************************************************************/ int AddStrokeCountFeature(int cStroke, unsigned short *rgFeat) { int tmp = LSHFT(cStroke-1)/cStroke; *rgFeat++ = (unsigned short)tmp; return 1; } /******************************Private*Routine******************************\ * DoOneContour * * Once a contour has been found (defined by a sequence of values, X-values * for left- or right-contour, Y-values for top- or bottom-contour), this function * is called to fit a Chebychev polynomial to the contour generating 9 new * features. * * The arg "contour" is of length GRIDSIZE. The output features are filled in * the arg rgFeat and the count of features generated is returned. * * History: * 26-Sep-1997 -by- Angshuman Guha aguha * Wrote it. \**************************************************************************/ int DoOneContour(int *contour, unsigned short *rgFeat) { int rgX[2*GRIDSIZE], *pX; //The rgX array is of 2*GRIDSIZE because the Chebyshev function takes alternate array values int chby[10]; int norm = 0; int dT, i; // copy points into required format pX = rgX; for (i=0; i>= 1; if (dT >= 0x10000) dT = 0xFFFF; //Fill it with 1's for the lower 16 bits else if (dT < 0) dT = 0; //Does converting to an unsigned short always take the lowest 16 bits ?? *rgFeat++ = (unsigned short)dT; } return 10; } /******************************Private*Routine******************************\ * MakeLine * * Given a point in the GRIDSIZE x GRIDSIZE grid (bitmap) and given the * sequence of points so far, this function adds one or more points in a * straight line joining the last point and the given point. * * Returns the number of points added. * * History: * 26-Sep-1997 -by- Angshuman Guha aguha * Wrote it. * 06-Oct-1997 -by- Angshuman Guha aguha * Fixed a bug. \**************************************************************************/ int MakeLine(BYTE x, BYTE y, BYTE *pX, BYTE *pY, int space) { BYTE midx, midy; int ts1,ts2; int c, c2; if (space <= 0) { return 0; } ASSERT(x != *pX || y != *pY); if (Neighbor(*pX, x) && Neighbor(*pY, y)) { ASSERT(x >= 0); ASSERT(x < GRIDSIZE); ASSERT(y >= 0); ASSERT(y < GRIDSIZE); *++pX = x; *++pY = y; return 1; } ts1=(*pX+x)/2; ts2=(*pY+y)/2; midx = (BYTE) ts1; midy = (BYTE) ts2; c = MakeLine(midx, midy, pX, pY, space); if (!c) return 0; c2 = MakeLine(x, y, pX+c, pY+c, space-c); if (!c2) return 0; return c + c2; } /******************************Private*Routine******************************\ * AddContourFeatures * * Given a sequence of already-resampled points (pen-down strokes joined by * intervening pen-up strokes) and the bounding rect for the ink, this function * computes some contour features, fills up rgFeat with the features and * returns the count of features computed. * * History: * 26-Sep-1997 -by- Angshuman Guha aguha * Wrote it. * 06-Oct-1997 -by- Angshuman Guha aguha * Fixed a bug. Added Realloc for rgMappedX and rgMappedY. Comments added by Mango The idea of this function is to first map the points that we have to 2 GRIDSIZE*GRIDSIZE bitmap. Once the bitmap is made we use the contour features. Explanation of the arguments to the function pointinfo--Contains the strructure from which the points will be taken pRect--pointer to the bounding rectangle of the original ink rgFeat--This contains the finalk contour features \**************************************************************************/ int AddContourFeatures(POINTINFO *pointinfo, RECT *pRect, unsigned short *rgFeat) { //BYTE rgGrid[GRIDSIZE][GRIDSIZE]; int xMin = pRect->left; //Stores the minimum value of x int yMin = pRect->top; //Stores the minimum value of y int xRange = pRect->right - xMin; //Stores the width of the bounding rectangle int yRange = pRect->bottom - yMin; //Stores the height of the bounding rectangle int range, xOrigin, yOrigin;//Range stores the greated of the xRange and yRange int iPoint, *xy, *z, i, cMapped, cMappedMax; //cMapped stores the number of points that have been mapped. cMappedMAx--the max number of points that could be mapped BYTE *pMappedX, *pMappedY, *rgMappedX, *rgMappedY;//Since we are using BYTE * here,we are assuming that GRIDSIZE <256--a byte size int rgMaxX[GRIDSIZE], rgMinY[GRIDSIZE], rgMaxY[GRIDSIZE], lastz; //GRIDSIZE=32 int lastx, lasty; int iRetValue; BYTE *pbTmpX = NULL, *pbTmpY = NULL; ASSERT(xRange > 0); ASSERT(yRange > 0); //We want to map the points to a GRIDSIZE*GRIDSIZE bitmap--but at the same time we want to preserve the aspect ratio //It xRange >yRange,then the xValues will span the whole of the bit map //The y values will not span the entire bit map--but will simply be centered on the bit map.This helps to preserve the aspect ratio if (xRange > yRange) { range = xRange; xOrigin = 0; yOrigin = (GRIDSIZE - GRIDSIZE*(yRange-1)/range) / 2; } else if (yRange > 1) { //In this case the y will span the entire grid map.xOrigin is scaled according to the earlier comment range = yRange; xOrigin = (GRIDSIZE - GRIDSIZE*(xRange-1)/range) / 2; yOrigin = 0; } else // xRange == yRange == 1 { //In this case the Ink will be centered range = 1; xOrigin = GRIDSIZE/2; yOrigin = GRIDSIZE/2; } // make list of grid points which will make up the binary pixel map--we will map only the pen down points //Initialize the pointers to the raw points xy = pointinfo->xy; z = pointinfo->z; //In case there is a continuation of a line,then the bit map should not have any vacant spaces in the grid //connecting the points of the line.Hence we add extra space by 2*GRIDSIZE in case we need to connect the points //cMappedMax represents the maximum number of points that will be mapped cMappedMax = 2*(pointinfo->cPoint+GRIDSIZE); rgMappedX = (BYTE *) ExternAlloc(cMappedMax*sizeof(BYTE)); rgMappedY = (BYTE *) ExternAlloc(cMappedMax*sizeof(BYTE)); if (!rgMappedX || !rgMappedY) { //Allocatation failed.FLEE !! ASSERT(0); iRetValue = 0; goto cleanup; } cMapped = 0; //This contains the total number of points that have been mapped //The pointer to which it has been initialized is one less than the actual start. //Hence need to use an increment operator prior to using the first time. //After that the pointer always points to the last value that was allocated pMappedX = rgMappedX - 1; pMappedY = rgMappedY - 1; lastz = PEN_UP_VALUE; for (iPoint=0; iPointcPoint; iPoint++) { int xRaw, yRaw, x, y, zRaw; // get point xRaw = *xy++; yRaw = *xy++; zRaw = *z++; z++; if (zRaw > 0) // pen-down point { // map x to grid x = xOrigin + GRIDSIZE*(xRaw - xMin)/range; ASSERT(x >= 0); ASSERT(x < GRIDSIZE); // map y to grid y = yOrigin + GRIDSIZE*(yRaw - yMin)/range; ASSERT(y >= 0); ASSERT(y < GRIDSIZE); // save this point in the list if (lastz < 0) { if (cMapped==cMappedMax) { //If the max space has already been used up then we ned to realloc. cMappedMax *= 2; pbTmpX = ExternRealloc(rgMappedX, cMappedMax*sizeof(BYTE)); if (!pbTmpX) { ASSERT(0); iRetValue = 0; goto cleanup; } rgMappedX = pbTmpX; pMappedX = rgMappedX - 1 + cMapped; pbTmpY = ExternRealloc(rgMappedY, cMappedMax*sizeof(BYTE)); if (!pbTmpY) { ASSERT(0); iRetValue = 0; goto cleanup; } rgMappedY = pbTmpY; pMappedY = rgMappedY - 1 + cMapped; } // first point of a stroke *++pMappedX = (BYTE)x; *++pMappedY = (BYTE)y; cMapped++; } else if (x != *pMappedX || y != *pMappedY) { // next unique mapped point //i contains the count of the number of points that have been added thru MakeLine ASSERT(*pMappedX < GRIDSIZE); ASSERT(*pMappedY =0); ASSERT(*pMappedY >=0); while (!(i = MakeLine((BYTE)x, (BYTE)y, pMappedX, pMappedY, cMappedMax - cMapped))) { // these reallocs happen on 0.4% of the samples of gtrain02.ste (1860 of 443292 samples) cMappedMax *= 2; pbTmpX = ExternRealloc(rgMappedX, cMappedMax*sizeof(BYTE)); if (!pbTmpX) { ASSERT(0); iRetValue = 0; goto cleanup; } rgMappedX = pbTmpX; pMappedX = rgMappedX - 1 + cMapped; pbTmpY = ExternRealloc(rgMappedY, cMappedMax*sizeof(BYTE)); if (!pbTmpY) { ASSERT(0); iRetValue = 0; goto cleanup; } rgMappedY = pbTmpY; pMappedY = rgMappedY - 1 + cMapped; } //Increment the count of mapped points by the number of points that have been added. cMapped += i; ASSERT(cMapped <=cMappedMax); //Increment the pointers accordingly pMappedX += i; pMappedY += i; } } // if zRaw > 0 lastz = zRaw; } // for iPoint=0 // now dump the mapped point list into the grid, deleting redundant points along the way //memset(rgGrid, 0, sizeof(rgGrid)); for (i=0; i 0 && iPoint < cMapped-1) { int nextx = *pMappedX; int nexty = *pMappedY; if (Neighbor(lastx, nextx) && Neighbor(lasty, nexty) && lastx != nextx && lasty != nexty) continue; } lastx = x; lasty = y; //rgGrid[x][y]=1; ASSERT(x >= 0); ASSERT(x < GRIDSIZE); ASSERT(y >= 0); ASSERT(y < GRIDSIZE); if (x > rgMaxX[y]) rgMaxX[y] = x; if (y < rgMinY[x]) rgMinY[x] = y; if (y > rgMaxY[x]) rgMaxY[x] = y; } // now Chebychev'ize the three contours // right contour rgFeat += DoOneContour(rgMaxX, rgFeat); // top contour rgFeat += DoOneContour(rgMinY, rgFeat); // bottom contour rgFeat += DoOneContour(rgMaxY, rgFeat); iRetValue = 30; cleanup: // Clean up temp space if (rgMappedX) ExternFree(rgMappedX); if (rgMappedY) ExternFree(rgMappedY); return iRetValue; } /******************************Private*Routine******************************\ * NormalizeCheby * * Routine to normalize the three (x, y and z) Chebychev polynomials. * * History: * 26-Sep-1997 -by- Angshuman Guha aguha * Wrote this comment. \**************************************************************************/ void NormalizeCheby(int *chbyX, int *chbyY, int *chbyZ, unsigned short *rgFeat) { int norm = 0; int dT; int cFeat = 0, i; //The norm is applied both to x and y prior to dividing,so that the relative sizes of x and y can be kept intact // for (i = 1; i < XCHB; ++i) // 1st X coeff skipped { Mul16(chbyX[i], chbyX[i], dT) norm += dT; } for (i = 1; i < YCHB; ++i) // 1st Y coeff skipped { Mul16(chbyY[i], chbyY[i], dT) norm += dT; } norm = ISqrt(norm) << 8; if (norm < LSHFT(1)) norm = LSHFT(1); for (i=1; i>= 1; // now between 0 and 1 if (dT >= 0x10000) dT = 0xFFFF; else if (dT < 0) dT = 0; rgFeat[cFeat++] = (unsigned short)dT; } for (i=1; i>= 1; if (dT >= 0x10000) dT = 0xFFFF; else if (dT < 0) dT = 0; rgFeat[cFeat++] = (unsigned short)dT; } // Z norm = 0; for (i = 0; i < 8; ++i) { Mul16(chbyZ[i], chbyZ[i], dT) norm += dT; } norm = ISqrt(norm) << 8; if (norm < LSHFT(1)) norm = LSHFT(1); for (i = 0; i < 8; i++) { dT = Div16(chbyZ[i], norm) + LSHFT(1); dT >>= 1; if (dT >= 0x10000) dT = 0xFFFF; else if (dT < 0) dT = 0; rgFeat[cFeat++] = (unsigned short)dT; } ASSERT(cFeat == 26); } /******************************Public*Routine******************************\ * SoleFeaturize * * The top-level routine for featurizing ink for a char. * * History: * 26-Sep-1997 -by- Angshuman Guha aguha * Modified it. \**************************************************************************/ BOOL SoleFeaturize(GLYPH *pGlyph, RECT *pGuide, unsigned short *rgFeat) { int cStroke, iStroke; int iPoint; int sumX, sumY, sum; double totvar; int var; int isumX, isumY;//These store the mean values of x and y int chbyX[IMAXCHB], chbyY[IMAXCHB], chbyZ[IMAXCHB]; int retVal = 1; XY *rgXY, lastXY; int cXY, iXY, dx, dy, t; GLYPH *glyph; FRAME *frame; int ydev; //Stores the ydev value used for storing the step size POINTINFO pointinfo; RECT rect; // compute cStroke for (cStroke=0, glyph=pGlyph; glyph; glyph=glyph->next) { frame = glyph->frame; if (!IsVisibleFRAME(frame)) continue; cXY = CrawxyFRAME(frame); ASSERT(cXY > 0); cStroke++; } if (cStroke < 1) return 0; // compute step size--originally this was done using the box height // pointinfo.iStepSize = pGuide->cyBox*3/200; // 1.5% of box height ydev= YDeviation(pGlyph); if (ydev < 1) ydev = 1; // a "-" or a "." //The step size is computed from the ydev value pointinfo.iStepSize = ydev/5; if (pointinfo.iStepSize < 2) pointinfo.iStepSize = 2; pointinfo.iStepSizeSqr = pointinfo.iStepSize * pointinfo.iStepSize; // estimate total count of points pointinfo.cPointMax = 1; // make sure it does not end up being zero for (iStroke=0, glyph=pGlyph; glyph; glyph=glyph->next) { frame = glyph->frame; if (!IsVisibleFRAME(frame)) continue; rgXY = RgrawxyFRAME(frame); cXY = CrawxyFRAME(frame); ASSERT(cXY > 0); sum = 0; for (iXY=1; iXY dy) sum += dx; else sum += dy; } //The sum that we are computing here is an underestimate--we are only taking the max on |x| or |y|.The distance will // be more pointinfo.cPointMax += sum/pointinfo.iStepSize; // if not first stroke simulate pen-up stroke if (iStroke) { dx = lastXY.x - rgXY->x; dy = lastXY.y - rgXY->y; t = ISqrt(dx*dx + dy*dy)/pointinfo.iStepSize; if (t >= 2) pointinfo.cPointMax += t-1; } lastXY = rgXY[cXY-1]; iStroke++; } //Since we have computed an underestimate multiply by two pointinfo.cPointMax *= 2; // allocate space pointinfo.xy = (int *) ExternAlloc(2*pointinfo.cPointMax*sizeof(int)); if (!pointinfo.xy) return 0; //The array size for z is double of what actually needs to be allocated because- //--the chebyshev function expects the array to be spaced in this manner //--indexing is easier in the functions--can exactly mirror what you do for the x and y pointinfo.z = (int *) ExternAlloc(2*pointinfo.cPointMax*sizeof(int)); if (!pointinfo.z) { ExternFree(pointinfo.xy); return 0; } // join all strokes into one stream pointinfo.cPoint = 0; for (glyph=pGlyph; glyph; glyph=glyph->next) { frame = glyph->frame; if (!IsVisibleFRAME(frame)) continue; rgXY = RgrawxyFRAME(frame); cXY = CrawxyFRAME(frame); for (iXY = 0; iXY < cXY; iXY++) if (!AddPoint(&pointinfo, rgXY[iXY].x, rgXY[iXY].y, !iXY)) { retVal = 0; goto freeReturn; } } // contour features (computed from resampled raw points) GetRectGLYPH(pGlyph, &rect); //PLEASE NOTE--rgFeat is unsigned short.Its value comes from the lower 16 bits of the 16.16 format that had been defined earlier. rgFeat += AddContourFeatures(&pointinfo, &rect, rgFeat); //IS there any reason why the mean and the variance has been subtracted only AFTER the contour features ??Maybe because there we are dumping to a bit map ?? // compute X-mean and Y-mean sumX = sumY = 0; for (iPoint=0; iPoint<2*pointinfo.cPoint; iPoint+=2) { sumX += pointinfo.xy[iPoint] - rect.left; sumY += pointinfo.xy[iPoint+1] - rect.top; } //isumX and isumY represent the mean values of the x and y coordinates. isumX = (sumX / pointinfo.cPoint) + rect.left; isumY = (sumY / pointinfo.cPoint) + rect.top; // shift points by means for (iPoint=0; iPoint<2*pointinfo.cPoint; iPoint+=2) { pointinfo.xy[iPoint] -= isumX; pointinfo.xy[iPoint+1] -= isumY; } // compute variance totvar = 0; for (iPoint=0; iPoint<2*pointinfo.cPoint; iPoint++) { totvar += (double) pointinfo.xy[iPoint] * (double) pointinfo.xy[iPoint]; } var = (int) sqrt(totvar/pointinfo.cPoint); if (var < 1) var = 1; // scale points by standard deviation // From this point on,the pointinfo values are in 16.16 //IMPORTTANT NOTE---THE pointinfo array is not directly used after this point //If it is,you will have to use 16.16 arithmetic for (iPoint=0; iPoint<2*pointinfo.cPoint; iPoint++) { pointinfo.xy[iPoint] = LSHFT(pointinfo.xy[iPoint])/var; } //Basically,since we effectively have a normal distribution(hopefully)most of the values will be between +-3. // chebychev'ize! if (!LSCheby(pointinfo.xy, pointinfo.cPoint, chbyX, XCHB)) { retVal = 0; goto freeReturn; } if (!LSCheby(pointinfo.xy+1, pointinfo.cPoint, chbyY, YCHB)) { retVal = 0; goto freeReturn; } if (!LSCheby(pointinfo.z, pointinfo.cPoint, chbyZ, ZCHB)) { retVal = 0; goto freeReturn; } NormalizeCheby(chbyX, chbyY, chbyZ, rgFeat); rgFeat += 26; // stroke count feature--1 feature is added rgFeat += AddStrokeCountFeature(cStroke, rgFeat); // guide features //The rect had been comptured prior to scaling the points --by mean and standard deviation //isumY represents the mean Y value //We add 5 guide features rgFeat += AddGuideFeatures(pGuide, &rect, isumY, rgFeat); // curved-ness features //Note the original glyph is being passed here //Why not simply use the sampled points ?? rgFeat += AddCurveFeatures(pGlyph, pointinfo.iStepSizeSqr, rgFeat); freeReturn: ExternFree(pointinfo.xy); ExternFree(pointinfo.z); return retVal; } /* void DumpFeatures (unsigned short *pFeat) { static int c = 0; static FILE *fp = NULL; int i, cMap; if (!fp) { fp = fopen ("feat.dmp", "wt"); if (!fp) return; } cMap = sizeof (s_aaMap) / sizeof (s_aaMap[0]); for (i = 0; i < cMap; i++) { if (s_aaMap[i] == s_wch) break; } if (i == cMap) return; fprintf (fp, "%d\t", i + 1); for (i = 0; i < 65; i++) { fprintf (fp, "%d%c", pFeat[i], i == 64 ? '\n' : ' '); } fflush (fp); c++; } */ #pragma optimize("",off) int SoleNN(SOLE_LOAD_INFO *pSole, int cStrk, unsigned short *pFeat, ALT_LIST *pAlt) { RREAL *pNetMem, *pNetOut; int iWinner, cOut; int i, j, k; int *pIndex; wchar_t *pwchMap; pAlt->cAlt = -1; if (cStrk == 1) { pNetMem = ExternAlloc(pSole->iNet1Size * sizeof (*pNetMem)); if (!pNetMem) return -1; for (i = 0; i < SOLE_NUM_FEATURES; i++) { pNetMem[i] = pFeat[i]; } pNetOut = runLocalConnectNet(&pSole->net1, pNetMem, &iWinner, &cOut); pwchMap = pSole->pMap1; } else { pNetMem = ExternAlloc(pSole->iNet2Size * sizeof (*pNetMem)); if (!pNetMem) return -1; for (i = 0; i < SOLE_NUM_FEATURES; i++) { pNetMem[i] = pFeat[i]; } pNetOut = runLocalConnectNet(&pSole->net2, pNetMem, &iWinner, &cOut); pwchMap = pSole->pMap2; } pIndex = ExternAlloc(sizeof(int) * cOut); if (pIndex == NULL) { ExternFree(pNetMem); return -1; } for (i = 0; i < cOut; i++) { pIndex[i] = i; } for (i = 0; i < MAX_ALT_LIST; i++) { for (j = i + 1; j < cOut; j++) { if (pNetOut[pIndex[i]] < pNetOut[pIndex[j]]) { k = pIndex[i]; pIndex[i] = pIndex[j]; pIndex[j] = k; } } if (pNetOut[pIndex[i]] == 0) { break; } pAlt->awchList[i] = pwchMap[pIndex[i]]; pAlt->aeScore[i] = (float) pNetOut[pIndex[i]] / (float) SOFT_MAX_UNITY; } //DumpFeatures (pFeat); // If the following line is compiled with optimization turned on, // the VS6 compiler goes into an infinite loop. pAlt->cAlt = i; ExternFree(pIndex); ExternFree(pNetMem); return pAlt->cAlt; } #pragma optimize("",on) int SoleMatch(SOLE_LOAD_INFO *pSole, ALT_LIST *pAlt, int cAlt, GLYPH *pGlyph, RECT *pGuide, CHARSET *pCS, LOCRUN_INFO * pLocRunInfo) { int cStrk; unsigned short aiSoleFeat[SOLE_NUM_FEATURES]; cStrk = CframeGLYPH (pGlyph); if (SoleFeaturize(pGlyph, pGuide, aiSoleFeat) != 1) { return -1; } return SoleNN (pSole, cStrk, aiSoleFeat, pAlt); } static void AddChar(wchar_t *pwchTop1, float *pflTop1, ALT_LIST *pAltList, wchar_t dch, float flProb, LOCRUN_INFO *pLocRunInfo, CHARSET *pCS) { int j; for (j = 0; j < (int) pAltList->cAlt; j++) { if (pAltList->awchList[j] == dch) { pAltList->aeScore[j] = flProb; } } if (flProb > 0) { // Check whether the character (or folding set) passes the filter, // if so see if it is the new top 1. if (IsAllowedChar(pLocRunInfo, pCS, dch)) { if (*pwchTop1 == 0xFFFE || flProb > *pflTop1) { *pflTop1 = flProb; *pwchTop1 = dch; } } } } int SoleMatchRescore(SOLE_LOAD_INFO *pSole, wchar_t *pwchTop1, float *pflTop1, ALT_LIST *pAltList, int cAlt, GLYPH *pGlyph, RECT *pGuide, CHARSET *pCharSet, LOCRUN_INFO *pLocRunInfo) { int i; int cStrk; RREAL *pNetMem, *pNetOut; int iWinner, cOut; int j; wchar_t *pwchMap; unsigned short aiSoleFeat[SOLE_NUM_FEATURES]; // First set all the scores to zero. This is because some code points Fugu // supports may not be supported by Sole. This implicitly says Sole gives // them a score of zero. for (j = 0; j < (int) pAltList->cAlt; j++) { pAltList->aeScore[j] = 0; } *pflTop1 = 0; *pwchTop1 = 0xFFFE; cStrk = CframeGLYPH (pGlyph); if (SoleFeaturize (pGlyph, pGuide, aiSoleFeat) != 1) { return pAltList->cAlt; } if (cStrk == 1) { pNetMem = ExternAlloc(pSole->iNet1Size * sizeof (*pNetMem)); if (!pNetMem) return -1; for (i = 0; i < SOLE_NUM_FEATURES; i++) { pNetMem[i] = aiSoleFeat[i]; } pNetOut = runLocalConnectNet(&pSole->net1, pNetMem, &iWinner, &cOut); pwchMap = pSole->pMap1; } else { pNetMem = ExternAlloc(pSole->iNet2Size * sizeof (*pNetMem)); if (!pNetMem) return -1; for (i = 0; i < SOLE_NUM_FEATURES; i++) { pNetMem[i] = aiSoleFeat[i]; } pNetOut = runLocalConnectNet(&pSole->net2, pNetMem, &iWinner, &cOut); pwchMap = pSole->pMap2; } // This is the version for Fugu trained on dense codes, which will usually be // what we use. Loops over the outputs for (i = 0; i < cOut; i++) { wchar_t fdch = pwchMap[i]; float flProb = (float) pNetOut[i] / (float) SOFT_MAX_UNITY; #if 1 AddChar(pwchTop1, pflTop1, pAltList, fdch, flProb, pLocRunInfo, pCharSet); #else if (LocRunIsFoldedCode(pLocRunInfo, fdch)) { // If it is a folded code, look up the folding set wchar_t *pFoldingSet = LocRunFolded2FoldingSet(pLocRunInfo, fdch); // Run through the folding set, adding non-NUL items to the output list // (until the output list is full) for (j = 0; j < LOCRUN_FOLD_MAX_ALTERNATES && pFoldingSet[j] != 0; j++) { AddChar(pwchTop1, pflTop1, pAltList, pFoldingSet[j], flProb, pLocRunInfo, pCharSet); } } else { AddChar(pwchTop1, pflTop1, pAltList, fdch, flProb, pLocRunInfo, pCharSet); } #endif } ExternFree(pNetMem); return pAltList->cAlt; } BOOL SoleLoadPointer(SOLE_LOAD_INFO *pSole, LOCRUN_INFO *pLocRunInfo) { NNET_HEADER *pHeader; NNET_SPACE_HEADER *apSpcHeader[2]; pHeader = (NNET_HEADER *) pSole->info.pbMapping; // check version and signature ASSERT (pHeader->dwFileType == SOLE_FILE_TYPE); ASSERT (pHeader->iFileVer >= SOLE_OLD_FILE_VERSION); ASSERT (pHeader->iMinCodeVer <= SOLE_CUR_FILE_VERSION); ASSERT ( !memcmp ( pHeader->adwSignature, g_locRunInfo.adwSignature, sizeof (pHeader->adwSignature) ) ); if (pHeader->dwFileType != SOLE_FILE_TYPE || pHeader->adwSignature[0] != pLocRunInfo->adwSignature[0] || pHeader->adwSignature[1] != pLocRunInfo->adwSignature[1] || pHeader->adwSignature[2] != pLocRunInfo->adwSignature[2] || pHeader->iFileVer < SOLE_OLD_FILE_VERSION || pHeader->iMinCodeVer > SOLE_CUR_FILE_VERSION) { return FALSE; } // # of spaces has to be two ASSERT (pHeader->cSpace == 2); apSpcHeader[0] = (NNET_SPACE_HEADER *) (pSole->info.pbMapping + sizeof (NNET_HEADER)); apSpcHeader[1] = (NNET_SPACE_HEADER *) ( pSole->info.pbMapping + sizeof (NNET_HEADER) + sizeof (NNET_SPACE_HEADER)); if (restoreLocalConnectNet ( pSole->info.pbMapping + apSpcHeader[0]->iDataOffset, 0, &pSole->net1 ) == NULL ) { return FALSE; } if (restoreLocalConnectNet ( pSole->info.pbMapping + apSpcHeader[1]->iDataOffset, 0, &pSole->net2 ) == NULL ) { return FALSE; } pSole->iNet1Size = getRunTimeNetMemoryRequirements(pSole->info.pbMapping + apSpcHeader[0]->iDataOffset); pSole->iNet2Size = getRunTimeNetMemoryRequirements(pSole->info.pbMapping + apSpcHeader[1]->iDataOffset); pSole->pMap1 = (wchar_t *) (pSole->info.pbMapping + apSpcHeader[0]->iMapOffset); pSole->pMap2 = (wchar_t *) (pSole->info.pbMapping + apSpcHeader[1]->iMapOffset); return TRUE; } BOOL SoleLoadRes(SOLE_LOAD_INFO *pSole, HINSTANCE hInst, int nResID, int nType, LOCRUN_INFO *pLocRunInfo) { if (DoLoadResource(&pSole->info, hInst, nResID, nType) == NULL) { return FALSE; } return SoleLoadPointer(pSole, pLocRunInfo); } BOOL SoleLoadFile(SOLE_LOAD_INFO *pSole, wchar_t *wszRecogDir, LOCRUN_INFO *pLocRunInfo) { wchar_t wszName[MAX_PATH]; FormatPath(wszName, wszRecogDir, NULL, NULL, NULL, L"sole.bin"); if (DoOpenFile(&pSole->info, wszName) == NULL) { return FALSE; } return SoleLoadPointer(pSole, pLocRunInfo); } BOOL SoleUnloadFile(SOLE_LOAD_INFO *pSole) { return DoCloseFile(&pSole->info); } #if 0 BOOL SoleReject (int cStrk, wchar_t wchDense) { RREAL *pNetMem, *pNetOut; int iWinner, cOut; int i; if (cStrk == 1) { pNetMem = _alloca(s_iSoleRejNetSize1 * sizeof (*pNetMem)); if (!pNetMem) return FALSE; for (i = 0; i < SOLE_NUM_FEATURES; i++) { pNetMem[i] = s_aSoleFeat[i]; } pNetOut = runLocalConnectNet(&s_SoleRejNet1, pNetMem, &iWinner, &cOut); ASSERT (cOut == SOLE_OUT_1); return s_aaMap[0][iWinner] != LocRunDense2Unicode (&g_locRunInfo, wchDense); } else { pNetMem = _alloca(s_iSoleRejNetSize2 * sizeof (*pNetMem)); if (!pNetMem) return FALSE; for (i = 0; i < SOLE_NUM_FEATURES; i++) { pNetMem[i] = s_aSoleFeat[i]; } pNetOut = runLocalConnectNet(&s_SoleRejNet2, pNetMem, &iWinner, &cOut); ASSERT (cOut == SOLE_OUT_2); return s_aaMap[1][iWinner] != LocRunDense2Unicode (&g_locRunInfo, wchDense); } } extern wchar_t s_wch; void SaveRecoInfo (wchar_t wch) { static FILE *fp = NULL; if (!fp) { fp = fopen ("recores.txt", "wt"); if (!fp) return; } fprintf (fp, "%d\n", wch == s_wch); fflush (fp); } #define JAWS_ALT 10 int GetCharID (int cStrk, wchar_t wch) { int i, cClass; cClass = (cStrk == 1 ? SOLE_OUT_1 : SOLE_OUT_2); for (i = 0; i < cClass; i++) { if (s_aaMap[cStrk - 1][i] == wch) return i; } return -1; } void SaveJAWSInfo (LATTICE *pLat) { static FILE *fpDisagree1 = NULL, *fpDisagree2 = NULL, *fpSole1 = NULL, *fpSole2 = NULL; FILE *fp, *fpSole; int i, iWinningCand, iSoleBest, cAlt, j, cProbAlt, cStrk, cSoleOut, iClass; LATTICE_ALT_LIST *pAlt; int aSoleCost[JAWS_ALT]; wchar_t wch; RECOG_ALT aProbAlt[JAWS_ALT]; BOXINFO box; RECT bbox; RECT rGuide; GLYPH *pGlyph; if (!pLat->fUseGuide) return; if (!fpDisagree1) { fpDisagree1 = fopen ("jawsdis1.txt", "wt"); if (!fpDisagree1) return; } if (!fpDisagree2) { fpDisagree2 = fopen ("jawsdis2.txt", "wt"); if (!fpDisagree2) return; } if (!fpSole1) { fpSole1 = fopen ("sole1.txt", "wt"); if (!fpSole1) return; } if (!fpSole2) { fpSole2 = fopen ("sole2.txt", "wt"); if (!fpSole2) return; } // find out if the right answer is in the list iWinningCand = iSoleBest = -1; pAlt = pLat->pAltList + pLat->nStrokes - 1; cAlt = pAlt->nUsed; // This is a temporary call to get probs directly, until we have Hawk. cProbAlt = GetProbsTsunami(pLat->nStrokes, pAlt, JAWS_ALT, aProbAlt); // Convert strokes to GLYPHs and FRAMEs so that we can call the // old code. pGlyph = GlyphFromStrokes(pLat->nStrokes, pLat->pStroke); if (!pGlyph) { return; } cStrk = pLat->nStrokes; // Get the bounding box for the character GetRectGLYPH(pGlyph, &bbox); // Free the glyph structure. DestroyFramesGLYPH(pGlyph); DestroyGLYPH(pGlyph); rGuide = GetGuideDrawnBox(&pLat->guide, pLat->pStroke[pLat->nStrokes - 1].iBox); // Build up a BOXINFO structure from the guide, for use in the baseline/height scoring box.size = rGuide.bottom - rGuide.top; box.baseline = rGuide.bottom; box.xheight = box.size / 2; box.midline = box.baseline - box.xheight; cSoleOut = (cStrk == 1 ? SOLE_OUT_1 : SOLE_OUT_2); for (i = 0; i < cAlt && i < JAWS_ALT; i++) { wch = LocRunDense2Unicode(&g_locRunInfo, pAlt->alts[i].wChar); if (wch == s_wch) { iWinningCand = i; } // sole cost for (j = 0; j < cSoleOut; j++) { if (s_aaMap[cStrk -1][j] == wch) { break; } } if (j == cSoleOut) { aSoleCost[i] = 0xFFFF; } else { aSoleCost[i] = (0xFFFF * s_aSoleOut[j]) / SOFT_MAX_UNITY; aSoleCost[i] = 0xFFFF - aSoleCost[i]; } if (iSoleBest == -1 || aSoleCost[iSoleBest] > aSoleCost[i]) { iSoleBest = i; } } for (i = cAlt; i < JAWS_ALT; i++) { aSoleCost[i] = 0xFFFF; } if (iWinningCand == -1) return; if (cStrk == 1) { fpSole = fpSole1; } else { fpSole = fpSole2; } // do not run if sole and otter agree if (!SoleReject (cStrk, pAlt->alts[0].wChar)) { return; } else { if (cStrk == 1) { fp = fpDisagree1; } else { fp = fpDisagree2; } } fprintf (fp, "{ "); // write the alternate features for (i = 0; i < cAlt && i < JAWS_ALT; i++) { int iOttCost, iUni; float fCost; wch = LocRunDense2Unicode(&g_locRunInfo, pAlt->alts[i].wChar); // otter cost iOttCost = min (0xFFFF, (int)(-pAlt->alts[i].logProbPath * 1000)); fprintf (fp, "%d ", iOttCost); // sole cost fprintf (fp, "%d ", aSoleCost[i]); // unigram cost iUni = (int)(-255 * 100 * UnigramCost(&g_unigramInfo, pAlt->alts[i].wChar)); iUni = min (0xFFFF, iUni); fprintf (fp, "%d ", iUni); // baseline trans cost fCost = BaselineTransitionCost(0, bbox, &box, pAlt->alts[i].wChar, bbox, &box); fprintf (fp, "%d ", min (0xFFFF, (int) (-100000.0 * fCost))); // base line cost fCost = BaselineBoxCost(pAlt->alts[i].wChar, bbox, &box); fprintf (fp, "%d ", min (0xFFFF, (int) (-100000.0 * fCost))); // height trans cost fCost = HeightTransitionCost(0, bbox, &box, pAlt->alts[i].wChar, bbox, &box); fprintf (fp, "%d ", min (0xFFFF, (int) (-100000.0 * fCost))); // height cost fCost = HeightBoxCost(pAlt->alts[i].wChar, bbox, &box); fprintf (fp, "%d ", min (0xFFFF, (int) (-100000.0 * fCost))); // describe the codepoint // is it a digit if (wch >= L'0' && wch <= '9') { fprintf (fp, "65535 "); } else { fprintf (fp, "0 "); } // is alpha if ((wch >= L'a' && wch <= 'z') || (wch >= L'A' && wch <= 'Z')) { fprintf (fp, "65535 "); } else { fprintf (fp, "0 "); } // is punct if (iswpunct (wch)) { fprintf (fp, "65535 "); } else { fprintf (fp, "0 "); } // is hiragana if (wch >= 0x3040 && wch <= 0x309f) { fprintf (fp, "65535 "); } else { fprintf (fp, "0 "); } // is katakana if (wch >= 0x30a0 && wch <= 0x30ff) { fprintf (fp, "65535 "); } else { fprintf (fp, "0 "); } // is kanji if (wch >= 0x3190 && wch <= 0xabff) { fprintf (fp, "65535 "); } else { fprintf (fp, "0 "); } } // the rest of the candidates for (i = cAlt; i < JAWS_ALT; i++) { // otter cost fprintf (fp, "%d ", 0xFFFF); // sole cost fprintf (fp, "%d ", 0xFFFF); // unigram cost fprintf (fp, "%d ", 0xFFFF); // baseline trans cost fprintf (fp, "%d ", 0xFFFF); // baseline cost fprintf (fp, "%d ", 0xFFFF); // height trans cost fprintf (fp, "%d ", 0xFFFF); // height cost fprintf (fp, "%d ", 0xFFFF); // describe the codepoint // digit fprintf (fp, "0 "); // is alpha fprintf (fp, "0 "); // is punct fprintf (fp, "0 "); // is hiragana fprintf (fp, "0 "); // is katakana fprintf (fp, "0 "); // is kanji fprintf (fp, "0 "); } // write sole features for fpSole if (iWinningCand != 0) { iClass = GetCharID (cStrk, s_wch); if (iClass >= 0) { fprintf (fpSole, "{ "); for (i = 0; i < SOLE_NUM_FEATURES; i++) { fprintf (fpSole, "%d ", s_aSoleFeat[i]); } fprintf (fpSole, "} { %d }\n", iClass); fflush (fpSole); } } fprintf (fp, "} { %d }\n", iWinningCand); fflush (fp); fflush (fpSole); } void RunJaws (LATTICE *pLat, HWXRESULTS *rgRes) { int i, iSoleBest, cAlt, j, cFeat, k, iWinningCand, iOttBest; LATTICE_ALT_LIST *pAlt; int aSoleCost[JAWS_ALT], aIdx[JAWS_ALT], iWinner, cOut, cStrk, cSoleOut; wchar_t wch, awch[JAWS_ALT]; RREAL *pJawsNetMem, *pJawsNetOut; float fCost; BOXINFO box; RECT bbox; RECT rGuide; GLYPH *pGlyph; pJawsNetMem = _alloca(s_iJawsNetSize * sizeof (*pJawsNetMem)); if (!pJawsNetMem) return; // featurize for JAWS iOttBest = iWinningCand = iSoleBest = -1; pAlt = pLat->pAltList + pLat->nStrokes - 1; cAlt = pAlt->nUsed; // Convert strokes to GLYPHs and FRAMEs so that we can call the // old code. pGlyph = GlyphFromStrokes(pLat->nStrokes, pLat->pStroke); if (!pGlyph) { return; } cStrk = CframeGLYPH (pGlyph); // do not run if sole/reject and otter agree if (!SoleReject (cStrk, pAlt->alts[0].wChar)) return; // Get the bounding box for the character GetRectGLYPH(pGlyph, &bbox); // Free the glyph structure. DestroyFramesGLYPH(pGlyph); DestroyGLYPH(pGlyph); rGuide = GetGuideDrawnBox(&pLat->guide, pLat->pStroke[pLat->nStrokes - 1].iBox); // Build up a BOXINFO structure from the guide, for use in the baseline/height scoring box.size = rGuide.bottom - rGuide.top; box.baseline = rGuide.bottom; box.xheight = box.size / 2; box.midline = box.baseline - box.xheight; cSoleOut = (cStrk == 1 ? SOLE_OUT_1 : SOLE_OUT_2); for (i = 0; i < cAlt && i < JAWS_ALT; i++) { wch = LocRunDense2Unicode(&g_locRunInfo, pAlt->alts[i].wChar); if (wch == s_wch) { iWinningCand = i; } // sole cost for (j = 0; j < cSoleOut; j++) { if (s_aaMap[cStrk - 1][j] == wch) { break; } } if (j == cSoleOut) { aSoleCost[i] = 0xFFFF; } else { aSoleCost[i] = (0xFFFF * s_aSoleOut[j]) / SOFT_MAX_UNITY; aSoleCost[i] = 0xFFFF - aSoleCost[i]; } if (iSoleBest == -1 || aSoleCost[iSoleBest] > aSoleCost[i]) { iSoleBest = i; } if (iOttBest == -1 || pAlt->alts[iOttBest].logProbPath < pAlt->alts[i].logProbPath) { iOttBest = i; } } for (i = cAlt; i < JAWS_ALT; i++) { aSoleCost[i] = 0xFFFF; } // for all candidates cFeat = 0; for (i = 0; i < cAlt && i < JAWS_ALT; i++) { int iOttCost, iUni; wch = LocRunDense2Unicode(&g_locRunInfo, pAlt->alts[i].wChar); // otter cost iOttCost = min (0xFFFF, (int)(-pAlt->alts[i].logProb * 1000)); pJawsNetMem[cFeat++] = iOttCost; // sole cost pJawsNetMem[cFeat++] = aSoleCost[i]; // unigram cost iUni = (int)(-255 * 100 * UnigramCost(&g_unigramInfo, pAlt->alts[i].wChar)); pJawsNetMem[cFeat++] = iUni; // baseline trans cost fCost = BaselineTransitionCost(0, bbox, &box, pAlt->alts[i].wChar, bbox, &box); pJawsNetMem[cFeat++] = min (0xFFFF, (int) (-100000.0 * fCost)); // base line cost fCost = BaselineBoxCost(pAlt->alts[i].wChar, bbox, &box); pJawsNetMem[cFeat++] = min (0xFFFF, (int) (-100000.0 * fCost)); // height trans cost fCost = HeightTransitionCost(0, bbox, &box, pAlt->alts[i].wChar, bbox, &box); pJawsNetMem[cFeat++] = min (0xFFFF, (int) (-100000.0 * fCost)); // height cost fCost = HeightBoxCost(pAlt->alts[i].wChar, bbox, &box); pJawsNetMem[cFeat++] = min (0xFFFF, (int) (-100000.0 * fCost)); // describe the codepoint // digit if (wch >= L'0' && wch <= '9') { pJawsNetMem[cFeat++] = 65535; } else { pJawsNetMem[cFeat++] = 0; } // is alpha if ((wch >= L'a' && wch <= 'z') || (wch >= L'A' && wch <= 'Z')) { pJawsNetMem[cFeat++] = 65535; } else { pJawsNetMem[cFeat++] = 0; } // is punct if (iswpunct (wch)) { pJawsNetMem[cFeat++] = 65535; } else { pJawsNetMem[cFeat++] = 0; } // is hiragana if (wch >= 0x3040 && wch <= 0x309f) { pJawsNetMem[cFeat++] = 65535; } else { pJawsNetMem[cFeat++] = 0; } // is katakana if (wch >= 0x30a0 && wch <= 0x30ff) { pJawsNetMem[cFeat++] = 65535; } else { pJawsNetMem[cFeat++] = 0; } // is kanji if (wch >= 0x3190 && wch <= 0xabff) { pJawsNetMem[cFeat++] = 65535; } else { pJawsNetMem[cFeat++] = 0; } } // the rest of the candidates for (i = cAlt; i < JAWS_ALT; i++) { // otter cost pJawsNetMem[cFeat++] = 65535; // sole cost pJawsNetMem[cFeat++] = 65535; // unigram cost pJawsNetMem[cFeat++] = 65535; // baseline trans cost pJawsNetMem[cFeat++] = 65535; // baseline cost pJawsNetMem[cFeat++] = 65535; // height trans cost pJawsNetMem[cFeat++] = 65535; // height cost pJawsNetMem[cFeat++] = 65535; // describe the codepoint // digit pJawsNetMem[cFeat++] = 0; // is alpha pJawsNetMem[cFeat++] = 0; // is punct pJawsNetMem[cFeat++] = 0; // is hiragana pJawsNetMem[cFeat++] = 0; // is katakana pJawsNetMem[cFeat++] = 0; // is kanji pJawsNetMem[cFeat++] = 0; } // call the JAWS net pJawsNetOut = runLocalConnectNet(&s_JawsNet, pJawsNetMem, &iWinner, &cOut); ASSERT (cOut == JAWS_ALT); // index the result for (i = 0; i < JAWS_ALT; i++) { aIdx[i] = i; } for (i = 0; i < JAWS_ALT; i++) { for (j = i + 1; j < JAWS_ALT; j++) { if (pJawsNetOut[aIdx[i]] < pJawsNetOut[aIdx[j]]) { k = aIdx[i]; aIdx[i] = aIdx[j]; aIdx[j] = k; } } awch[i] = pAlt->alts[aIdx[i]].wChar; } for (i = 0; i < cAlt && i < JAWS_ALT; i++) { rgRes->rgChar[i] = LocRunDense2Unicode(&g_locRunInfo, awch[i]); } } #endif #if 0 LOCAL_NET *LoadNet(void *pData, int *piNetSize, LOCAL_NET *pNet) { if ( !pData || !(pNet = restoreLocalConnectNet(pData, 0, pNet)) ) { return NULL; } (*piNetSize) = getRunTimeNetMemoryRequirements(pData); if ((*piNetSize) <= 0) { return NULL; } return pNet; } BOOL LoadSoleFromFile(wchar_t *pwszPath) { BYTE *pData; wchar_t aPath[128]; HANDLE hFile, hMap; // Generate path to file. By passing in name as "locale" we can get FormatPath // to do what we want. wsprintf (aPath, L"%s\\sole1.bin", pwszPath); // Try to open the file. hFile = CreateMappingCall (aPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { return FALSE; } // Create a mapping handle hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMap == NULL) { return FALSE; } // Map the entire file starting at the first byte pData = (void *) MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (pData == NULL) { return FALSE; } // Sole net if (!LoadNet(pData, &s_iSoleNetSize1, &s_SoleNet1)) { return FALSE; } // Generate path to file. By passing in name as "locale" we can get FormatPath // to do what we want. wsprintf (aPath, L"%s\\sole2.bin", pwszPath); // Try to open the file. hFile = CreateMappingCall (aPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { return FALSE; } // Create a mapping handle hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMap == NULL) { return FALSE; } // Map the entire file starting at the first byte pData = (void *) MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (pData == NULL) { return FALSE; } // Sole net if (!LoadNet(pData, &s_iSoleNetSize2, &s_SoleNet2)) { return FALSE; } // Generate path to file. By passing in name as "locale" we can get FormatPath // to do what we want. wsprintf (aPath, L"%s\\solerej1.bin", pwszPath); // Try to open the file. hFile = CreateMappingCall (aPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { return FALSE; } // Create a mapping handle hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMap == NULL) { return FALSE; } // Map the entire file starting at the first byte pData = (void *) MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (pData == NULL) { return FALSE; } // Sole net if (!LoadNet(pData, &s_iSoleRejNetSize1, &s_SoleRejNet1)) { return FALSE; } // Generate path to file. By passing in name as "locale" we can get FormatPath // to do what we want. wsprintf (aPath, L"%s\\solerej2.bin", pwszPath); // Try to open the file. hFile = CreateMappingCall (aPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { return FALSE; } // Create a mapping handle hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMap == NULL) { return FALSE; } // Map the entire file starting at the first byte pData = (void *) MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (pData == NULL) { return FALSE; } // Sole net if (!LoadNet(pData, &s_iSoleRejNetSize2, &s_SoleRejNet2)) { return FALSE; } return TRUE; } #endif #if 0 BOOL LoadJawsFromFile(wchar_t *pwszPath) { BYTE *pData; wchar_t aPath[128]; HANDLE hFile, hMap; // Generate path to file. By passing in name as "locale" we can get FormatPath // to do what we want. wsprintf (aPath, L"%s\\jaws.bin", pwszPath); // Try to open the file. hFile = CreateMappingCall (aPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { return FALSE; } // Create a mapping handle hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMap == NULL) { return FALSE; } // Map the entire file starting at the first byte pData = (void *) MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (pData == NULL) { return FALSE; } // Jaws net if (!LoadNet(pData, &s_iJawsNetSize, &s_JawsNet)) { return FALSE; } return TRUE; } #endif