Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3876 lines
126 KiB

  1. //$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
  2. //
  3. // Copyright (c) 2001 Microsoft Corporation. All rights reserved.
  4. //
  5. // Module:
  6. // volcano/dll/lattice.c
  7. //
  8. // Description:
  9. // Functions to implement the lattice search for the best
  10. // explanation of the input.
  11. //
  12. // Author:
  13. // hrowley
  14. //
  15. // Modified by:
  16. // ahmadab 11/05/01
  17. //
  18. //$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
  19. #include <math.h>
  20. #include <stdlib.h>
  21. #include "volcanop.h"
  22. #include "otterp.h"
  23. #include "bboxfeat.h"
  24. // If defined, then centipede is used
  25. #define USE_CENTIPEDE
  26. #ifdef USE_CENTIPEDE
  27. // If defined, all scoring apart from centipede is disabled, to isolate the
  28. // contribution of centipede relative to the old heuristics.
  29. //#define TEST_CENTIPEDE
  30. #endif
  31. //#define DUMP_BBOXES
  32. // This define controls whether to merge sequential strokes whose end and start points
  33. // are so close together that they were probably caused by a pen skip. This helps with
  34. // old data from flaky hardware. Note that this code is size dependent...
  35. #define MERGE_STROKES
  36. // This defines the maximum distance between the end point of one stroke and the start
  37. // of the next one in order for them to be merged.
  38. #define MERGE_THRESHOLD 10
  39. // This defines the minimum number of characters which must be available to apply
  40. // IFELang3. For boxed mode, this is just a count of the number of distinct boxes
  41. // containing ink plus the number of context characters. For free mode, we use the
  42. // number of characters on the best path using the internal language model plus the
  43. // context.
  44. #define MIN_CHARS_FOR_IFELANG3 3
  45. // There is a special case for boxed mode when there is only one character of ink,
  46. // in which we require that the pre- and post-context both have at least this many
  47. // characters.
  48. #define MIN_CONTEXT_FOR_IFELANG3 2
  49. // Uncomment to dump out the lattice
  50. //#define DUMP_LATTICE
  51. // Name of lattice file
  52. #define LATTICE_FILENAME "c:/lattice.txt"
  53. // Whether to dump in a format readable by the DOTTY program
  54. #define DUMP_LATTICE_TO_DOTTY
  55. // Uncomment to dump out the DTW lattice used by SearchForTargetResult()
  56. //#define DUMP_DTW
  57. #ifdef HWX_TUNE
  58. #include <stdio.h>
  59. #endif
  60. // These functions were copied and adapted from pathsrch.c in Tsunami
  61. #define TYPE_BASE_MASK (BYTE)0x0f
  62. #define TYPE_HEIGHT_MASK (BYTE)0xf0
  63. #define BASE_NORMAL 0x00 // kanji, kana, numbers, etc
  64. #define BASE_QUOTE 0x01 // upper punctuation, etc
  65. #define BASE_DASH 0x02 // middle punctuation, etc
  66. #define BASE_DESCENDER 0x03 // gy, anything that descends.
  67. #define BASE_THIRD 0x04 // something that starts a third way up.
  68. #define XHEIGHT_HALF 0x00 // lower-case, small kana, etc
  69. #define XHEIGHT_FULL 0x10 // upper-case, kana, kanji, numbers, etc
  70. #define XHEIGHT_PUNC 0x20 // comma, quote, etc
  71. #define XHEIGHT_DASH 0x30 // dash, period, etc
  72. #define XHEIGHT_3Q 0x40
  73. #define XHEIGHT_NORMAL 0x00 // lower-case, small kana, etc
  74. #define XHEIGHT_KANJI 0x10 // upper-case, kana, kanji, numbers, etc
  75. // This will go away when we use Greg's scoring code
  76. float FloatClippedLog2(COUNTER num, COUNTER denom)
  77. {
  78. double ratio, val;
  79. ASSERT(num>=0);
  80. ASSERT(denom>=0);
  81. if (denom==0) return Log2Range;
  82. if (num==0) return Log2Range;
  83. ratio=(double)num/(double)denom;
  84. val=log(ratio)/log(2.0);
  85. if (val<Log2Range) val=Log2Range;
  86. if (val>0) val=0;
  87. return (FLOAT)val;
  88. }
  89. // Given the bounding box of the ink and the dense code of a character,
  90. // guess what the writing area was for the character. This is the centipede version.
  91. RECT GuessWritingBoxCentipede(RECT bbox, SYM sym)
  92. {
  93. int stats[INKSTAT_ALL];
  94. RECT result;
  95. memset(stats,0,sizeof(int)*INKSTAT_ALL);
  96. stats[INKSTAT_W] = bbox.right - bbox.left;
  97. stats[INKSTAT_H] = bbox.bottom - bbox.top;
  98. // Get the inferred box enclosing the character.
  99. ShapeUnigramBaseline(&g_centipedeInfo, sym, stats);
  100. result.left = bbox.left - stats[INKSTAT_X];
  101. result.top = bbox.top - stats[INKSTAT_Y];
  102. result.right = result.left + stats[INKSTAT_BOX_W];
  103. result.bottom = result.top + stats[INKSTAT_BOX_H];
  104. if (result.right == result.left) result.right++;
  105. if (result.bottom == result.top) result.bottom++;
  106. ASSERT(result.bottom > result.top);
  107. return result;
  108. }
  109. // Given the bounding box of the ink and the dense code of a character,
  110. // guess what the writing area was for the character.
  111. RECT GuessWritingBox(RECT bbox, SYM sym)
  112. {
  113. return GuessWritingBoxCentipede(bbox,sym);
  114. }
  115. // Given a guide and a box number, get the drawn box
  116. RECT GetGuideDrawnBox(HWXGUIDE *guide, int iBox)
  117. {
  118. RECT box;
  119. box.top = (iBox / guide->cHorzBox) * guide->cyBox + guide->yOrigin + guide->cyOffset;
  120. box.bottom = box.top + guide->cyWriting;
  121. box.left = (iBox % guide->cHorzBox) * guide->cxBox + guide->xOrigin + guide->cxOffset;
  122. box.right = box.left + guide->cxWriting;
  123. return box;
  124. }
  125. // Get information about a given box in the guide, will go away
  126. // when the insurance version goes away.
  127. BOXINFO GetBoxinfo(HWXGUIDE *guide, int iBox)
  128. {
  129. RECT rect = GetGuideDrawnBox(guide,iBox);
  130. BOXINFO box;
  131. box.size = rect.bottom - rect.top;
  132. box.baseline = rect.bottom;
  133. box.xheight = box.size / 2;
  134. box.midline = box.baseline - box.xheight;
  135. return box;
  136. }
  137. // Functions copied from the old lattice search code, used by the insurance version.
  138. FLOAT BaselineTransitionCost(SYM symPrev, RECT rPrev, BOXINFO *biPrev, SYM sym, RECT r, BOXINFO *bi)
  139. {
  140. BYTE type, typePrev;
  141. int base;
  142. FLOAT cost;
  143. // ASSUMPTION: SYM_UNKNOWN should be the only sym if its present.
  144. // So there aren't any alternatives that could get a "better" cost
  145. // so it probably doesn't really matter what cost we return here
  146. if (sym == SYM_UNKNOWN)
  147. return (FLOAT) 0.0;
  148. type = LocRunDense2BLineHgt(&g_locRunInfo, sym);
  149. type = LOCBH_BASE_MASK & type;
  150. if (symPrev == SYM_UNKNOWN)
  151. symPrev = 0;
  152. if (symPrev)
  153. {
  154. typePrev = LocRunDense2BLineHgt(&g_locRunInfo, symPrev);
  155. typePrev = LOCBH_BASE_MASK & typePrev;
  156. }
  157. //
  158. // If the first and second chars are supposed to have the same baseline then
  159. // compute a penalty based on the difference in their baseline.
  160. //
  161. if (symPrev && type == typePrev)
  162. {
  163. cost = (FLOAT) (100L * abs((r.bottom-bi->baseline) - (rPrev.bottom-biPrev->baseline)) / (bi->size * 2));
  164. // cost = (FLOAT) (100L * abs((r.bottom) - (rPrev.bottom)) / (bi->size * 2));
  165. }
  166. else
  167. {
  168. switch (type)
  169. {
  170. case BASE_NORMAL:
  171. base = bi->baseline;
  172. break;
  173. case BASE_THIRD:
  174. base = bi->baseline - (bi->xheight / 2);
  175. break;
  176. case BASE_DASH:
  177. base = bi->baseline - bi->xheight;
  178. break;
  179. case BASE_QUOTE:
  180. base = bi->baseline - (7 * bi->xheight / 4);
  181. break;
  182. default:
  183. base = bi->baseline;
  184. break;
  185. }
  186. cost = (FLOAT) (100L * abs(r.bottom - base) / (2 * bi->size));
  187. }
  188. cost = (-cost) / (FLOAT) 100.0;
  189. // ASSERT(cost <= 0.0);
  190. return(cost);
  191. }
  192. #define HEIGHT_DASH 20
  193. FLOAT HeightTransitionCost(SYM symPrev, RECT rPrev, BOXINFO *biPrev, SYM sym, RECT r, BOXINFO *bi)
  194. {
  195. BYTE type, typePrev;
  196. int ht, htPrev;
  197. FLOAT cost;
  198. // ASSUMPTION: SYM_UNKNOWN should be the only sym if its present.
  199. // So there aren't any alternatives that could get a "better" cost
  200. // so it probably doesn't really matter what cost we return here
  201. if (sym == SYM_UNKNOWN)
  202. return (FLOAT) 0.0;
  203. if (symPrev == SYM_UNKNOWN)
  204. symPrev = 0;
  205. cost = (FLOAT) 0.0;
  206. type = LocRunDense2BLineHgt(&g_locRunInfo, sym);
  207. type = LOCBH_HEIGHT_MASK & type;
  208. ht = r.bottom - r.top;
  209. // we may want to handle XHEIGHT_PUNC in the same manner that
  210. // we do XHEIGHT_DASH, i.e. no relative sizing
  211. if (type == XHEIGHT_DASH)
  212. {
  213. cost = (FLOAT) (100L * abs(ht - HEIGHT_DASH) / (ht + HEIGHT_DASH));
  214. }
  215. else
  216. {
  217. if (symPrev)
  218. {
  219. typePrev = LocRunDense2BLineHgt(&g_locRunInfo, symPrev);
  220. typePrev = LOCBH_HEIGHT_MASK & typePrev;
  221. }
  222. if (symPrev && typePrev != XHEIGHT_DASH)
  223. {
  224. //
  225. // We scale everything up to be normal (1/2) height.
  226. //
  227. htPrev = rPrev.bottom - rPrev.top;
  228. if (type == XHEIGHT_KANJI)
  229. ht = ht / 2;
  230. else if (type == XHEIGHT_PUNC)
  231. ht = ht * 3;
  232. if (typePrev == XHEIGHT_KANJI)
  233. htPrev = htPrev / 2;
  234. else if (typePrev == XHEIGHT_PUNC)
  235. htPrev = htPrev * 3;
  236. if ((ht + htPrev) == 0)
  237. {
  238. cost = (FLOAT) 0.0;
  239. }
  240. else
  241. {
  242. cost = (FLOAT) (100L * abs(ht - htPrev) / (ht + htPrev));
  243. }
  244. }
  245. else
  246. {
  247. //
  248. // We scale everything up to be 1/2 height of box.
  249. //
  250. if (type == XHEIGHT_KANJI)
  251. {
  252. // 3/4 of the box
  253. ht = (2 * ht) / 3;
  254. }
  255. else if (type == XHEIGHT_NORMAL)
  256. {
  257. // 1/3 of the box
  258. ht = (3 * ht) / 2;
  259. }
  260. else if (type == XHEIGHT_PUNC)
  261. {
  262. // 1/8 of the box
  263. ht = 4 * ht;
  264. }
  265. //
  266. // Now we compute a cost based on how far off from what
  267. // we computed we should be.
  268. //
  269. cost = (FLOAT) (100L * abs(ht - bi->xheight) / (2 * bi->xheight));
  270. }
  271. }
  272. cost = (-cost) / (FLOAT) 100.0;
  273. // ASSERT(cost <= 0.0);
  274. return(cost);
  275. }
  276. /******************************Public*Routine******************************\
  277. * HeightBoxCost
  278. *
  279. * This function computes the likelihood of a character given the height of
  280. * the character written and the height of the box the person was supposed
  281. * to write in.
  282. *
  283. * History:
  284. * 05-May-1995 -by- Patrick Haluptzok patrickh
  285. * Wrote it.
  286. \**************************************************************************/
  287. FLOAT HeightBoxCost(SYM sym, RECT r, BOXINFO *bi)
  288. {
  289. int heightShouldBe; // This is the height it should be given the box
  290. // they were told to write in.
  291. int heightIs; // This is the height the glyph is.
  292. BYTE type;
  293. FLOAT cost;
  294. heightIs = r.bottom - r.top;
  295. // ASSUMPTION: SYM_UNKNOWN should be the only sym if its present.
  296. // So there aren't any alternatives that could get a "better" cost
  297. // so it probably doesn't really matter what cost we return here
  298. if (sym == SYM_UNKNOWN)
  299. {
  300. return (FLOAT) 0.0;
  301. }
  302. type = LocRunDense2BLineHgt(&g_locRunInfo, sym);
  303. // Could not determine the code
  304. if (type == LOC_RUN_NO_BLINEHGT) {
  305. // ASSERT(0);
  306. return (FLOAT) 0.0;
  307. }
  308. type = LOCBH_HEIGHT_MASK & type;
  309. switch (type)
  310. {
  311. case XHEIGHT_KANJI: // full height "ABC" etc.
  312. heightShouldBe = (bi->size * 3) / 4;
  313. if (heightIs >= heightShouldBe)
  314. {
  315. // No cost, can't be anything bigger.
  316. cost = (FLOAT) 0.0;
  317. }
  318. else
  319. {
  320. cost = (FLOAT) ( ((float) (heightShouldBe - heightIs)) / (float) (bi->size));
  321. }
  322. break;
  323. case XHEIGHT_NORMAL: // half height "ace" etc.
  324. heightShouldBe = (bi->size * 2) / 5;
  325. cost = (FLOAT) ( ((float) abs(heightIs - heightShouldBe)) / (float) (bi->size));
  326. break;
  327. case XHEIGHT_PUNC: // small height "maru ," etc.
  328. heightShouldBe = bi->size / 6;
  329. cost = (FLOAT) (((float) abs(heightIs - heightShouldBe)) / (float) (bi->size));
  330. break;
  331. case XHEIGHT_DASH:
  332. heightShouldBe = bi->xheight / 8;
  333. if (heightIs <= heightShouldBe)
  334. {
  335. //
  336. // It's below the minimum height, way down, so no penalty given.
  337. //
  338. cost = (FLOAT) 0.0;
  339. }
  340. else
  341. {
  342. cost = (FLOAT) (((float) (heightIs - heightShouldBe)) / (float) (bi->size));
  343. }
  344. break;
  345. default:
  346. ASSERT(0); // We should not get here but if we do we have some default
  347. // behaviour that should be OK.
  348. cost = (FLOAT) 0.0;
  349. }
  350. cost = cost * cost * -5.0F;
  351. // ASSERT(cost <= 0.0);
  352. return(cost);
  353. }
  354. /******************************Public*Routine******************************\
  355. * BaselineBoxCost
  356. *
  357. * This function computes a penalty given the baseline of the character
  358. * and where we thought the characters baseline should be given the box
  359. * they were told to write in.
  360. *
  361. * History:
  362. * 04-May-1995 -by- Patrick Haluptzok patrickh
  363. * Modify it.
  364. \**************************************************************************/
  365. FLOAT BaselineBoxCost(SYM sym, RECT r, BOXINFO *bi)
  366. {
  367. BYTE type;
  368. FLOAT cost;
  369. int baselineShouldBe; // This is what the baseline should be for the
  370. // char we are proposing.
  371. int baselineIs; // This is what the baseline is for the char written.
  372. // ASSUMPTION: SYM_UNKNOWN should be the only sym if its present.
  373. // So there aren't any alternatives that could get a "better" cost
  374. // so it probably doesn't really matter what cost we return here.
  375. if (sym == SYM_UNKNOWN)
  376. {
  377. return (FLOAT) 0.0;
  378. }
  379. type = LocRunDense2BLineHgt(&g_locRunInfo, sym);
  380. // Could not determine the code
  381. if (type == LOC_RUN_NO_BLINEHGT) {
  382. // ASSERT(0);
  383. return (FLOAT) 0.0;
  384. }
  385. type = LOCBH_BASE_MASK & type;
  386. baselineIs = r.bottom; // This is what the baseline is for the glyph.
  387. switch (type)
  388. {
  389. case BASE_NORMAL:
  390. baselineShouldBe = bi->baseline - (bi->size / 8);
  391. cost = (FLOAT) (100L * abs(baselineIs - baselineShouldBe) / (bi->size));
  392. break;
  393. case BASE_THIRD:
  394. baselineShouldBe = bi->baseline - (bi->size / 4);
  395. cost = (FLOAT) (100L * abs(baselineIs - baselineShouldBe) / (bi->size));
  396. break;
  397. case BASE_DASH:
  398. baselineShouldBe = bi->midline;
  399. cost = (FLOAT) (100L * abs(baselineIs - baselineShouldBe) / (bi->size));
  400. break;
  401. case BASE_QUOTE:
  402. ASSERT((bi->baseline - bi->midline) > 0);
  403. baselineShouldBe = ((bi->baseline - bi->size + bi->midline) / 2);
  404. if (baselineIs <= baselineShouldBe)
  405. {
  406. //
  407. // It's above the quote baseline, way up high, so no penalty
  408. // for any BASE_QUOTE chars.
  409. //
  410. cost = (FLOAT) 0.0;
  411. }
  412. else
  413. {
  414. cost = (FLOAT) (100L * (baselineIs - baselineShouldBe) / (bi->size));
  415. }
  416. break;
  417. case BASE_DESCENDER:
  418. baselineShouldBe = bi->baseline;
  419. if (baselineIs >= baselineShouldBe)
  420. {
  421. //
  422. // It's below the descender baseline, way down, so no penalty
  423. // for any BASE_DESCENDER chars.
  424. //
  425. cost = (FLOAT) 0.0;
  426. }
  427. else
  428. {
  429. cost = (FLOAT) (100L * (baselineShouldBe - baselineIs) / (bi->size));
  430. }
  431. break;
  432. default:
  433. ASSERT(0); // We should not get here but if we do we have some default
  434. // behaviour that should be OK.
  435. cost = (FLOAT) 0.0;
  436. break;
  437. }
  438. cost = (-cost) / (FLOAT) 100.0;
  439. // ASSERT(cost <= 0.0);
  440. return(cost);
  441. }
  442. // Given a character (dense code), return the probability of that character occuring in natural text.
  443. // If fProbMode is not set, then the value is returned as a score (used by the insurance recognizer).
  444. void LanguageModelUnigram(BOOL fProbMode, wchar_t wChar, int nStrokes,
  445. VOLCANO_WEIGHTS *pTuneScores)
  446. {
  447. #ifdef DISABLE_HEURISTICS
  448. return;
  449. #else
  450. pTuneScores->afl[VTUNE_UNIGRAM] += (float) (UnigramCost(&g_unigramInfo, wChar) * 100.0 / 10.0);
  451. // float fl = (float) (UnigramCost(&g_unigramInfo, wChar) * 100.0 / 10.0);
  452. // fl = log(0.5 * pow(2, fl) + 0.5 / g_locRunInfo.cCodePoints);
  453. // pTuneScores->aflWeights[VTUNE_UNIGRAM] = fl;
  454. #endif
  455. }
  456. // Given a pair of characters (dense codes), return the probability of that character sequence occurring
  457. // in natural text.
  458. // If fProbMode is not set, then the value is returned as a score (used by the insurance recognizer).
  459. // The fStringMode flag is used to adjust the tuning parameters for the insurance version.
  460. void LanguageModelBigram(BOOL fProbMode, BOOL fStringMode, BOOL fFreeMode, wchar_t wChar, int nStrokes, wchar_t wPrevChar,
  461. VOLCANO_WEIGHTS *pTuneScores)
  462. {
  463. #ifdef DISABLE_HEURISTICS
  464. return;
  465. #else
  466. if (wPrevChar==SYM_UNKNOWN)
  467. {
  468. LanguageModelUnigram(fProbMode, wChar, nStrokes, pTuneScores);
  469. return;
  470. }
  471. pTuneScores->afl[fFreeMode ? VTUNE_FREE_SMOOTHING_UNIGRAM : VTUNE_STRING_SMOOTHING_UNIGRAM] +=
  472. (float)(UnigramCost(&g_unigramInfo, wChar) * 100.0 / 10.0);
  473. #if !defined(WINCE) && !defined(FAKE_WINCE)
  474. pTuneScores->afl[fFreeMode ? VTUNE_FREE_BIGRAM : VTUNE_STRING_BIGRAM] +=
  475. (float)(BigramTransitionCost(&g_locRunInfo, &g_bigramInfo, wPrevChar, wChar) * 256.0 / 10.0);
  476. #endif
  477. pTuneScores->afl[fFreeMode ? VTUNE_FREE_CLASS_BIGRAM : VTUNE_STRING_CLASS_BIGRAM] +=
  478. (float)(ClassBigramTransitionCost(&g_locRunInfo, &g_classBigramInfo, wPrevChar, wChar) * 256.0 / 10.0);
  479. #endif
  480. }
  481. // Allocate memory for a path of nChars characters. nChars can be zero.
  482. LATTICE_PATH *AllocatePath(int nChars)
  483. {
  484. LATTICE_PATH *path=(LATTICE_PATH*)ExternAlloc(sizeof(LATTICE_PATH));
  485. if (path==NULL) return NULL;
  486. path->nChars=nChars;
  487. if (nChars==0) {
  488. path->pElem=NULL;
  489. } else {
  490. path->pElem=(LATTICE_PATH_ELEMENT*)ExternAlloc(sizeof(LATTICE_PATH_ELEMENT)*nChars);
  491. if (path->pElem==NULL) {
  492. ExternFree(path);
  493. path=NULL;
  494. }
  495. }
  496. return path;
  497. }
  498. // Create a stroke structure to store in the lattice. Makes a copy of the
  499. // array of points that is passed in.
  500. BOOL CreateStroke(STROKE *pStroke, int nInk, POINT *pts, long time)
  501. {
  502. int iInk;
  503. ASSERT(nInk>0);
  504. ASSERT(pts!=NULL);
  505. pStroke->pts=(POINT*)ExternAlloc(sizeof(POINT)*nInk);
  506. if (pStroke->pts!=NULL) {
  507. pStroke->nInk=nInk;
  508. memcpy(pStroke->pts,pts,sizeof(POINT)*nInk);
  509. } else {
  510. return FALSE;
  511. }
  512. pStroke->bbox.left=pStroke->bbox.right=pts[0].x;
  513. pStroke->bbox.top=pStroke->bbox.bottom=pts[0].y;
  514. for (iInk=1; iInk<nInk; iInk++) {
  515. pStroke->bbox.left=__min(pStroke->bbox.left,pts[iInk].x);
  516. pStroke->bbox.right=__max(pStroke->bbox.right,pts[iInk].x);
  517. pStroke->bbox.top=__min(pStroke->bbox.top,pts[iInk].y);
  518. pStroke->bbox.bottom=__max(pStroke->bbox.bottom,pts[iInk].y);
  519. }
  520. // Make the bottom and right sides exclusive, to match the old glyph code
  521. pStroke->bbox.right++;
  522. pStroke->bbox.bottom++;
  523. pStroke->timeStart=time;
  524. pStroke->timeEnd=time+10*nInk;
  525. pStroke->iBox=-1;
  526. return TRUE;
  527. }
  528. #ifdef MERGE_STROKES
  529. // Given two strokes, add the ink from the second stroke at the end of the first
  530. // stroke. The second stroke is freed. Returns FALSE if for some reason it fails,
  531. // in which case the second stroke is not freed.
  532. BOOL AddStrokeToStroke(STROKE *pStroke, STROKE *pAddStroke)
  533. {
  534. POINT *pNewPoints=(POINT*)ExternAlloc(sizeof(POINT)*(pStroke->nInk+pAddStroke->nInk));
  535. if (pNewPoints==NULL) return FALSE;
  536. memcpy(pNewPoints,pStroke->pts,sizeof(POINT)*pStroke->nInk);
  537. memcpy(pNewPoints+pStroke->nInk,pAddStroke->pts,sizeof(POINT)*pAddStroke->nInk);
  538. UnionRect(&pStroke->bbox, &pStroke->bbox, &pAddStroke->bbox);
  539. pStroke->timeEnd=pAddStroke->timeEnd;
  540. pStroke->iLast = pAddStroke->iLast;
  541. pStroke->nInk+=pAddStroke->nInk;
  542. ExternFree(pStroke->pts);
  543. pStroke->pts=pNewPoints;
  544. return TRUE;
  545. }
  546. #endif
  547. // Free the memory for a stroke
  548. void FreeStroke(STROKE *stroke)
  549. {
  550. ASSERT(stroke!=NULL);
  551. ASSERT(stroke->pts!=NULL);
  552. ExternFree(stroke->pts);
  553. }
  554. // Delete the specified alternate from the specified stroke.
  555. void DeleteElement(LATTICE *lat, int iStroke, int iAlt)
  556. {
  557. // If there are any alternates after the specified one, move them up.
  558. if (lat->pAltList[iStroke].nUsed-iAlt-1>0) {
  559. memmove(&lat->pAltList[iStroke].alts[iAlt],
  560. &lat->pAltList[iStroke].alts[iAlt+1],
  561. sizeof(LATTICE_ELEMENT)*(lat->pAltList[iStroke].nUsed-iAlt-1));
  562. }
  563. // Reduce the number of elements in the column.
  564. lat->pAltList[iStroke].nUsed--;
  565. }
  566. // Insert a new alternate into the specified lattice column. If the
  567. // column already contains a "matching" element (having the same character
  568. // label or number of strokes), then that element will get replaced if
  569. // the given one has a higher log prob path score.
  570. void InsertElement(LATTICE *lat, int iStroke, LATTICE_ELEMENT *elem)
  571. {
  572. int newLength;
  573. int iAlt;
  574. // make sure that by default the char detector score is set -1 for any new element
  575. elem->iCharDetectorScore = -1;
  576. // Log prob is too low (a case we haven't seen in training), so ignore this path
  577. // if (elem->logProb<=Log2Range) return;
  578. // Make sure that the element being inserted is connected to a path
  579. // (unless it is connecting to the start of the lattice).
  580. ASSERT(iStroke-elem->nStrokes+1==0 || elem->iPrevAlt!=-1);
  581. // For each alternate already present, check for a match with the given one
  582. for (iAlt=0; iAlt<lat->pAltList[iStroke].nUsed; iAlt++) {
  583. BOOL fCharMatch=elem->wChar==lat->pAltList[iStroke].alts[iAlt].wChar;
  584. // BOOL fPrevCharMatch=elem->wPrevChar==lat->pAltList[iStroke].alts[iAlt].wPrevChar;
  585. BOOL fnStrokesMatch=elem->nStrokes==lat->pAltList[iStroke].alts[iAlt].nStrokes;
  586. // BOOL fnPrevStrokesMatch=elem->nPrevStrokes==lat->pAltList[iStroke].alts[iAlt].nPrevStrokes;
  587. // BOOL fMatch=fCharMatch && fPrevCharMatch && fnStrokesMatch && fnPrevStrokesMatch;
  588. BOOL fMatch=fCharMatch && fnStrokesMatch;
  589. if (fMatch) {
  590. // If it matches, check the log probs
  591. if (elem->logProbPath > lat->pAltList[iStroke].alts[iAlt].logProbPath) {
  592. // Matched a previous entry, but with a higher log prob, so replace it.
  593. DeleteElement(lat,iStroke,iAlt);
  594. // Insert later as normal
  595. break;
  596. } else {
  597. // Matched a previous entry, but with a lower score, so drop the given element.
  598. return;
  599. }
  600. }
  601. }
  602. // Compute the new length of the alt list after the insertion.
  603. newLength=lat->pAltList[iStroke].nUsed;
  604. if (lat->pAltList[iStroke].nUsed<MaxAltsPerStroke)
  605. newLength++;
  606. // Go through all the potential places for the given element.
  607. for (iAlt=0; iAlt<newLength; iAlt++)
  608. // If we are beyond the end of the existing elements in the list, or we beat the current element
  609. if (iAlt > lat->pAltList[iStroke].nUsed-1 ||
  610. elem->logProbPath > lat->pAltList[iStroke].alts[iAlt].logProbPath) {
  611. // Shift all remaining elements (if any) down.
  612. if (newLength-iAlt-1>0) {
  613. memmove(&lat->pAltList[iStroke].alts[iAlt+1],
  614. &lat->pAltList[iStroke].alts[iAlt],
  615. sizeof(LATTICE_ELEMENT)*(newLength-iAlt-1));
  616. }
  617. // Put in the new element
  618. lat->pAltList[iStroke].alts[iAlt] = *elem;
  619. break;
  620. }
  621. // Store thew new alt list length
  622. lat->pAltList[iStroke].nUsed=newLength;
  623. }
  624. // Clear out an alt list
  625. void ClearAltList(LATTICE_ALT_LIST *list)
  626. {
  627. int i;
  628. ASSERT(list!=NULL);
  629. list->nUsed=0;
  630. for (i=0; i<MaxAltsPerStroke; i++) list->alts[i].fUsed=FALSE;
  631. }
  632. // Create an empty lattice data structure.
  633. LATTICE *AllocateLattice()
  634. {
  635. LATTICE *lat=(LATTICE*)ExternAlloc(sizeof(LATTICE));
  636. if (lat!=NULL) {
  637. lat->nStrokes=0;
  638. lat->nRealStrokes = 0;
  639. lat->nStrokesAllocated=0;
  640. lat->pStroke=NULL;
  641. lat->pAltList=NULL;
  642. lat->fUseFactoid = FALSE;
  643. lat->alcFactoid = 0;
  644. lat->pbFactoidChars = NULL;
  645. lat->recogSettings.alcValid=0xFFFFFFFF;
  646. lat->recogSettings.alcPriority=0;
  647. lat->recogSettings.pbAllowedChars = NULL;
  648. lat->recogSettings.pbPriorityChars = NULL;
  649. lat->recogSettings.partialMode=HWX_PARTIAL_ALL;
  650. lat->recogSettings.pAbort=(UINT *)0;
  651. lat->recogSettings.pbAllowedChars = NULL;
  652. lat->fUseGuide=FALSE;
  653. lat->wszBefore = NULL;
  654. lat->wszAfter = NULL;
  655. lat->nProcessed = 0;
  656. lat->fEndInput = FALSE;
  657. lat->fIncremental = FALSE;
  658. lat->nFixedResult = 0;
  659. lat->wszAnswer = NULL;
  660. lat->fWordMode = FALSE;
  661. lat->fSingleSeg = FALSE;
  662. lat->fCoerceMode = FALSE;
  663. lat->fSepMode = 0;
  664. lat->pvCache = NULL;
  665. lat->fUseLM = TRUE;
  666. }
  667. return lat;
  668. }
  669. // Creates a new lattice structure with the same settings as the old one.
  670. LATTICE *CreateCompatibleLattice(LATTICE *lat)
  671. {
  672. LATTICE *newlat=AllocateLattice();
  673. if (newlat==NULL || lat==NULL) return newlat;
  674. newlat->recogSettings=lat->recogSettings;
  675. newlat->recogSettings.pbAllowedChars = CopyAllowedChars(&g_locRunInfo, lat->recogSettings.pbAllowedChars);
  676. newlat->recogSettings.pbPriorityChars = CopyAllowedChars(&g_locRunInfo, lat->recogSettings.pbPriorityChars);
  677. newlat->pbFactoidChars = CopyAllowedChars(&g_locRunInfo, lat->pbFactoidChars);
  678. newlat->alcFactoid = lat->alcFactoid;
  679. newlat->fUseFactoid = lat->fUseFactoid;
  680. newlat->fWordMode = lat->fWordMode;
  681. newlat->fSingleSeg = lat->fSingleSeg;
  682. newlat->fCoerceMode = lat->fCoerceMode;
  683. newlat->fUseGuide=lat->fUseGuide;
  684. newlat->guide=lat->guide;
  685. newlat->fUseLM = lat->fUseLM;
  686. if (lat->wszBefore != NULL)
  687. {
  688. newlat->wszBefore = Externwcsdup(lat->wszBefore);
  689. if (newlat->wszBefore == NULL)
  690. {
  691. ExternFree(newlat);
  692. return NULL;
  693. }
  694. }
  695. if (lat->wszAfter != NULL)
  696. {
  697. newlat->wszAfter = Externwcsdup(lat->wszAfter);
  698. if (newlat->wszAfter == NULL)
  699. {
  700. ExternFree(newlat->wszBefore);
  701. ExternFree(newlat);
  702. return NULL;
  703. }
  704. }
  705. return newlat;
  706. }
  707. // Sets the guide structure in the lattice
  708. void SetLatticeGuide(LATTICE *lat, HWXGUIDE *pGuide)
  709. {
  710. ASSERT(lat!=NULL);
  711. ASSERT(pGuide!=NULL);
  712. lat->guide=*pGuide;
  713. lat->fUseGuide=TRUE;
  714. }
  715. // Sets the ALC settings for the lattice (only used in the lower level of the character recognizer,
  716. // if at all)
  717. void SetLatticeALCValid(LATTICE *lat, ALC alcValid)
  718. {
  719. ASSERT(lat!=NULL);
  720. lat->recogSettings.alcValid=alcValid;
  721. }
  722. // Sets the ALC settings for the lattice (only used in the lower level of the character recognizer,
  723. // if at all)
  724. void SetLatticeALCPriority(LATTICE *lat, ALC alcPriority)
  725. {
  726. ASSERT(lat!=NULL);
  727. lat->recogSettings.alcPriority=alcPriority;
  728. }
  729. // Get one character of the pre-context
  730. wchar_t GetPreContextChar(LATTICE *lat)
  731. {
  732. if (lat->wszBefore != NULL)
  733. {
  734. return lat->wszBefore[0];
  735. }
  736. return SYM_UNKNOWN;
  737. }
  738. // Get one character of the pre-context
  739. wchar_t GetPostContextChar(LATTICE *lat)
  740. {
  741. if (lat->wszAfter != NULL)
  742. {
  743. return lat->wszAfter[0];
  744. }
  745. return SYM_UNKNOWN;
  746. }
  747. // Destroy lattice data structure.
  748. void FreeLattice(LATTICE *lat)
  749. {
  750. ASSERT(lat!=NULL);
  751. if (lat->pStroke!=NULL) {
  752. int i;
  753. for (i=0; i<lat->nStrokes; i++) {
  754. FreeStroke(lat->pStroke+i);
  755. }
  756. ExternFree(lat->pStroke);
  757. }
  758. if (lat->pAltList!=NULL) ExternFree(lat->pAltList);
  759. ExternFree(lat->wszBefore);
  760. ExternFree(lat->wszAfter);
  761. if (lat->wszAnswer != NULL)
  762. {
  763. ExternFree(lat->wszAnswer);
  764. }
  765. ExternFree(lat->recogSettings.pbAllowedChars);
  766. ExternFree(lat->recogSettings.pbPriorityChars);
  767. ExternFree(lat->pbFactoidChars);
  768. FreeRecognizerCache(lat->pvCache);
  769. ExternFree(lat);
  770. }
  771. // Add a stroke to the lattice, returns TRUE if it succeeds.
  772. BOOL AddStrokeToLattice(LATTICE *lat, int nInk, POINT *pts, DWORD time)
  773. {
  774. int iStroke;
  775. ASSERT(nInk>0);
  776. ASSERT(lat!=NULL);
  777. if (lat->nStrokes+1>lat->nStrokesAllocated) {
  778. STROKE *pNewStroke;
  779. LATTICE_ALT_LIST *ppNewElem;
  780. int nNewAlloc=lat->nStrokesAllocated*2;
  781. if (nNewAlloc==0) nNewAlloc=32;
  782. pNewStroke=(STROKE*)ExternAlloc(sizeof(STROKE)*nNewAlloc);
  783. ppNewElem=(LATTICE_ALT_LIST*)ExternAlloc(sizeof(LATTICE_ALT_LIST)*nNewAlloc);
  784. if (pNewStroke==NULL || ppNewElem==NULL) {
  785. if (pNewStroke!=NULL) ExternFree(pNewStroke);
  786. if (ppNewElem!=NULL) ExternFree(ppNewElem);
  787. return FALSE;
  788. }
  789. for (iStroke=0; iStroke<nNewAlloc; iStroke++) ClearAltList(&ppNewElem[iStroke]);
  790. if (lat->nStrokesAllocated>0) {
  791. memcpy(pNewStroke,lat->pStroke,sizeof(STROKE)*lat->nStrokesAllocated);
  792. memcpy(ppNewElem,lat->pAltList,sizeof(LATTICE_ALT_LIST)*lat->nStrokesAllocated);
  793. ExternFree(lat->pStroke);
  794. ExternFree(lat->pAltList);
  795. }
  796. lat->nStrokesAllocated=nNewAlloc;
  797. lat->pStroke=pNewStroke;
  798. lat->pAltList=ppNewElem;
  799. }
  800. iStroke=lat->nStrokes;
  801. ClearAltList(lat->pAltList+iStroke);
  802. if (CreateStroke(lat->pStroke+iStroke,nInk,pts,time)) {
  803. lat->pStroke[iStroke].iOrder=lat->nRealStrokes;
  804. lat->pStroke[iStroke].iLast=lat->nRealStrokes;
  805. lat->nStrokes++;
  806. lat->nRealStrokes++;
  807. return TRUE;
  808. } else
  809. return FALSE;
  810. }
  811. // Pre-segmentation probability for single characters.
  812. BOOL CheapUnaryProb(int nStrokes, RECT bbox, int cSpace, int cArea, VOLCANO_WEIGHTS *pTuneScores)
  813. {
  814. float logProb;
  815. int iUnary;
  816. STROKE_SET_STATS stats;
  817. stats.rect=bbox;
  818. stats.space=cSpace;
  819. stats.area=cArea;
  820. iUnary=ComputeUnaryFeatures(&stats,nStrokes);
  821. logProb = (float)g_pProbTable->unarySamples[iUnary];
  822. pTuneScores->afl[VTUNE_FREE_SEG_UNIGRAM] = logProb;
  823. return (logProb > Log2Range);
  824. }
  825. // Pre-segmentation probability for character pairs
  826. BOOL CheapBinaryProb(int nStrokes, RECT bbox, int cSpace, int cArea,
  827. int nPrevStrokes, RECT prevBbox, int cPrevSpace, int cPrevArea,
  828. VOLCANO_WEIGHTS *pTuneScores)
  829. {
  830. float logProb;
  831. int iBinary;
  832. STROKE_SET_STATS stats, prevStats;
  833. stats.rect=bbox;
  834. stats.space=cSpace;
  835. stats.area=cArea;
  836. prevStats.rect=prevBbox;
  837. prevStats.space=cPrevSpace;
  838. prevStats.area=cPrevArea;
  839. iBinary=ComputeBinaryFeatures(&prevStats,&stats,nPrevStrokes,nStrokes);
  840. logProb=(float)g_pProbTable->binarySamples[iBinary];
  841. // Probability of 7/8 of moving to the right, 1/8 for other cases.
  842. if (prevBbox.right < bbox.right)
  843. logProb += FloatClippedLog2(7,8); else
  844. logProb += FloatClippedLog2(1,8);
  845. pTuneScores->afl[VTUNE_FREE_SEG_BIGRAM] = logProb;
  846. return (logProb > Log2Range);
  847. }
  848. // Post-segmentation probability for single characters
  849. void RecogUnaryProb(LATTICE *lat, int iStroke, int nStrokes, RECT bbox, wchar_t wChar,
  850. VOLCANO_WEIGHTS *pTuneScores, BOOL fStringMode)
  851. {
  852. #ifdef DISABLE_HEURISTICS
  853. return;
  854. #else
  855. float sizePenalty=0;
  856. wchar_t unicode=LocRunDense2Unicode(&g_locRunInfo,wChar);
  857. // If there's a guide, compute the probability of this character appearing at
  858. // a particular location in the box.
  859. if (lat->fUseGuide) {
  860. SCORE score;
  861. int stats[INKSTAT_ALL];
  862. float logprob;
  863. RECT guide=GetGuideDrawnBox(&lat->guide,lat->pStroke[iStroke].iBox);
  864. stats[INKSTAT_X] = bbox.left - guide.left;
  865. stats[INKSTAT_Y] = bbox.top - guide.top;
  866. stats[INKSTAT_CX] = (bbox.left + bbox.right + 1) / 2 - (guide.left + guide.right + 1) / 2;
  867. stats[INKSTAT_CY] = (bbox.top + bbox.bottom + 1) / 2 - (guide.top + guide.bottom + 1) / 2;
  868. stats[INKSTAT_W] = bbox.right - bbox.left;
  869. stats[INKSTAT_H] = bbox.bottom - bbox.top;
  870. stats[INKSTAT_BOX_W] = guide.right - guide.left;
  871. CentipedeNormalizeUnigram(stats);
  872. score=ShapeUnigramBoxCost(&g_centipedeInfo,wChar,stats);
  873. logprob= -(float)score / (float)SCORE_SCALE;
  874. pTuneScores->afl[fStringMode ? VTUNE_STRING_SHAPE_BOX_UNIGRAM : VTUNE_CHAR_SHAPE_BOX_UNIGRAM] = logprob;
  875. } else {
  876. // Otherwise compute the probability of this character having a particular
  877. // aspect ratio.
  878. SCORE score;
  879. int stats[INKSTAT_ALL];
  880. float logprob;
  881. stats[INKSTAT_W] = bbox.right-bbox.left;
  882. stats[INKSTAT_H] = bbox.bottom-bbox.top;
  883. score=ShapeUnigramFreeCost(&g_centipedeInfo,wChar,stats);
  884. logprob= -(float)score / (float)SCORE_SCALE;
  885. // We'd also like to include a bias so that small ink gives punctuation.
  886. pTuneScores->afl[VTUNE_FREE_SHAPE_UNIGRAM] = logprob;
  887. }
  888. #endif
  889. }
  890. // Post-segmentation for character pairs
  891. void RecogBinaryProb(LATTICE *lat,
  892. int iStroke, int nStrokes, RECT bbox, wchar_t wChar,
  893. int iPrevStroke, int nPrevStrokes, RECT prevBbox, wchar_t wPrevChar,
  894. VOLCANO_WEIGHTS *pTuneScores)
  895. {
  896. #ifdef DISABLE_HEURISTICS
  897. return;
  898. #else
  899. float sizePenalty=0;
  900. wchar_t unicode = LocRunDense2Unicode(&g_locRunInfo,wChar);
  901. wchar_t prevUnicode;
  902. float logprob=0;
  903. // If we had to generate a fake alternate because there are no viable alternates, then the
  904. // previous character might be one of these fakes. If we pass this fake through to Centipede,
  905. // there will be problems. So let's just use the unary shape scores instead.
  906. if (wPrevChar == SYM_UNKNOWN)
  907. {
  908. RecogUnaryProb(lat, iStroke, nStrokes, bbox,wChar, pTuneScores, TRUE);
  909. return;
  910. }
  911. prevUnicode = LocRunDense2Unicode(&g_locRunInfo,wPrevChar);
  912. if (lat->fUseGuide)
  913. {
  914. SCORE score;
  915. int prevStats[INKSTAT_ALL], stats[INKSTAT_ALL];
  916. RECT prevGuide=GetGuideDrawnBox(&lat->guide,lat->pStroke[iStroke-nStrokes].iBox);
  917. RECT guide=GetGuideDrawnBox(&lat->guide,lat->pStroke[iStroke].iBox);
  918. memset(prevStats,0,sizeof(int)*INKSTAT_ALL);
  919. prevStats[INKSTAT_X] = prevBbox.left - prevGuide.left;
  920. prevStats[INKSTAT_Y] = prevBbox.top - prevGuide.top;
  921. prevStats[INKSTAT_CX] = (prevBbox.left + prevBbox.right + 1) / 2 - (prevGuide.left + prevGuide.right + 1) / 2;
  922. prevStats[INKSTAT_CY] = (prevBbox.top + prevBbox.bottom + 1) / 2 - (prevGuide.top + prevGuide.bottom + 1) / 2;
  923. prevStats[INKSTAT_W] = prevBbox.right - prevBbox.left;
  924. prevStats[INKSTAT_H] = prevBbox.bottom - prevBbox.top;
  925. prevStats[INKSTAT_BOX_W] = prevGuide.right - prevGuide.left;
  926. prevStats[INKSTAT_MARGIN_W] = lat->guide.cxOffset;
  927. CentipedeNormalizeUnigram(prevStats);
  928. memset(stats,0,sizeof(int)*INKSTAT_ALL);
  929. stats[INKSTAT_X] = bbox.left - guide.left;
  930. stats[INKSTAT_Y] = bbox.top - guide.top;
  931. stats[INKSTAT_CX] = (bbox.left + bbox.right + 1) / 2 - (guide.left + guide.right + 1) / 2;
  932. stats[INKSTAT_CY] = (bbox.top + bbox.bottom + 1) / 2 - (guide.top + guide.bottom + 1) / 2;
  933. stats[INKSTAT_W] = bbox.right - bbox.left;
  934. stats[INKSTAT_H] = bbox.bottom - bbox.top;
  935. stats[INKSTAT_BOX_W] = guide.right - guide.left;
  936. stats[INKSTAT_MARGIN_W] = lat->guide.cxOffset;
  937. CentipedeNormalizeUnigram(stats);
  938. score = ShapeBigramBoxCost(&g_centipedeInfo,wPrevChar,wChar,prevStats,stats);
  939. logprob= -(float)score / (float)SCORE_SCALE;
  940. pTuneScores->afl[VTUNE_STRING_SHAPE_BOX_BIGRAM] = logprob;
  941. }
  942. else
  943. {
  944. int stats[INKBIN_ALL];
  945. SCORE score;
  946. stats[INKBIN_W_LEFT] = prevBbox.right - prevBbox.left;
  947. stats[INKBIN_W_GAP] = bbox.left - prevBbox.right;
  948. stats[INKBIN_W_RIGHT] = bbox.right - bbox.left;
  949. stats[INKBIN_H_LEFT] = prevBbox.bottom - prevBbox.top;
  950. stats[INKBIN_H_GAP] = bbox.top - prevBbox.bottom;
  951. stats[INKBIN_H_RIGHT] = bbox.bottom - bbox.top;
  952. CentipedeNormalizeBigram(stats);
  953. score = ShapeBigramFreeCost(&g_centipedeInfo, wPrevChar, wChar, stats);
  954. logprob = -(float)score / (float)SCORE_SCALE;
  955. pTuneScores->afl[VTUNE_FREE_SHAPE_BIGRAM] = logprob;
  956. }
  957. #endif
  958. }
  959. #ifdef TRAIN_ZILLA_HOUND_COMBINER // Pass on label when training combiner.
  960. wchar_t g_CurCharAnswer;
  961. #endif
  962. // Call the underlying recognizer, and get a list of alternates with
  963. // associated probability (research version or insurance version in free mode), or
  964. // scores (for the insurance version in boxed mode).
  965. int RecognizerProbs(LATTICE *lat, int iStroke, int nStrokes,
  966. RECT bbox,
  967. wchar_t wAlts[MaxAltsPerStroke],
  968. FLOAT valueAlts[MaxAltsPerStroke],
  969. FLOAT *pScore, int *pnZillaStrokes,
  970. VOLCANO_WEIGHTS *pTuneScores,
  971. BOOL fStringMode)
  972. {
  973. int i;
  974. int space=0;
  975. RECOG_ALT scoreAlts[MaxAltsPerStroke], probAlts[MaxAltsPerStroke];
  976. int nScoreAlts=0, nProbAlts=0;
  977. FLOAT probScore, score=0;
  978. RECT rGuide;
  979. SYM context;
  980. // If we have a guide, convert it into a RECT for the current box
  981. if (lat->fUseGuide) {
  982. rGuide = GetGuideDrawnBox(&lat->guide, lat->pStroke[iStroke].iBox);
  983. } else {
  984. int size;
  985. // If we don't have a guide, too bad, because we need one anyway.
  986. // We'll walk back through the lattice, averaging the box sizes along
  987. // the best path from the current node.
  988. int totalWidth = 0, totalHeight = 0, totalBoxes = 0;
  989. int currStroke = iStroke - nStrokes, iAlt, bestAlt = 0;
  990. BOOL fHavePrevBox = FALSE;
  991. RECT rPrevBox;
  992. // If there are more characters
  993. if (currStroke >= 0) {
  994. // Find the best alternate before the current character
  995. float bestProb = lat->pAltList[currStroke].alts[0].logProbPath;
  996. for (iAlt=1; iAlt<lat->pAltList[currStroke].nUsed; iAlt++) {
  997. if (lat->pAltList[currStroke].alts[iAlt].logProbPath > bestProb) {
  998. bestAlt = iAlt;
  999. bestProb = lat->pAltList[currStroke].alts[iAlt].logProbPath;
  1000. }
  1001. }
  1002. // If the previous character is on the same line, then record
  1003. if (bbox.left > lat->pAltList[currStroke].alts[bestAlt].writingBox.left)
  1004. {
  1005. rPrevBox = lat->pAltList[currStroke].alts[bestAlt].writingBox;
  1006. fHavePrevBox = TRUE;
  1007. }
  1008. // Follow the best path back through that alternate to the
  1009. // start of the lattice adding in the box sizes as we go.
  1010. while (currStroke >= 0)
  1011. {
  1012. int newStroke, newAlt;
  1013. RECT box = lat->pAltList[currStroke].alts[bestAlt].writingBox;
  1014. totalWidth += box.right-box.left;
  1015. totalHeight += box.bottom-box.top;
  1016. totalBoxes++;
  1017. newStroke = currStroke - lat->pAltList[currStroke].alts[bestAlt].nStrokes;
  1018. newAlt = lat->pAltList[currStroke].alts[bestAlt].iPrevAlt;
  1019. currStroke=newStroke;
  1020. bestAlt=newAlt;
  1021. }
  1022. }
  1023. // Get the maximum dimension
  1024. if (totalBoxes == 0)
  1025. {
  1026. size = max(bbox.right - bbox.left, bbox.bottom - bbox.top);
  1027. }
  1028. else
  1029. {
  1030. size = __max( totalWidth, totalHeight ) / totalBoxes;
  1031. }
  1032. // Set an arbitrary minimum on the box size to prevent divide by zeros
  1033. size = __max( size, 10 );
  1034. // If there was no previous box on the line
  1035. if (!fHavePrevBox)
  1036. {
  1037. // Then center the square box on top of the ink
  1038. rGuide.top = ( bbox.top + bbox.bottom - size ) / 2;
  1039. rGuide.bottom = ( bbox.top + bbox.bottom + size ) / 2;
  1040. rGuide.left = ( bbox.left + bbox.right - size ) / 2;
  1041. rGuide.right = ( bbox.left + bbox.right + size ) / 2;
  1042. }
  1043. else
  1044. {
  1045. // If there was a previous character on the same line, then try to
  1046. // keep it lined up.
  1047. // Center the box vertically with the previous character.
  1048. rGuide.top = (rPrevBox.top + rPrevBox.bottom - size) / 2;
  1049. rGuide.bottom = (rPrevBox.top + rPrevBox.bottom + size) / 2;
  1050. // Center the box horizontally
  1051. rGuide.left = ( bbox.left + bbox.right - size ) / 2;
  1052. rGuide.right = ( bbox.left + bbox.right + size ) / 2;
  1053. // If it overlaps with the previous box, then shift over to the right
  1054. if (rGuide.left - rPrevBox.right < 0)
  1055. {
  1056. rGuide.right += (rPrevBox.right - rGuide.left);
  1057. rGuide.left = rPrevBox.right;
  1058. }
  1059. // Expand to include the ink
  1060. rGuide.top = min(rGuide.top, bbox.top);
  1061. rGuide.bottom = max(rGuide.bottom, bbox.bottom);
  1062. rGuide.left = min(rGuide.left, bbox.left);
  1063. rGuide.right = max(rGuide.right, bbox.right);
  1064. }
  1065. }
  1066. // If this is the first character in the lattice, then pass the context through.
  1067. // Otherwise the context is unknown (at least at the single-character level
  1068. // the recognizer is working at).
  1069. if (iStroke-nStrokes+1==0)
  1070. context = GetPreContextChar(lat); else
  1071. context=SYM_UNKNOWN;
  1072. #ifdef USE_OLD_DATABASES
  1073. // Zero out the scores
  1074. for (i = 0; i < MaxAltsPerStroke; i++)
  1075. {
  1076. VTuneZeroWeights(pTuneScores + i);
  1077. }
  1078. #ifdef TRAIN_ZILLA_HOUND_COMBINER // Pass on label when training combiner.
  1079. g_CurCharAnswer = lat->wszAnswer[0];
  1080. #endif
  1081. RecognizeCharInsurance(&lat->recogSettings, nStrokes, lat->nRealStrokes, lat->pStroke + iStroke - nStrokes + 1,
  1082. &score, MaxAltsPerStroke, probAlts, &nProbAlts, scoreAlts, &nScoreAlts, &rGuide, context, &space, pTuneScores,
  1083. fStringMode, lat->fProbMode, lat->pvCache, iStroke);
  1084. #else
  1085. nProbAlts=RecognizeChar(&lat->recogSettings, nStrokes, lat->nRealStrokes, lat->pStroke + iStroke - nStrokes + 1,
  1086. &score,MaxAltsPerStroke,probAlts,&rGuide,&space);
  1087. #endif
  1088. if (pScore!=NULL) *pScore=score;
  1089. if (pnZillaStrokes!=NULL) *pnZillaStrokes=space;
  1090. // If we're in probability mode, return the probabilities
  1091. if (lat->fProbMode) {
  1092. if (nProbAlts<=0) return 0;
  1093. // Zero out the scores
  1094. for (i = 0; i < MaxAltsPerStroke; i++)
  1095. {
  1096. VTuneZeroWeights(pTuneScores + i);
  1097. }
  1098. // The probability that this is a character given the matcher score.
  1099. // This code is disabled for the old databases, because the otter space
  1100. // is not returned. For the new databases, we get space numbers for both
  1101. // otter and zilla, so the match score can be interpreted meaningfully.
  1102. #ifdef USE_OLD_DATABASES
  1103. probScore=0;
  1104. #else
  1105. probScore=0;
  1106. // probScore=g_pProbTable->scoreSamples[MatchSpaceScoreToFeature(nStrokes,score,space)];
  1107. #endif
  1108. for (i=0; i<nProbAlts; i++) {
  1109. #ifdef USE_OLD_DATABASES
  1110. double logProbAlt = FloatClippedLog2((int)probAlts[i].prob,65535);
  1111. #else
  1112. double logProbAlt = -(double)probAlts[i].prob/(double)SCORE_SCALE;
  1113. ASSERT(SCORE_BASE==2);
  1114. #endif
  1115. wAlts[i]=probAlts[i].wch;
  1116. valueAlts[i]=(FLOAT)(probScore+logProbAlt);
  1117. if (!lat->fUseGuide)
  1118. {
  1119. pTuneScores[i].afl[VTUNE_FREE_PROB] = valueAlts[i];
  1120. }
  1121. else if (fStringMode)
  1122. {
  1123. pTuneScores[i].afl[VTUNE_STRING_CORE] = valueAlts[i];
  1124. }
  1125. else
  1126. {
  1127. pTuneScores[i].afl[VTUNE_CHAR_CORE] = valueAlts[i];
  1128. }
  1129. }
  1130. return nProbAlts;
  1131. } else {
  1132. // Otherwise return the matcher scores to emulate the legacy recognizer
  1133. #ifndef USE_OLD_DATABASES
  1134. ASSERT(("Trying to use matcher scores with the new recognizer",0));
  1135. #endif
  1136. if (nScoreAlts<=0) return 0;
  1137. for (i=0; i<nScoreAlts; i++) {
  1138. wAlts[i]=scoreAlts[i].wch;
  1139. valueAlts[i]=scoreAlts[i].prob;
  1140. }
  1141. return nScoreAlts;
  1142. }
  1143. }
  1144. #define PENALTY_ALC_PRIORITY 1000
  1145. // Sort the alt list so that all characters matching alcPriority are above
  1146. // all those that do not.
  1147. void ApplyALCPriority(LATTICE *lat, int nAlts, wchar_t *wAlts, VOLCANO_WEIGHTS *pTuneScores)
  1148. {
  1149. wchar_t wTmp[MaxAltsPerStroke];
  1150. VOLCANO_WEIGHTS aTuneScoresTmp[MaxAltsPerStroke];
  1151. FLOAT penalty;
  1152. BOOL fPenaltySet = FALSE;
  1153. int iUsed = 0, i;
  1154. BOOL fNonPriorityFound = FALSE, fPriorityBelowNonPriority = FALSE;
  1155. CHARSET charSet;
  1156. charSet.recmask = lat->recogSettings.alcValid;
  1157. charSet.recmaskPriority = lat->recogSettings.alcPriority;
  1158. charSet.pbAllowedChars = lat->recogSettings.pbAllowedChars;
  1159. charSet.pbPriorityChars = lat->recogSettings.pbPriorityChars;
  1160. // If there are no priorities, don't change anything.
  1161. if (charSet.recmaskPriority == 0 && charSet.pbPriorityChars == NULL) return;
  1162. // If there are no alternates, don't do anything.
  1163. if (nAlts == 0) return;
  1164. // Scan through the alt list and copy all the priority characters into the
  1165. // temporary list.
  1166. for (i = 0; i < nAlts; i ++) {
  1167. if (IsPriorityChar(&g_locRunInfo, &charSet, wAlts[i]))
  1168. {
  1169. wTmp[iUsed] = wAlts[i];
  1170. aTuneScoresTmp[iUsed] = pTuneScores[i];
  1171. iUsed ++;
  1172. if (fNonPriorityFound) {
  1173. fPriorityBelowNonPriority = TRUE;
  1174. }
  1175. } else {
  1176. fNonPriorityFound = TRUE;
  1177. }
  1178. }
  1179. // If there weren't any priority characters, then return
  1180. if (iUsed == 0) return;
  1181. // If there aren't any priority characters listed below non-priority ones
  1182. if (!fPriorityBelowNonPriority) return;
  1183. // Scan through the alt list and copy all the non-priority characters into the
  1184. // temporary list.
  1185. for (i = 0; i < nAlts; i ++) {
  1186. if (!IsPriorityChar(&g_locRunInfo, &charSet, wAlts[i]))
  1187. {
  1188. // If the penalty has not been set yet.
  1189. if (!fPenaltySet) {
  1190. penalty = (VTuneComputeScore(&g_vtuneInfo.pTune->weights, &(aTuneScoresTmp[iUsed - 1])) -
  1191. VTuneComputeScore(&g_vtuneInfo.pTune->weights, &(pTuneScores[i])) - PENALTY_ALC_PRIORITY);
  1192. if (g_vtuneInfo.pTune->weights.afl[VTUNE_UNIGRAM] > 0)
  1193. {
  1194. penalty /= g_vtuneInfo.pTune->weights.afl[VTUNE_UNIGRAM];
  1195. }
  1196. fPenaltySet = TRUE;
  1197. }
  1198. wTmp[iUsed] = wAlts[i];
  1199. aTuneScoresTmp[iUsed] = pTuneScores[i];
  1200. aTuneScoresTmp[iUsed].afl[VTUNE_UNIGRAM] += penalty;
  1201. iUsed ++;
  1202. }
  1203. }
  1204. ASSERT(iUsed == nAlts);
  1205. // Then copy the temp lists back to the output
  1206. for (i = 0; i < nAlts; i ++) {
  1207. wAlts[i] = wTmp[i];
  1208. pTuneScores[i] = aTuneScoresTmp[i];
  1209. }
  1210. }
  1211. // See if two rectangles overlap one another
  1212. BOOL IsOverlapped(RECT r1, RECT r2)
  1213. {
  1214. if (r1.left>r2.right || r1.right<r2.left || r1.top>r2.bottom || r1.bottom<r2.top)
  1215. return FALSE;
  1216. return TRUE;
  1217. }
  1218. // See if the given bounding box overlaps with any previous character in the
  1219. // best path, starting from alternate iPrevAlt at iPrevStroke.
  1220. BOOL IsOverlappedPath(LATTICE *lat, int iPrevStroke, int iPrevAlt, RECT bbox)
  1221. {
  1222. while (iPrevStroke!=-1) {
  1223. LATTICE_ELEMENT *elem=lat->pAltList[iPrevStroke].alts+iPrevAlt;
  1224. if (IsOverlapped(elem->bbox,bbox)) return TRUE;
  1225. iPrevStroke -= elem->nStrokes;
  1226. iPrevAlt = elem->iPrevAlt;
  1227. }
  1228. return FALSE;
  1229. }
  1230. // Penalties used in separator mode
  1231. #define PENALTY_SKIP_INK 200
  1232. #define PENALTY_SKIP_PROMPT 200
  1233. #define PENALTY_OVERLAP_CHAR 300
  1234. #define PENALTY_OVERLAP 100
  1235. // Given the proposed next character to be added to the lattice, and the next available prompt character,
  1236. // is the proposed character allowed? The character is allowed if is either the "SYM_UNKNOWN"
  1237. // character or a character which appears later in the string. If this required skipping prompt
  1238. // characters, or the character is SYM_UNKNOWN, then a penalty is also returned. If fEndInput is set
  1239. // then all characters remaining in the prompt are consumed (skipped) with a cost. If the proposed
  1240. // character is not allowed, -1 is returned. Otherwise, we return the next available prompt character.
  1241. int IsAllowedNextChar(LATTICE *lat, wchar_t wchNext, int iAnswerPtr, BOOL fEndInput, float *pflCost)
  1242. {
  1243. BOOL fMatched = FALSE;
  1244. *pflCost = 0;
  1245. // If we're not in separator mode, don't do anything.
  1246. if (!lat->fSepMode)
  1247. {
  1248. return 0;
  1249. }
  1250. // If this character is unknown
  1251. if (wchNext == SYM_UNKNOWN)
  1252. {
  1253. // At end of input, skip all remaining prompt characters
  1254. if (fEndInput)
  1255. {
  1256. while (lat->wszAnswer[iAnswerPtr] != 0)
  1257. {
  1258. *pflCost += -PENALTY_SKIP_PROMPT;
  1259. iAnswerPtr++;
  1260. }
  1261. }
  1262. // Return a pointer to the end of the prompt
  1263. return iAnswerPtr;
  1264. }
  1265. // Convert the dense code to unicode for comparison with the prompt
  1266. wchNext = LocRunDense2Unicode(&g_locRunInfo, wchNext);
  1267. // While we haven't reached the end of the prompt and haven't found a match,
  1268. // skip a prompt character and add a penalty
  1269. while (lat->wszAnswer[iAnswerPtr] != 0 && lat->wszAnswer[iAnswerPtr] != wchNext)
  1270. {
  1271. iAnswerPtr++;
  1272. *pflCost += -PENALTY_SKIP_PROMPT;
  1273. }
  1274. // If we didn't find a match, return failure.
  1275. if (lat->wszAnswer[iAnswerPtr] != wchNext)
  1276. {
  1277. return -1;
  1278. }
  1279. // Found a match
  1280. iAnswerPtr++;
  1281. // Now add a penalty for any unmatched characters at the end of the input.
  1282. if (fEndInput)
  1283. {
  1284. while (lat->wszAnswer[iAnswerPtr] != 0)
  1285. {
  1286. *pflCost += -PENALTY_SKIP_PROMPT;
  1287. iAnswerPtr++;
  1288. }
  1289. }
  1290. // Return the prompt pointer.
  1291. return iAnswerPtr;
  1292. }
  1293. // Sometimes the algorithm will not produce any alternates for a given stroke. This is bad, because it could
  1294. // result in a disconnected lattice. The solution is to produce an alternate with the lowest possible
  1295. // probability and a character code of SYM_UNKNOWN. This will never get used if there is another choice,
  1296. // but if there is no other choice, the user might see it. It will get translated to a SYM_UNKNOWN when returned
  1297. // to the user.
  1298. void FakeRecogResult(LATTICE *lat, int iStroke, int nStrokes, float logProb)
  1299. {
  1300. int i, width, height;
  1301. // Create the fake alternate, which is always alternate zero.
  1302. LATTICE_ELEMENT elem;
  1303. elem.iPathLength = 1;
  1304. elem.fCurrentPath=FALSE;
  1305. elem.fUsed=TRUE;
  1306. elem.maxDist=0;
  1307. elem.nPrevStrokes=0;
  1308. elem.nStrokes=nStrokes;
  1309. elem.logProb=logProb;
  1310. elem.logProbPath=logProb;
  1311. elem.iPrevAlt=-1;
  1312. elem.score=0;
  1313. elem.wChar=SYM_UNKNOWN;
  1314. elem.wPrevChar=0;
  1315. elem.iPromptPtr = 0;
  1316. #ifdef HWX_TUNE
  1317. VTuneZeroWeights(&elem.tuneScores);
  1318. elem.tuneScores.afl[VTUNE_FREE_PROB] = logProb;
  1319. #endif
  1320. // Build a bounding box for this fake character.
  1321. elem.bbox = lat->pStroke[iStroke].bbox;
  1322. for (i = iStroke-nStrokes+1; i < iStroke; i ++) {
  1323. RECT other = lat->pStroke[i].bbox;
  1324. elem.bbox.left = __min(elem.bbox.left, other.left);
  1325. elem.bbox.right = __max(elem.bbox.right, other.right);
  1326. elem.bbox.top = __min(elem.bbox.top, other.top);
  1327. elem.bbox.bottom = __max(elem.bbox.bottom, other.bottom);
  1328. }
  1329. // Copy bounding box to the writing box
  1330. elem.writingBox = elem.bbox;
  1331. // If the box is more than 4 times wider than high ...
  1332. width = elem.writingBox.right - elem.writingBox.left;
  1333. height = elem.writingBox.bottom - elem.writingBox.top;
  1334. if (width / 4 > height) {
  1335. // ... increase the height by width / 4
  1336. elem.writingBox.top -= width / 8;
  1337. elem.writingBox.bottom += width / 8;
  1338. }
  1339. // If this is not the first character in the lattice, then hook it up to the best previous
  1340. // alternate.
  1341. if (iStroke-nStrokes>-1) {
  1342. LATTICE_ELEMENT *pPrevElem;
  1343. int iAlt;
  1344. elem.iPrevAlt=0;
  1345. for (iAlt=1; iAlt<lat->pAltList[iStroke-nStrokes].nUsed; iAlt++) {
  1346. if (lat->pAltList[iStroke-nStrokes].alts[elem.iPrevAlt].logProbPath <
  1347. lat->pAltList[iStroke-nStrokes].alts[iAlt].logProbPath) {
  1348. elem.iPrevAlt=iAlt;
  1349. }
  1350. }
  1351. pPrevElem = &lat->pAltList[iStroke-nStrokes].alts[elem.iPrevAlt];
  1352. elem.iPathLength = pPrevElem->iPathLength + 1;
  1353. elem.wPrevChar = pPrevElem->wChar;
  1354. elem.logProbPath = (pPrevElem->logProbPath * pPrevElem->iPathLength + logProb) / elem.iPathLength;
  1355. elem.iPromptPtr = pPrevElem->iPromptPtr;
  1356. #ifdef HWX_TUNE
  1357. {
  1358. int iParam;
  1359. for (iParam = 0; iParam < VTUNE_NUM_WEIGHTS; iParam++)
  1360. {
  1361. elem.tuneScores.afl[iParam] =
  1362. (elem.tuneScores.afl[iParam] +
  1363. pPrevElem->tuneScores.afl[iParam] * pPrevElem->iPathLength) /
  1364. elem.iPathLength;
  1365. }
  1366. }
  1367. #endif
  1368. }
  1369. InsertElement(lat, iStroke, &elem);
  1370. }
  1371. // Go through the pre-segmentation lattice, and run the recognizer on the top alternates of the
  1372. // given column. The alternates will get replaced with ones containing actual characters.
  1373. void BuildRecogAlts(LATTICE *lat, int iStroke, BOOL fStringMode)
  1374. {
  1375. VOLCANO_WEIGHTS aTuneScores[MaxAltsPerStroke];
  1376. LATTICE_ALT_LIST bboxAlts;
  1377. int iAlt;
  1378. ASSERT(lat!=NULL);
  1379. ASSERT(iStroke>=0 && iStroke<lat->nStrokes);
  1380. // Make a copy of the current alt-list.
  1381. memcpy(&bboxAlts,&lat->pAltList[iStroke],sizeof(LATTICE_ALT_LIST));
  1382. // Clear out the alt-list in the lattice
  1383. ClearAltList(&lat->pAltList[iStroke]);
  1384. // For each segmentation lattice
  1385. for (iAlt=0; iAlt<bboxAlts.nUsed; iAlt++) {
  1386. // We continue only if either there is more space in the alt list, or if it is filled, only
  1387. // if there is a chance that the alternates we are going to insert will have a high enough
  1388. // probability to get on to the list.
  1389. if (1 /*lat->pAltList[iStroke].nUsed<MaxAltsPerStroke ||
  1390. bboxAlts.alts[iAlt].logProbPath>lat->pAltList[iStroke].alts[lat->pAltList[iStroke].nUsed-1].logProbPath*/) {
  1391. int nZillaStrokes;
  1392. wchar_t wAlts[MaxAltsPerStroke];
  1393. FLOAT probAlts[MaxAltsPerStroke];
  1394. FLOAT score;
  1395. float flCost;
  1396. // Run the recognizer, get scores/probabilities back
  1397. int nRecogAlts=RecognizerProbs(lat,iStroke,bboxAlts.alts[iAlt].nStrokes,bboxAlts.alts[iAlt].bbox,
  1398. wAlts,probAlts,&score,&nZillaStrokes, aTuneScores, fStringMode);
  1399. int iRecogAlt;
  1400. // In separator mode, add on an "unknown character" at the end of the alt list
  1401. if (lat->fSepMode)
  1402. {
  1403. if (nRecogAlts < MaxAltsPerStroke)
  1404. {
  1405. wAlts[nRecogAlts] = SYM_UNKNOWN;
  1406. aTuneScores[nRecogAlts].afl[VTUNE_FREE_PROB] = -PENALTY_SKIP_INK;
  1407. if (nRecogAlts > 0)
  1408. {
  1409. aTuneScores[nRecogAlts].afl[VTUNE_FREE_PROB] +=
  1410. aTuneScores[nRecogAlts - 1].afl[VTUNE_FREE_PROB];
  1411. }
  1412. nRecogAlts++;
  1413. }
  1414. }
  1415. // Make sure we don't try to apply a priority during tuning.
  1416. #ifdef HWX_TUNE
  1417. if (g_pTuneFile == NULL)
  1418. #endif
  1419. {
  1420. ApplyALCPriority(lat, nRecogAlts, wAlts, aTuneScores);
  1421. }
  1422. // For reach alternate from the recognizer
  1423. for (iRecogAlt=0; iRecogAlt<nRecogAlts; iRecogAlt++)
  1424. {
  1425. LATTICE_ELEMENT elem;
  1426. wchar_t unicode = (wAlts[iRecogAlt] != SYM_UNKNOWN) ? LocRunDense2Unicode(&g_locRunInfo,wAlts[iRecogAlt]) : SYM_UNKNOWN;
  1427. // Set up the alternate
  1428. elem.fUsed=TRUE;
  1429. elem.fCurrentPath=FALSE;
  1430. elem.wChar=wAlts[iRecogAlt];
  1431. elem.wPrevChar=0;
  1432. elem.nStrokes=bboxAlts.alts[iAlt].nStrokes;
  1433. elem.nPrevStrokes=0;
  1434. elem.iPrevAlt=-1;
  1435. elem.space=bboxAlts.alts[iAlt].space;
  1436. elem.area=bboxAlts.alts[iAlt].area;
  1437. elem.bbox=bboxAlts.alts[iAlt].bbox;
  1438. elem.writingBox=GuessWritingBox(elem.bbox,elem.wChar);
  1439. elem.maxDist=bboxAlts.alts[iAlt].maxDist;
  1440. elem.score=score;
  1441. #ifdef USE_IFELANG3_BIGRAMS
  1442. elem.nBigrams=0;
  1443. #endif
  1444. // If this alternate is the first character in the lattice, then it
  1445. // gets handled differently.
  1446. if (iStroke-elem.nStrokes+1==0)
  1447. {
  1448. int iAnswerPtr = IsAllowedNextChar(lat, elem.wChar, 0,
  1449. ((iStroke == lat->nStrokes - 1) && lat->fEndInput), &flCost);
  1450. if (iAnswerPtr >= 0)
  1451. {
  1452. elem.iPromptPtr = iAnswerPtr;
  1453. aTuneScores[iRecogAlt].afl[VTUNE_FREE_PROB] += flCost;
  1454. // We can only apply the language model when we are not in
  1455. // separator mode, and we know what character this is.
  1456. if (!lat->fSepMode && elem.wChar != SYM_UNKNOWN)
  1457. {
  1458. if (lat->fUseLM)
  1459. {
  1460. // Apply the language model
  1461. LanguageModelBigram(
  1462. lat->fProbMode,
  1463. FALSE,
  1464. !lat->fUseGuide,
  1465. elem.wChar,
  1466. elem.nStrokes,
  1467. GetPreContextChar(lat),
  1468. aTuneScores + iRecogAlt);
  1469. // If we are at the end of the input, use the post-context if it is available
  1470. if (lat->fEndInput && iStroke == lat->nStrokes - 1 && GetPostContextChar(lat) != SYM_UNKNOWN)
  1471. {
  1472. LanguageModelBigram(
  1473. lat->fProbMode,
  1474. TRUE,
  1475. !lat->fUseGuide,
  1476. GetPostContextChar(lat),
  1477. elem.nStrokes,
  1478. elem.wChar,
  1479. aTuneScores + iRecogAlt);
  1480. }
  1481. }
  1482. // Centipede only makes sense when the full character is written.
  1483. // Also we may want to allow this in separator mode once it is
  1484. // trained on free input data.
  1485. if (lat->recogSettings.partialMode == HWX_PARTIAL_ALL)
  1486. {
  1487. RecogUnaryProb(lat, iStroke, elem.nStrokes, elem.bbox, elem.wChar, aTuneScores + iRecogAlt, fStringMode);
  1488. }
  1489. }
  1490. // If in free mode, also add in the pre-segmentation probabilities.
  1491. // We don't care about segmentation in word mode.
  1492. if (!lat->fWordMode && !lat->fUseGuide)
  1493. {
  1494. CheapUnaryProb(elem.nStrokes, elem.bbox, elem.space, elem.area, aTuneScores + iRecogAlt);
  1495. }
  1496. #ifdef HWX_TUNE
  1497. // Record values for tuning
  1498. elem.tuneScores = aTuneScores[iRecogAlt];
  1499. #endif
  1500. // Apply tuning parameters
  1501. elem.logProb = VTuneComputeScoreNoLM(&g_vtuneInfo.pTune->weights, aTuneScores + iRecogAlt);
  1502. // Combined score includes the language model probability
  1503. elem.logProbPath = VTuneComputeScore(&g_vtuneInfo.pTune->weights, aTuneScores + iRecogAlt);
  1504. elem.iPathLength = 1;
  1505. // Put it in the lattice
  1506. InsertElement(lat, iStroke, &elem);
  1507. }
  1508. }
  1509. else
  1510. {
  1511. BOOL first=TRUE;
  1512. int iPrevAlt;
  1513. // If there are previous characters in the lattice, then we loop over each previous
  1514. // alternate, and use bigram probabilities. The best alternate is recorded, and
  1515. // the probability associated with the best alternate is stored with the current
  1516. // alternate. This essentially does a Viterbi search.
  1517. for (iPrevAlt=0; iPrevAlt<lat->pAltList[iStroke-elem.nStrokes].nUsed; iPrevAlt++)
  1518. {
  1519. LATTICE_ELEMENT *prevElem=&lat->pAltList[iStroke-elem.nStrokes].alts[iPrevAlt];
  1520. int iAnswerPtr = IsAllowedNextChar(lat, elem.wChar, prevElem->iPromptPtr,
  1521. ((iStroke == lat->nStrokes - 1) && lat->fEndInput), &flCost);
  1522. if (iAnswerPtr >= 0)
  1523. {
  1524. float prob, probPath;
  1525. wchar_t prevUnicode =
  1526. (prevElem->wChar == SYM_UNKNOWN) ?
  1527. SYM_UNKNOWN : LocRunDense2Unicode(&g_locRunInfo,prevElem->wChar);
  1528. VOLCANO_WEIGHTS altTuneScores = aTuneScores[iRecogAlt];
  1529. ASSERT(prevElem->fUsed);
  1530. // If we're in free mode and this choice of a previous alternate would cause
  1531. // an overlap, then don't consider it.
  1532. // Don't use this heuristic in separator mode, because sometimes the characters
  1533. // really do overlap and we want to be able to learn that in the future.
  1534. if (!lat->fSepMode && !lat->fUseGuide &&
  1535. IsOverlappedPath(lat, iStroke - elem.nStrokes, iPrevAlt, elem.bbox))
  1536. {
  1537. continue;
  1538. }
  1539. // Only use the language model if we are not in separator mode,
  1540. // and we know what the current character is.
  1541. if (!lat->fSepMode && elem.wChar != SYM_UNKNOWN)
  1542. {
  1543. if (lat->fUseLM)
  1544. {
  1545. // If we're in boxed mode, and the boxes are consequtive, then
  1546. // use bigrams as the language model, otherwise use a unigram
  1547. if (!lat->fUseGuide || lat->pStroke[iStroke].iBox-1 == lat->pStroke[iStroke-elem.nStrokes].iBox)
  1548. {
  1549. LanguageModelBigram(
  1550. lat->fProbMode,
  1551. TRUE,
  1552. !lat->fUseGuide,
  1553. elem.wChar,
  1554. elem.nStrokes,
  1555. prevElem->wChar,
  1556. &altTuneScores);
  1557. }
  1558. else
  1559. {
  1560. LanguageModelBigram(lat->fProbMode,
  1561. FALSE,
  1562. !lat->fUseGuide,
  1563. elem.wChar,
  1564. elem.nStrokes,
  1565. SYM_UNKNOWN,
  1566. &altTuneScores);
  1567. }
  1568. // If we are at the end of the input, use the post-context if it is present
  1569. if (lat->fEndInput && iStroke == lat->nStrokes - 1 && GetPostContextChar(lat) != SYM_UNKNOWN)
  1570. {
  1571. LanguageModelBigram(
  1572. lat->fProbMode,
  1573. TRUE,
  1574. !lat->fUseGuide,
  1575. GetPostContextChar(lat),
  1576. elem.nStrokes,
  1577. elem.wChar,
  1578. &altTuneScores);
  1579. }
  1580. }
  1581. // Centipede only makes sense when the whole character is written
  1582. // Also we may want to allow this in separator mode once it is
  1583. // trained on free input data.
  1584. if (lat->recogSettings.partialMode == HWX_PARTIAL_ALL)
  1585. {
  1586. RecogBinaryProb(lat, iStroke, elem.nStrokes, elem.bbox, elem.wChar,
  1587. iStroke-elem.nStrokes, prevElem->nStrokes, prevElem->bbox, prevElem->wChar,
  1588. &altTuneScores);
  1589. }
  1590. }
  1591. // In free mode, also include the pre-segmentation probabilities
  1592. // We don't care about segmentation in word mode
  1593. if (!lat->fWordMode && !lat->fUseGuide)
  1594. {
  1595. BOOL fSegOkay = CheapBinaryProb(elem.nStrokes, elem.bbox, elem.space, elem.area,
  1596. prevElem->nStrokes, prevElem->bbox, prevElem->space, prevElem->area,
  1597. &altTuneScores);
  1598. // If we're not in separator mode, then prune based on the
  1599. // segmentation score
  1600. if (!lat->fSepMode && !fSegOkay)
  1601. {
  1602. continue;
  1603. }
  1604. // In separator mode, add a penalty for overlapping characters, rather than
  1605. // making a hard decision as is done during normal recognition.
  1606. if (lat->fSepMode && IsOverlappedPath(lat, iStroke - elem.nStrokes, iPrevAlt, elem.bbox))
  1607. {
  1608. altTuneScores.afl[VTUNE_FREE_SEG_BIGRAM] +=
  1609. ((elem.wChar == SYM_UNKNOWN) ? -PENALTY_OVERLAP : -PENALTY_OVERLAP_CHAR);
  1610. }
  1611. }
  1612. // Add in the prompt matching cost temporarily
  1613. altTuneScores.afl[VTUNE_FREE_PROB] += flCost;
  1614. // Apply tuning parameters
  1615. prob = VTuneComputeScoreNoLM(&g_vtuneInfo.pTune->weights, &altTuneScores);
  1616. // Combined score includes the language model probability
  1617. probPath = (VTuneComputeScore(&g_vtuneInfo.pTune->weights, &altTuneScores) +
  1618. prevElem->logProbPath * prevElem->iPathLength) / (prevElem->iPathLength + 1);
  1619. // If we found a better previous alternate, update.
  1620. if (/*probPath > Log2Range &&*/ (first || probPath > elem.logProbPath))
  1621. {
  1622. #ifdef HWX_TUNE
  1623. int iParam;
  1624. for (iParam = 0; iParam < VTUNE_NUM_WEIGHTS; iParam++)
  1625. {
  1626. elem.tuneScores.afl[iParam] =
  1627. (altTuneScores.afl[iParam] +
  1628. prevElem->tuneScores.afl[iParam] * prevElem->iPathLength) /
  1629. (prevElem->iPathLength + 1);
  1630. }
  1631. #endif
  1632. elem.logProb=prob;
  1633. elem.logProbPath=probPath;
  1634. elem.iPrevAlt=iPrevAlt;
  1635. elem.nPrevStrokes=prevElem->nStrokes;
  1636. elem.wPrevChar=prevElem->wChar;
  1637. elem.iPathLength = prevElem->iPathLength + 1;
  1638. elem.iPromptPtr = iAnswerPtr;
  1639. first=FALSE;
  1640. #ifdef USE_IFELANG3_BIGRAMS
  1641. elem.bigramLogProbs[elem.nBigrams]=prob;
  1642. elem.bigramAlts[elem.nBigrams]=iPrevAlt;
  1643. elem.nBigrams++;
  1644. #endif
  1645. }
  1646. }
  1647. // put the alternate in the lattice
  1648. if (!first) InsertElement(lat,iStroke,&elem);
  1649. }
  1650. }
  1651. }
  1652. }
  1653. }
  1654. }
  1655. // This function does assignment of strokes to boxes, and tries to keep ink going into
  1656. // consequtive sequences of boxes, even if it is not written in quite the right place.
  1657. // This seems to make a difference on the test set.
  1658. void GetBoxIndexSINFO(LATTICE *lat, STROKE *pStroke, int *pixBoxPrev, int *piyBoxPrev)
  1659. {
  1660. #define CYBASE_FUDGE 4
  1661. XY xy;
  1662. int ixBox, iyBox, iyBoxBiased;
  1663. RECT *rect;
  1664. HWXGUIDE *lpguide = &lat->guide;
  1665. ASSERT(lpguide);
  1666. ASSERT(lpguide->cHorzBox > 0);
  1667. ASSERT(lpguide->cVertBox > 0);
  1668. rect = &pStroke->bbox;
  1669. // Center of the stroke bounding box horizontally
  1670. xy.x = (rect->left + rect->right) / 2;
  1671. // 1/4 down from the top of the stroke's bounding box
  1672. xy.y = (rect->top * (CYBASE_FUDGE - 1) + rect->bottom) / CYBASE_FUDGE;
  1673. // Box number in X is just where xy.x falls in the guide.
  1674. ixBox = (xy.x - lpguide->xOrigin) / (int)lpguide->cxBox;
  1675. if (ixBox < 0)
  1676. ixBox = 0;
  1677. else if (ixBox > (int)lpguide->cHorzBox - 1)
  1678. ixBox = lpguide->cHorzBox - 1;
  1679. // Same for Y
  1680. iyBox = (xy.y - lpguide->yOrigin) / (int)lpguide->cyBox;
  1681. if (iyBox < 0)
  1682. iyBox = 0;
  1683. else if (iyBox > (int)lpguide->cVertBox - 1)
  1684. iyBox = lpguide->cVertBox - 1;
  1685. // If we have a previous stroke's Y box number
  1686. if (*piyBoxPrev >= 0)
  1687. {
  1688. // Help dots, accents, etc land on their respective boxes...
  1689. if (iyBox < *piyBoxPrev)
  1690. {
  1691. // If the stroke seems to be in a higher box,
  1692. // shift the "hot spot" down 1/2 of the remaining distance to the
  1693. // bottom of the box
  1694. xy.y = (rect->bottom + xy.y) / 2;
  1695. // compute the new Y box number
  1696. iyBoxBiased = (xy.y - lpguide->yOrigin) / (int)lpguide->cyBox;
  1697. if (iyBoxBiased > (int)(lpguide->cVertBox - 1))
  1698. iyBoxBiased = lpguide->cVertBox - 1;
  1699. else if (iyBoxBiased < 0)
  1700. iyBoxBiased = 0;
  1701. // if this one matches the previous stroke, then keep it
  1702. if (iyBoxBiased == *piyBoxPrev)
  1703. iyBox = iyBoxBiased;
  1704. }
  1705. else if (iyBox > *piyBoxPrev && ixBox >= *pixBoxPrev)
  1706. {
  1707. // If the stroke seems to be in a lower box,
  1708. // shift the "hot spot" up 1/2 of the remaining distance to the
  1709. // top of the box
  1710. xy.y = (rect->top + xy.y) / 2;
  1711. // compute the new Y box number
  1712. iyBoxBiased = (xy.y - lpguide->yOrigin) / (int)lpguide->cyBox;
  1713. if (iyBoxBiased > (int)(lpguide->cVertBox - 1))
  1714. iyBoxBiased = lpguide->cVertBox - 1;
  1715. else if (iyBoxBiased < 0)
  1716. iyBoxBiased = 0;
  1717. // if this one matches the previous stroke, then keep it
  1718. if (iyBoxBiased == *piyBoxPrev)
  1719. iyBox = iyBoxBiased;
  1720. }
  1721. }
  1722. // Return the result
  1723. *pixBoxPrev = ixBox;
  1724. *piyBoxPrev = iyBox;
  1725. pStroke->iBox = ixBox + (iyBox * lpguide->cHorzBox);
  1726. }
  1727. // This function contained my old algorithm for assigning strokes to boxes, which
  1728. // has now been commented out to call the above routine from Tsunami.
  1729. void AssignStrokeToBox(LATTICE *lat, STROKE *pStroke, int *pixBoxPrev, int *piyBoxPrev)
  1730. {
  1731. #if 0
  1732. int iInk;
  1733. int totX=0, totY=0;
  1734. int boxX, boxY;
  1735. ASSERT(lat!=NULL);
  1736. ASSERT(pStroke!=NULL);
  1737. ASSERT(pStroke->nInk>0);
  1738. if (pStroke->iBox != -1) return;
  1739. for (iInk = 0; iInk < pStroke->nInk; iInk++) {
  1740. totX += pStroke->pts[iInk].x;
  1741. totY += pStroke->pts[iInk].y;
  1742. }
  1743. totX /= pStroke->nInk;
  1744. totY /= pStroke->nInk;
  1745. boxX = (totX - lat->guide.xOrigin) / lat->guide.cxBox;
  1746. boxY = (totY - lat->guide.yOrigin) / lat->guide.cyBox;
  1747. if (boxX < 0) boxX=0;
  1748. if (boxX >= (int)lat->guide.cHorzBox) boxX = lat->guide.cHorzBox - 1;
  1749. if (boxY < 0) boxY=0;
  1750. if (boxY >= (int)lat->guide.cVertBox) boxY = lat->guide.cVertBox - 1;
  1751. pStroke->iBox = boxX + boxY * lat->guide.cHorzBox;
  1752. #endif
  1753. GetBoxIndexSINFO(lat,pStroke,pixBoxPrev,piyBoxPrev);
  1754. }
  1755. // Function to compare the ordering of two strokes in qsort. Used to put
  1756. // the strokes back in their original order if re-processing is requested.
  1757. int __cdecl CompareStrokes(const void *p1, const void *p2)
  1758. {
  1759. STROKE *pStroke1=(STROKE*)p1;
  1760. STROKE *pStroke2=(STROKE*)p2;
  1761. ASSERT(pStroke1!=NULL);
  1762. ASSERT(pStroke2!=NULL);
  1763. // Boxes are in numerical order
  1764. if (pStroke1->iBox < pStroke2->iBox) return -1;
  1765. if (pStroke1->iBox > pStroke2->iBox) return 1;
  1766. // Within a box, strokes are in time order.
  1767. if (pStroke1->iOrder < pStroke2->iOrder) return -1;
  1768. if (pStroke1->iOrder > pStroke2->iOrder) return 1;
  1769. // Two strokes with the same iOrder values shouldn't occur
  1770. ASSERT(0);
  1771. return 0;
  1772. }
  1773. #ifdef MERGE_STROKES
  1774. // This code merges adjacent strokes with one another, if the end point of
  1775. // one is close enough to the starting point of the next. Note that this
  1776. // code works on the whole list of strokes at once; it is possible to do
  1777. // this incrementally also, by keeping track of the last stroke which was
  1778. // added.
  1779. void MergeStrokes(LATTICE *lat)
  1780. {
  1781. int iStroke, iDest;
  1782. // First put the strokes back into time order, and clear the alt lists
  1783. /* for (iStroke=0; iStroke<lat->nStrokes; iStroke++) {
  1784. lat->pStroke[iStroke].iBox=-1;
  1785. ClearAltList(lat->pAltList+iStroke);
  1786. }
  1787. qsort(lat->pStroke,lat->nStrokes,sizeof(STROKE),CompareStrokes); */
  1788. // Make sure we have some more strokes to process
  1789. if (lat->nProcessed == lat->nStrokes)
  1790. return;
  1791. // Then go through and merge strokes, starting at the newly added ones.
  1792. iDest=lat->nProcessed;
  1793. for (iStroke=lat->nProcessed+1; iStroke<lat->nStrokes; iStroke++) {
  1794. BOOL fMerged=FALSE;
  1795. POINT ptFirst=lat->pStroke[iDest].pts[lat->pStroke[iDest].nInk-1];
  1796. POINT ptLast=lat->pStroke[iStroke].pts[0];
  1797. int dx=(ptFirst.x-ptLast.x);
  1798. int dy=(ptFirst.y-ptLast.y);
  1799. int dist=dx*dx+dy*dy;
  1800. // fprintf(stderr,"%d(%d,%d-%d,%d),",dist,ptFirst.x,ptFirst.y,ptLast.x,ptLast.y);
  1801. if (dist<=MERGE_THRESHOLD*MERGE_THRESHOLD) {
  1802. // Merge the strokes, and free up the one that was merged in
  1803. fMerged=AddStrokeToStroke(lat->pStroke+iDest,lat->pStroke+iStroke);
  1804. // fprintf(stderr,"X,");
  1805. // fprintf(stderr,"(%d->%d)",iStroke,iDest);
  1806. }
  1807. if (fMerged) {
  1808. // If we successfully merged, then free the merged stroke.
  1809. FreeStroke(lat->pStroke+iStroke);
  1810. } else {
  1811. // If we didn't merge, just copy the stroke
  1812. iDest++;
  1813. if (iDest<iStroke)
  1814. lat->pStroke[iDest]=lat->pStroke[iStroke];
  1815. }
  1816. }
  1817. // fprintf(stderr,"\n");
  1818. // Record the new number of strokes
  1819. lat->nStrokes=iDest+1;
  1820. }
  1821. #endif
  1822. // Assign the strokes to boxes, then sort the strokes in order by their box numbers.
  1823. // Note that the alternate lists are invalidated by this (since they depend on stroke
  1824. // order), so the alt lists are cleared and must be recomputed.
  1825. void SortStrokesByGuide(LATTICE *lat)
  1826. {
  1827. int iStroke, ixBoxPrev=-1, iyBoxPrev=-1;
  1828. ASSERT(lat!=NULL);
  1829. ASSERT(lat->fUseGuide);
  1830. // If we have any unprocessed strokes left to assign
  1831. if (lat->nStrokes-lat->nProcessed>0) {
  1832. int iEarliestTouched = -1;
  1833. // Assign the strokes to boxes and clear out their alt lists,
  1834. // and record the lowest box number which has been touched.
  1835. for (iStroke=lat->nProcessed; iStroke<lat->nStrokes; iStroke++)
  1836. {
  1837. AssignStrokeToBox(lat,lat->pStroke+iStroke,&ixBoxPrev,&iyBoxPrev);
  1838. if (iEarliestTouched == -1 || lat->pStroke[iStroke].iBox < iEarliestTouched)
  1839. {
  1840. iEarliestTouched = lat->pStroke[iStroke].iBox;
  1841. }
  1842. }
  1843. // If the writer touched a box which has already been processed, then
  1844. // we will need to do some reprocessing.
  1845. if (lat->nProcessed > 0)
  1846. {
  1847. if (lat->pStroke[lat->nProcessed - 1].iBox >= iEarliestTouched)
  1848. {
  1849. // Figure out how far back we actually need to process. This is done
  1850. // by scanning the lattice to find the boundary for the earliest touched box.
  1851. lat->nProcessed = 0;
  1852. for (iStroke = 0; iStroke < lat->nStrokes; iStroke++)
  1853. {
  1854. if (lat->pStroke[iStroke].iBox >= iEarliestTouched)
  1855. {
  1856. lat->nProcessed = iStroke;
  1857. break;
  1858. }
  1859. }
  1860. }
  1861. }
  1862. // Sort the strokes into box order
  1863. qsort(lat->pStroke + lat->nProcessed,
  1864. lat->nStrokes - lat->nProcessed,
  1865. sizeof(STROKE), CompareStrokes);
  1866. }
  1867. }
  1868. // Count the number of distinct boxes that are used in the ink
  1869. int CountBoxes(LATTICE *lat)
  1870. {
  1871. int iStroke;
  1872. int nBoxes=0;
  1873. int prevBox=-1;
  1874. for (iStroke=0; iStroke<lat->nStrokes; iStroke++) {
  1875. if (prevBox!=lat->pStroke[iStroke].iBox) nBoxes++;
  1876. prevBox=lat->pStroke[iStroke].iBox;
  1877. }
  1878. return nBoxes;
  1879. }
  1880. // Determine if the given stroke number iEnd is the valid end of a character.
  1881. BOOL CandidateEnd(LATTICE *lat, int iEnd)
  1882. {
  1883. // If we're on the last stroke seen so far, then we don't know for sure that
  1884. // it is the end of a character until we get the end of input signal.
  1885. if (iEnd==lat->nStrokes-1) {
  1886. return TRUE;
  1887. /*
  1888. if (lat->fEndInput)
  1889. return TRUE; else
  1890. return FALSE;
  1891. */
  1892. }
  1893. // If we're not at the last stroke, just check if the box numbers change.
  1894. if (lat->pStroke[iEnd].iBox!=lat->pStroke[iEnd+1].iBox) return TRUE;
  1895. return FALSE;
  1896. }
  1897. // Mark the "best path so far", ie the path through the lattice which will not change
  1898. // if more input is added. This only works in boxed mode.
  1899. void FindIncrementalPath(LATTICE *lat)
  1900. {
  1901. int iStroke, iAlt;
  1902. int iStartAlt, iStartStroke = lat->nProcessed - 1;
  1903. if (iStartStroke >= 0) {
  1904. int nPaths = lat->pAltList[iStartStroke].nUsed;
  1905. ASSERT(lat->fUseGuide);
  1906. // First zero out the hit counts in each lattice element which doesn't have
  1907. // its result determined yet.
  1908. for (iStroke = lat->nFixedResult; iStroke < lat->nProcessed; iStroke++)
  1909. for (iAlt = 0; iAlt < lat->pAltList[iStroke].nUsed; iAlt++)
  1910. lat->pAltList[iStroke].alts[iAlt].nHits = 0;
  1911. // Then trace back all the paths from the current end of lattice to the
  1912. // point at which all paths converge, counting the number of times each
  1913. // element occurs on a path.
  1914. for (iStartAlt = 0; iStartAlt < nPaths; iStartAlt++) {
  1915. iStroke = iStartStroke;
  1916. iAlt = iStartAlt;
  1917. while (iStroke >= lat->nFixedResult) {
  1918. LATTICE_ELEMENT *elem = lat->pAltList[iStroke].alts + iAlt;
  1919. elem->nHits++;
  1920. iStroke -= elem->nStrokes;
  1921. iAlt = elem->iPrevAlt;
  1922. }
  1923. }
  1924. // All path elements that were hit by all nPaths paths are part of the
  1925. // current path.
  1926. for (iStroke = lat->nFixedResult; iStroke < lat->nProcessed; iStroke++)
  1927. for (iAlt = 0; iAlt < lat->pAltList[iStroke].nUsed; iAlt++) {
  1928. if (lat->pAltList[iStroke].alts[iAlt].nHits == nPaths) {
  1929. lat->pAltList[iStroke].alts[iAlt].fCurrentPath = TRUE;
  1930. lat->nFixedResult = iStroke + 1;
  1931. } else
  1932. lat->pAltList[iStroke].alts[iAlt].fCurrentPath = FALSE;
  1933. }
  1934. }
  1935. }
  1936. // Mark the best path through the lattice (assuming we're at the end of the input),
  1937. // and return the number of characters along that path.
  1938. int FindFullPath(LATTICE *lat)
  1939. {
  1940. int nChars=0;
  1941. int iStroke, iAlt;
  1942. // Wipe out the current path
  1943. for (iStroke = 0; iStroke < lat->nStrokes; iStroke++)
  1944. for (iAlt = 0; iAlt < lat->pAltList[iStroke].nUsed; iAlt++)
  1945. lat->pAltList[iStroke].alts[iAlt].fCurrentPath = FALSE;
  1946. // Trace back from the best path
  1947. iStroke = lat->nProcessed - 1;
  1948. iAlt = 0;
  1949. while (iStroke!=-1) {
  1950. int iNextStroke, iNextAlt;
  1951. if (lat->pAltList[iStroke].nUsed==0) {
  1952. ASSERT(lat->pAltList[iStroke].nUsed!=0);
  1953. iStroke--;
  1954. iAlt=0;
  1955. continue;
  1956. }
  1957. lat->pAltList[iStroke].alts[iAlt].fCurrentPath=TRUE;
  1958. nChars++;
  1959. iNextStroke=iStroke-lat->pAltList[iStroke].alts[iAlt].nStrokes;
  1960. iNextAlt=lat->pAltList[iStroke].alts[iAlt].iPrevAlt;
  1961. iStroke=iNextStroke;
  1962. iAlt=iNextAlt;
  1963. }
  1964. return nChars;
  1965. }
  1966. // This routine is the analogy of ProcessLattice for the boxed mode case. Since we have
  1967. // the boxes, the segmentation problem is "deterministic". We create the lattice with
  1968. // the minimal set of pre-segmentation lattice elements.
  1969. void CreateLatticeForGuide(LATTICE *lat)
  1970. {
  1971. int cContextBefore = 0, cContextAfter = 0;
  1972. int iStroke;
  1973. int iPrevEnd;
  1974. BOOL fStringMode;
  1975. ASSERT(lat!=NULL);
  1976. #ifdef MERGE_STROKES
  1977. // First run the stroke merging algorithm
  1978. MergeStrokes(lat);
  1979. #endif
  1980. // Assign the strokes to boxes, and put them in the appropriate order. Note that
  1981. // this also clears the alternate lists for the strokes.
  1982. SortStrokesByGuide(lat);
  1983. if (lat->wszBefore != NULL)
  1984. cContextBefore = wcslen(lat->wszBefore);
  1985. if (lat->wszAfter != NULL)
  1986. cContextAfter = wcslen(lat->wszAfter);
  1987. fStringMode = (CountBoxes(lat) + cContextBefore + cContextAfter > 1);
  1988. #ifdef USE_IFELANG3
  1989. // Decide whether to use IFELang3. The criterion is that we can't be doing incremental
  1990. // processing, we must have IFELang3 available, and we must have at least MIN_CHARS_FOR_IFELANG3 boxes
  1991. // of ink plus context. There is a special case for one character of ink, in which we require that
  1992. // both the pre- and post-context each have at least MIN_CONTEXT_FOR_IFELANG3 characters.
  1993. if (/* !lat->fIncremental && */ LatticeIFELang3Available())
  1994. {
  1995. int cChars = CountBoxes(lat);
  1996. if ((cChars == 1 && cContextBefore >= MIN_CONTEXT_FOR_IFELANG3 && cContextAfter >= MIN_CONTEXT_FOR_IFELANG3) ||
  1997. (cChars > 1 && cChars + cContextBefore + cContextAfter >= MIN_CHARS_FOR_IFELANG3))
  1998. {
  1999. lat->fUseIFELang3 = TRUE;
  2000. }
  2001. else
  2002. {
  2003. lat->fUseIFELang3 = FALSE;
  2004. }
  2005. } else {
  2006. lat->fUseIFELang3=FALSE;
  2007. }
  2008. // { FILE *f=fopen("/ife.log","w"); fprintf(f,"%s %d %d %d\n",(lat->fUseIFELang3?"YES":"NO"),cContextBefore,cContextAfter,CountBoxes(lat)); fclose(f); }
  2009. #else
  2010. lat->fUseIFELang3=FALSE;
  2011. #endif
  2012. #ifdef USE_OLD_DATABASES
  2013. if (lat->fUseIFELang3)
  2014. lat->fProbMode=TRUE; else
  2015. lat->fProbMode=FALSE;
  2016. // fprintf(stderr,"prob=%d, ife=%d, guide=%d\n",lat->fProbMode,lat->fUseIFELang3,lat->fUseGuide);
  2017. // lat->fProbMode=FALSE;
  2018. // lat->fUseIFELang3=FALSE;
  2019. lat->fProbMode = FALSE;
  2020. #else
  2021. lat->fProbMode=TRUE;
  2022. #endif
  2023. iPrevEnd = lat->nProcessed-1;
  2024. // For each stroke
  2025. for (iStroke=lat->nProcessed; iStroke<lat->nStrokes; iStroke++)
  2026. {
  2027. ClearAltList(lat->pAltList + iStroke);
  2028. // If this stroke is the end of a box, then create an alternate
  2029. if (CandidateEnd(lat,iStroke)) {
  2030. // Create the alternate, filling in all the features
  2031. int i;
  2032. LATTICE_ELEMENT *elem=&(lat->pAltList[iStroke].alts[0]);
  2033. RECT bbox;
  2034. // INTERVALS xIntervals, yIntervals;
  2035. bbox=lat->pStroke[iStroke].bbox;
  2036. // EmptyIntervals(&xIntervals,bbox.left,bbox.right);
  2037. // EmptyIntervals(&yIntervals,bbox.top,bbox.bottom);
  2038. for (i=iPrevEnd+1; i<=iStroke; i++) {
  2039. RECT other=lat->pStroke[i].bbox;
  2040. bbox.left=__min(bbox.left,other.left);
  2041. bbox.right=__max(bbox.right,other.right);
  2042. bbox.top=__min(bbox.top,other.top);
  2043. bbox.bottom=__max(bbox.bottom,other.bottom);
  2044. // ExpandIntervalsRange(&xIntervals,other.left,other.right);
  2045. // ExpandIntervalsRange(&yIntervals,other.top,other.bottom);
  2046. // RemoveInterval(&xIntervals,other.left,other.right);
  2047. // RemoveInterval(&yIntervals,other.top,other.bottom);
  2048. }
  2049. lat->pAltList[iStroke].nUsed=1;
  2050. elem->fCurrentPath=FALSE;
  2051. elem->fUsed=TRUE;
  2052. elem->iPrevAlt=0;
  2053. if (iPrevEnd==-1) elem->iPrevAlt=-1;
  2054. elem->maxDist=0;
  2055. elem->nPrevStrokes=0;
  2056. elem->nStrokes=iStroke-iPrevEnd;
  2057. elem->logProb=0;
  2058. elem->logProbPath=0;
  2059. elem->score=0;
  2060. elem->wChar=0;
  2061. elem->wPrevChar=0;
  2062. elem->area=0; //TotalRange(&xIntervals)+TotalRange(&yIntervals);
  2063. elem->space=0; //TotalPresent(&xIntervals)+TotalPresent(&yIntervals);
  2064. elem->bbox=bbox;
  2065. elem->iPathLength = 1;
  2066. // Convert this alternate to a list of recognition alternates.
  2067. BuildRecogAlts(lat, iStroke, fStringMode);
  2068. if (lat->pAltList[iStroke].nUsed==0) {
  2069. // Well, the recognizer came back and send there were no candidates.
  2070. // Let's put in a SYM_UNKNOWN so the user will see something, and also so
  2071. // the language model will have a path to work with.
  2072. // fprintf(stderr,"Lost a character in boxed mode (box %d).\n",lat->pStroke[iStroke].iBox);
  2073. FakeRecogResult(lat,iStroke,iStroke-iPrevEnd,0);
  2074. }
  2075. // fprintf(stderr,"Found %d\n",lat->pStroke[iStroke].iBox);
  2076. iPrevEnd=iStroke;
  2077. // Record that we have processed up to this point, so that we don't
  2078. // redo any work.
  2079. lat->nProcessed = iStroke+1;
  2080. }
  2081. }
  2082. // if (!lat->fEndInput)
  2083. // FindIncrementalPath(lat); else
  2084. FindFullPath(lat);
  2085. }
  2086. //******************************************************************************
  2087. //***** Time Bomb code
  2088. //******************************************************************************
  2089. // If there is no setting from the project, then by default turn the time bomb on.
  2090. #ifndef TIME_BOMB
  2091. #define TIME_BOMB 0
  2092. #endif
  2093. #if TIME_BOMB
  2094. #include <stdio.h>
  2095. #define TIME_BOMB_MONTH 4
  2096. #define TIME_BOMB_DAY 10
  2097. #define TIME_BOMB_YEAR 2000
  2098. #define TIME_BOMB_NUM_WARNING_DAYS 14
  2099. BOOL TimeBombExpired()
  2100. {
  2101. SYSTEMTIME st;
  2102. FILETIME ft;
  2103. DWORD dwCurrent, dwExpired;
  2104. // Get the expired day (relative to January 1, 1601)
  2105. ZeroMemory(&st, sizeof(st));
  2106. st.wMonth = TIME_BOMB_MONTH;
  2107. st.wDay = TIME_BOMB_DAY;
  2108. st.wYear = TIME_BOMB_YEAR;
  2109. SystemTimeToFileTime(&st, &ft);
  2110. dwExpired = (DWORD)(*(DWORDLONG*)&ft / (DWORDLONG)864000000000);
  2111. // Get the current day (relative to January 1, 1601)
  2112. GetLocalTime(&st);
  2113. SystemTimeToFileTime(&st, &ft);
  2114. dwCurrent = (DWORD)(*(DWORDLONG*)&ft / (DWORDLONG)864000000000);
  2115. // Check to see if we have expired.
  2116. if (dwCurrent >= dwExpired)
  2117. {
  2118. static BOOL fWarned = FALSE;
  2119. if (!fWarned)
  2120. {
  2121. MessageBox(NULL, L"This beta has expired. Please install our updated beta.",
  2122. L"Expiration", MB_ICONWARNING);
  2123. fWarned = TRUE;
  2124. }
  2125. return TRUE;
  2126. }
  2127. // Check to see if we are in the warning period.
  2128. if ((dwCurrent + TIME_BOMB_NUM_WARNING_DAYS) >= dwExpired)
  2129. {
  2130. static BOOL fWarned = FALSE;
  2131. if (!fWarned)
  2132. {
  2133. wchar_t wszMsg[256];
  2134. swprintf(wszMsg, L"This beta expires in %d day%s. Please install our updated beta.",
  2135. (int)(dwExpired - dwCurrent), (dwExpired - dwCurrent == 1) ? L"" : L"s");
  2136. MessageBox(NULL, wszMsg, L"Expiration", MB_ICONWARNING);
  2137. fWarned = TRUE;
  2138. }
  2139. }
  2140. return FALSE;
  2141. }
  2142. #endif // TIME_BOMB
  2143. // Figure out which strokes are valid end of character in paths that span the
  2144. // whole lattice.
  2145. static BOOL CheckIfValid(LATTICE *lat, int iStartStroke, int iStroke, BYTE *pValidEnd)
  2146. {
  2147. LATTICE_ALT_LIST *pAlts;
  2148. int ii;
  2149. BOOL fValidPath;
  2150. // Have we already checked this position?
  2151. if (pValidEnd[iStroke] == LCF_INVALID) {
  2152. return FALSE;
  2153. } else if (pValidEnd[iStroke] == LCF_VALID) {
  2154. return TRUE;
  2155. }
  2156. // Assume no valid path, until proved otherwise.
  2157. fValidPath = FALSE;
  2158. // Process each element ending at this position in lattice.
  2159. pAlts = lat->pAltList + iStroke;
  2160. for (ii = 0; ii < pAlts->nUsed; ++ii) {
  2161. LATTICE_ELEMENT *pElem;
  2162. int prevEnd;
  2163. pElem = pAlts->alts + ii;
  2164. prevEnd = iStroke - pElem->nStrokes;
  2165. ASSERT(prevEnd >= -1);
  2166. if (prevEnd == iStartStroke - 1) {
  2167. // Reached start.
  2168. fValidPath = TRUE;
  2169. } else if (CheckIfValid(lat, iStartStroke, prevEnd, pValidEnd)) {
  2170. // Have path to start.
  2171. fValidPath = TRUE;
  2172. }
  2173. }
  2174. // Did one (or more) paths reach the start?
  2175. if (fValidPath) {
  2176. pValidEnd[iStroke] = LCF_VALID;
  2177. return TRUE;
  2178. } else {
  2179. pValidEnd[iStroke] = LCF_INVALID;
  2180. return FALSE;
  2181. }
  2182. }
  2183. void PruneLattice(LATTICE *lat, int iStartStroke, int iEndStroke)
  2184. {
  2185. // Figure out valid character transition points by stroke. We don't want to include
  2186. // pieces of lattice that don't actually connect up for the full run. This uses
  2187. // a recursive algorithm to find valid paths. Since it marks the paths as it
  2188. // finds them, it can quickly test and not redo work.
  2189. int size = sizeof(BYTE) * (iEndStroke + 1);
  2190. int iStroke;
  2191. BYTE *pValidEnd = (BYTE *)ExternAlloc(size);
  2192. // If we failed to allocate memory, just return
  2193. if (pValidEnd == NULL) return;
  2194. memset(pValidEnd, LCF_UNKNOWN, size);
  2195. CheckIfValid(lat, iStartStroke, iEndStroke, pValidEnd);
  2196. for (iStroke = iStartStroke; iStroke <= iEndStroke; iStroke++)
  2197. {
  2198. if (pValidEnd[iStroke] != LCF_VALID)
  2199. {
  2200. lat->pAltList[iStroke].nUsed = 0;
  2201. }
  2202. }
  2203. ExternFree(pValidEnd);
  2204. }
  2205. // Evaluate all the elements on the best path. If any of them is a member of the big/small kana
  2206. // pair, and the other element of that pair is also present, then switch the best path element
  2207. // based on the position criterion.
  2208. static void BigSmallKanaHack(LATTICE *lat)
  2209. {
  2210. int iStroke, iAlt;
  2211. // List of all the small characters
  2212. static wchar_t *wszSmallKana = L"copsuvwxz\x3041\x3043\x3045\x3047\x3049\x3083\x3085\x3087\x308e\x30a1\x30a3\x30a5\x30a7\x30a9\x30e3\x30e5\x30e7\x30ee\x30f5\x30f6";
  2213. // Corresponding list of all the big characters
  2214. static wchar_t *wszBigKana = L"COPSUVWXZ\x3042\x3044\x3046\x3048\x304a\x3084\x3086\x3088\x308f\x30a2\x30a4\x30a6\x30a8\x30aa\x30e4\x30e6\x30e8\x30ef\x30ab\x30b1";
  2215. // Threshold on the fraction down from the top of the box below which
  2216. // we always classify as a large character.
  2217. static float aflBigThreshold[] = {
  2218. (float) 17.910447761194,
  2219. (float) 17.5257731958763,
  2220. (float) 9.33333333333333,
  2221. (float) 12,
  2222. (float) 14.8648648648649,
  2223. (float) 12.1621621621622,
  2224. (float) 13.4328358208955,
  2225. (float) 11,
  2226. (float) 15.2777777777778,
  2227. (float) 16.5532879818594,
  2228. (float) 30.1666666666667,
  2229. (float) 17.5,
  2230. (float) 15.6666666666667,
  2231. (float) 33.5,
  2232. (float) 16,
  2233. (float) 15.4639175257732,
  2234. (float) 15.4639175257732,
  2235. (float) 24.6666666666667,
  2236. (float) 21.5,
  2237. (float) 13.4020618556701,
  2238. (float) 10.3092783505155,
  2239. (float) 25,
  2240. (float) 15.4639175257732,
  2241. (float) 17.5257731958763,
  2242. (float) 27.536231884058,
  2243. (float) 23.3560090702948,
  2244. (float) 27,
  2245. (float) 4.21052631578947,
  2246. (float) 12.3711340206186,
  2247. };
  2248. // Threshold on the fraction down from the top of the box below which
  2249. // we always classify as a small character.
  2250. static float aflSmallThreshold[] = {
  2251. (float) 58.1081081081081,
  2252. (float) 57.7319587628866,
  2253. (float) 50.5154639175258,
  2254. (float) 51.5463917525773,
  2255. (float) 54.0540540540541,
  2256. (float) 56.7164179104478,
  2257. (float) 54.639175257732,
  2258. (float) 54.0540540540541,
  2259. (float) 64.8148148148148,
  2260. (float) 50.7246376811594,
  2261. (float) 67.0103092783505,
  2262. (float) 50.5154639175258,
  2263. (float) 50.7246376811594,
  2264. (float) 52.6315789473684,
  2265. (float) 54.1666666666667,
  2266. (float) 51.5789473684211,
  2267. (float) 54.639175257732,
  2268. (float) 53.6082474226804,
  2269. (float) 55.6701030927835,
  2270. (float) 52.5773195876289,
  2271. (float) 46.3917525773196,
  2272. (float) 61.9565217391304,
  2273. (float) 49.4845360824742,
  2274. (float) 53.6666666666667,
  2275. (float) 64.9484536082474,
  2276. (float) 57.1666666666667,
  2277. (float) 55.6701030927835,
  2278. (float) 55.6701030927835,
  2279. (float) 50.5154639175258,
  2280. };
  2281. // Sanity checks on the data tables above
  2282. ASSERT(wcslen(wszBigKana) == wcslen(wszSmallKana));
  2283. ASSERT(wcslen(wszBigKana) == sizeof(aflSmallThreshold) / sizeof(float));
  2284. ASSERT(wcslen(wszBigKana) == sizeof(aflBigThreshold) / sizeof(float));
  2285. // For each stroke
  2286. for (iStroke = 0; iStroke < lat->nStrokes; iStroke++)
  2287. {
  2288. RECT rInk, rGuide;
  2289. float flPosition;
  2290. wchar_t *pwchBig, *pwchSmall, wch, wchOther;
  2291. int iBestAlt = -1, iOtherAlt = -1;
  2292. // Scan through the alt list looking for a node on the current path
  2293. for (iAlt = 0; iAlt < lat->pAltList[iStroke].nUsed; iAlt++)
  2294. {
  2295. if (lat->pAltList[iStroke].alts[iAlt].fCurrentPath)
  2296. {
  2297. iBestAlt = iAlt;
  2298. break;
  2299. }
  2300. }
  2301. if (iBestAlt == -1)
  2302. {
  2303. continue;
  2304. }
  2305. // If we found one, look to see if it is in a big/small pair
  2306. wch = LocRunDense2Unicode(&g_locRunInfo, lat->pAltList[iStroke].alts[iAlt].wChar);
  2307. pwchBig = wcschr(wszBigKana, wch);
  2308. pwchSmall = wcschr(wszSmallKana, wch);
  2309. if (pwchBig == NULL && pwchSmall == NULL)
  2310. {
  2311. continue;
  2312. }
  2313. // If so, scan the alt list for another character with the same number of
  2314. // strokes which is the other member of the pair.
  2315. if (pwchBig == NULL)
  2316. {
  2317. wchOther = wszBigKana[pwchSmall - wszSmallKana];
  2318. }
  2319. else
  2320. {
  2321. wchOther = wszSmallKana[pwchBig - wszBigKana];
  2322. }
  2323. for (iAlt = 0; iAlt < lat->pAltList[iStroke].nUsed; iAlt++)
  2324. {
  2325. if (lat->pAltList[iStroke].alts[iAlt].nStrokes == lat->pAltList[iStroke].alts[iBestAlt].nStrokes &&
  2326. LocRunDense2Unicode(&g_locRunInfo, lat->pAltList[iStroke].alts[iAlt].wChar) == wchOther)
  2327. {
  2328. iOtherAlt = iAlt;
  2329. break;
  2330. }
  2331. }
  2332. if (iOtherAlt == -1)
  2333. {
  2334. continue;
  2335. }
  2336. // If found, then compute the position of the top of the ink in the writing box.
  2337. rInk = lat->pAltList[iStroke].alts[iAlt].bbox;
  2338. rGuide = GetGuideDrawnBox(&lat->guide, lat->pStroke[iStroke].iBox);
  2339. flPosition = 100 * (float) (rInk.top - rGuide.top) / (float) max(1, rGuide.bottom - rGuide.top);
  2340. #if 0
  2341. {
  2342. FILE *f = fopen("c:/boxes.log", "a");
  2343. fprintf(f, "top %5d start %5d end %5d bottom %5d fract %f\n",
  2344. rGuide.top, rInk.top, rInk.bottom, rGuide.bottom, flPosition);
  2345. fclose(f);
  2346. }
  2347. #endif
  2348. // Apply the thresholds.
  2349. if ((pwchBig != NULL && flPosition > aflSmallThreshold[pwchBig - wszBigKana]) ||
  2350. (pwchSmall != NULL && flPosition < aflBigThreshold[pwchSmall - wszSmallKana]))
  2351. {
  2352. lat->pAltList[iStroke].alts[iBestAlt].fCurrentPath = FALSE;
  2353. lat->pAltList[iStroke].alts[iOtherAlt].fCurrentPath = TRUE;
  2354. }
  2355. }
  2356. }
  2357. // Update the probabilities in the lattice, including setting current
  2358. // path to the most likely path so far (including language model).
  2359. // Can be called repeatedly for incremental processing after each stroke.
  2360. BOOL ProcessLattice(LATTICE *lat, BOOL fEndInput)
  2361. {
  2362. int iStroke, iAlt, nChars;
  2363. //DebugBreak();
  2364. ASSERT(lat!=NULL);
  2365. #if TIME_BOMB
  2366. if (TimeBombExpired())
  2367. {
  2368. return FALSE;
  2369. }
  2370. #endif
  2371. lat->fEndInput = fEndInput;
  2372. if (!fEndInput)
  2373. {
  2374. lat->fIncremental = TRUE;
  2375. }
  2376. if (lat->nStrokes==0)
  2377. {
  2378. return TRUE;
  2379. }
  2380. if (lat->fUseFactoid)
  2381. {
  2382. // Figure out which parameters we're updating
  2383. if (lat->fCoerceMode)
  2384. {
  2385. lat->recogSettings.alcValid = lat->alcFactoid;
  2386. lat->recogSettings.pbAllowedChars = CopyAllowedChars(&g_locRunInfo, lat->pbFactoidChars);
  2387. }
  2388. else
  2389. {
  2390. lat->recogSettings.alcPriority = lat->alcFactoid;
  2391. lat->recogSettings.pbPriorityChars = CopyAllowedChars(&g_locRunInfo, lat->pbFactoidChars);
  2392. }
  2393. }
  2394. if (lat->fUseGuide)
  2395. {
  2396. CreateLatticeForGuide(lat);
  2397. }
  2398. else
  2399. {
  2400. // We're in free mode, so create the cache
  2401. if (lat->pvCache == NULL)
  2402. {
  2403. lat->pvCache = AllocateRecognizerCache();
  2404. }
  2405. // Always use probability mode for free input
  2406. lat->fProbMode=TRUE;
  2407. // Word mode processing
  2408. if (lat->fWordMode)
  2409. {
  2410. for (iStroke = 0; iStroke < lat->nStrokes; iStroke++)
  2411. {
  2412. lat->pAltList[iStroke].nUsed = 0;
  2413. }
  2414. // Create a placeholder for the correct segmentation.
  2415. FakeRecogResult(lat, lat->nStrokes - 1, lat->nStrokes, 0);
  2416. BuildRecogAlts(lat, lat->nStrokes - 1, TRUE);
  2417. if (lat->pAltList[lat->nStrokes - 1].nUsed==0)
  2418. {
  2419. // Well, the recognizer came back and said there were no candidates.
  2420. // Let's put in a dummy alt so the user will see something, and also so
  2421. // the language model will have a path to work with.
  2422. // fprintf(stderr,"Lost a stroke in free mode.\n");
  2423. FakeRecogResult(lat, lat->nStrokes - 1, lat->nStrokes, -PENALTY_SKIP_INK);
  2424. }
  2425. // Indicate how much processing has been done so far
  2426. lat->nProcessed = lat->nStrokes;
  2427. }
  2428. else
  2429. {
  2430. // For each stroke in the lattice
  2431. for (iStroke = lat->nProcessed; iStroke < lat->nStrokes; iStroke++)
  2432. {
  2433. int maxDist=0;
  2434. int nChar1;
  2435. RECT bbox;
  2436. INTERVALS xIntervals, yIntervals;
  2437. // Wipe out the current path
  2438. ClearAltList(lat->pAltList + iStroke);
  2439. bbox=lat->pStroke[iStroke].bbox;
  2440. EmptyIntervals(&xIntervals,bbox.left,bbox.right);
  2441. EmptyIntervals(&yIntervals,bbox.top,bbox.bottom);
  2442. // Run through all possible numbers of strokes for this
  2443. // character
  2444. for (nChar1=1;
  2445. nChar1<=MaxStrokesPerCharacter && iStroke-nChar1+1>=0;
  2446. nChar1++) {
  2447. VOLCANO_WEIGHTS tuneScores;
  2448. // Determine the features for this proposed character
  2449. LATTICE_ELEMENT elem;
  2450. if (nChar1>1) {
  2451. RECT other=lat->pStroke[iStroke-nChar1+1].bbox;
  2452. int xdist=__max(other.left-bbox.right,bbox.left-other.right);
  2453. int ydist=__max(other.top-bbox.bottom,bbox.top-other.bottom);
  2454. int dist=__max(xdist,ydist);
  2455. bbox.left=__min(bbox.left,other.left);
  2456. bbox.right=__max(bbox.right,other.right);
  2457. bbox.top=__min(bbox.top,other.top);
  2458. bbox.bottom=__max(bbox.bottom,other.bottom);
  2459. dist=xdist/(bbox.right-bbox.left);
  2460. // dist=xdist;
  2461. maxDist=__max(dist,maxDist);
  2462. ExpandIntervalsRange(&xIntervals,other.left,other.right);
  2463. ExpandIntervalsRange(&yIntervals,other.top,other.bottom);
  2464. RemoveInterval(&xIntervals,other.left,other.right);
  2465. RemoveInterval(&yIntervals,other.top,other.bottom);
  2466. }
  2467. elem.fUsed=TRUE;
  2468. elem.fCurrentPath=FALSE;
  2469. elem.nStrokes=nChar1;
  2470. elem.nPrevStrokes=0;
  2471. elem.wChar=SYM_UNKNOWN;
  2472. elem.wPrevChar=0;
  2473. elem.bbox=bbox;
  2474. ASSERT(bbox.top<=bbox.bottom);
  2475. ASSERT(bbox.left<=bbox.right);
  2476. elem.iPrevAlt=-1;
  2477. elem.area=TotalRange(&xIntervals)+TotalRange(&yIntervals);
  2478. elem.space=TotalPresent(&xIntervals)+TotalPresent(&yIntervals);
  2479. elem.maxDist=maxDist;
  2480. // If this is the first character in the lattice, use single
  2481. // character statistics
  2482. if (iStroke-nChar1+1==0) {
  2483. BOOL fSegOkay = FALSE;
  2484. VTuneZeroWeights(&tuneScores);
  2485. fSegOkay = CheapUnaryProb(nChar1,bbox,elem.space,elem.area, &tuneScores);
  2486. // If we're not in separator mode, then prune based on the
  2487. // segmentation score
  2488. if (!lat->fSepMode && !fSegOkay)
  2489. {
  2490. continue;
  2491. }
  2492. // Equalize probs across different numbers of strokes
  2493. elem.logProb = VTuneComputeScoreNoLM(&g_vtuneInfo.pTune->weights, &tuneScores);
  2494. elem.logProbPath = elem.logProb;
  2495. elem.iPathLength = 1;
  2496. InsertElement(lat,iStroke,&elem);
  2497. } else {
  2498. // Otherwise go through all the previous characters in the
  2499. // lattice as alternates.
  2500. BOOL first=TRUE;
  2501. for (iAlt=0; iAlt<lat->pAltList[iStroke-nChar1].nUsed; iAlt++) {
  2502. LATTICE_ELEMENT *prevElem=&lat->pAltList[iStroke-nChar1].alts[iAlt];
  2503. float prob, probPath;
  2504. BOOL fSegOkay;
  2505. // Only disallow overlapping boxes in normal recognition mode
  2506. if (!lat->fSepMode && IsOverlappedPath(lat,iStroke-nChar1,iAlt,bbox)) {
  2507. continue;
  2508. }
  2509. VTuneZeroWeights(&tuneScores);
  2510. fSegOkay = CheapBinaryProb(nChar1,bbox,elem.space,elem.area,
  2511. prevElem->nStrokes,prevElem->bbox,prevElem->space,prevElem->area,
  2512. &tuneScores);
  2513. // If we're not in separator mode, then prune based on the
  2514. // segmentation score
  2515. if (!lat->fSepMode && !fSegOkay)
  2516. {
  2517. continue;
  2518. }
  2519. // In separator mode, just add a penalty for overlapping boxes
  2520. if (lat->fSepMode && IsOverlappedPath(lat,iStroke-nChar1,iAlt,bbox))
  2521. {
  2522. tuneScores.afl[VTUNE_FREE_SEG_BIGRAM] += -PENALTY_OVERLAP_CHAR;
  2523. }
  2524. prob = VTuneComputeScoreNoLM(&g_vtuneInfo.pTune->weights, &tuneScores);
  2525. // Equalize probs across different numbers of strokes
  2526. probPath = (prob + prevElem->logProbPath * prevElem->iPathLength) / (prevElem->iPathLength + 1);
  2527. if (first || probPath > elem.logProbPath)
  2528. {
  2529. first=FALSE;
  2530. elem.iPathLength = prevElem->iPathLength + 1;
  2531. elem.logProb=prob;
  2532. elem.logProbPath=probPath;
  2533. elem.nPrevStrokes=prevElem->nStrokes;
  2534. elem.iPrevAlt=iAlt;
  2535. }
  2536. }
  2537. if (!first)
  2538. {
  2539. InsertElement(lat,iStroke,&elem);
  2540. }
  2541. }
  2542. }
  2543. if (lat->pAltList[iStroke].nUsed==0)
  2544. {
  2545. // Well, the pre-segmentation came back and said there were no candidates.
  2546. // Let's put in a dummy alt so the pruning will still have a connected lattice
  2547. // to work with.
  2548. // fprintf(stderr,"Lost a stroke in free mode.\n");
  2549. FakeRecogResult(lat, iStroke, 1, -PENALTY_SKIP_INK);
  2550. }
  2551. }
  2552. PruneLattice(lat, 0, lat->nStrokes - 1);
  2553. // For each unprocessed stroke in the lattice
  2554. for (iStroke = 0; iStroke < lat->nStrokes; iStroke++)
  2555. {
  2556. if (iStroke >= lat->nProcessed)
  2557. {
  2558. BuildRecogAlts(lat, iStroke, TRUE);
  2559. }
  2560. if (lat->pAltList[iStroke].nUsed==0)
  2561. {
  2562. // Well, the recognizer came back and said there were no candidates.
  2563. // Let's put in a dummy alt so the user will see something, and also so
  2564. // the language model will have a path to work with.
  2565. // fprintf(stderr,"Lost a stroke in free mode.\n");
  2566. FakeRecogResult(lat, iStroke, 1, -PENALTY_SKIP_INK);
  2567. }
  2568. ASSERT(lat->pAltList[iStroke].nUsed>0);
  2569. }
  2570. // Mark this stroke as processed so we don't redo work.
  2571. lat->nProcessed = lat->nStrokes;
  2572. }
  2573. // Mark the best path so far, and count how many characters it has
  2574. nChars = FindFullPath(lat);
  2575. // If we're at the end of the input
  2576. if (lat->fEndInput)
  2577. {
  2578. int cContext = 0;
  2579. // If there are enough, use IFELang3
  2580. #ifdef USE_IFELANG3
  2581. if (lat->wszBefore != NULL)
  2582. cContext += wcslen(lat->wszBefore);
  2583. if (lat->wszAfter != NULL)
  2584. cContext += wcslen(lat->wszAfter);
  2585. if (nChars + cContext >= MIN_CHARS_FOR_IFELANG3 && LatticeIFELang3Available())
  2586. {
  2587. lat->fUseIFELang3=TRUE;
  2588. }
  2589. else
  2590. {
  2591. lat->fUseIFELang3=FALSE;
  2592. }
  2593. #else
  2594. lat->fUseIFELang3 = FALSE;
  2595. #endif
  2596. // Until we can get it tuned correctly, force IFELang3 off for free mode.
  2597. lat->fUseIFELang3 = FALSE;
  2598. }
  2599. }
  2600. // Hack for big and small kana characters
  2601. if (lat->fUseGuide)
  2602. {
  2603. BigSmallKanaHack(lat);
  2604. }
  2605. return TRUE;
  2606. }
  2607. #ifdef DUMP_LATTICE
  2608. #include <stdio.h>
  2609. #ifdef DUMP_LATTICE_TO_DOTTY
  2610. void DumpLatticeElement(FILE *f, LATTICE *lat, int iStroke, int iAlt)
  2611. {
  2612. LATTICE_ELEMENT *elem=&(lat->pAltList[iStroke].alts[iAlt]);
  2613. if (elem->fUsed)
  2614. {
  2615. wchar_t wch = elem->wChar == SYM_UNKNOWN ? SYM_UNKNOWN : LocRunDense2Unicode(&g_locRunInfo, elem->wChar);
  2616. wchar_t wchPrev = elem->wPrevChar == SYM_UNKNOWN ? SYM_UNKNOWN : LocRunDense2Unicode(&g_locRunInfo, elem->wPrevChar);
  2617. int nPrevStrokes = elem->iPrevAlt == -1 ? 0 : lat->pAltList[iStroke - elem->nStrokes].alts[elem->iPrevAlt].nStrokes;
  2618. if (0 && wch == 0)
  2619. {
  2620. char *color=",color=green,fontcolor=green,weight=2";
  2621. if (elem->fCurrentPath)
  2622. {
  2623. color=",color=red,fontcolor=red,weight=2";
  2624. }
  2625. fprintf(f,
  2626. "\"%d\" -> \"%d\" [ label = \"DUMMY (prev U+%04X/%d)\\npr=%.2f,pp=%.2f\"%s ];\n",
  2627. iStroke-elem->nStrokes,iStroke,
  2628. wchPrev, nPrevStrokes,
  2629. elem->logProb,
  2630. elem->logProbPath,
  2631. color);
  2632. }
  2633. else
  2634. {
  2635. char *color=",color=gray,fontcolor=gray,weight=2";
  2636. if (elem->fCurrentPath)
  2637. {
  2638. color=",color=black,fontcolor=black,weight=2";
  2639. }
  2640. fprintf(f,
  2641. "\"%d\" -> \"%d\" [ label = \"U+%04X (prev U+%04X/%d)\\nfr=%.2f,ch=%.2f\\nsh1=%.2f,sh2=%.2f\\nun1=%.2f,un2=%.2f\\nbi=%.2f,cb=%.2f\\npr=%.2f,pp=%.2f\"%s ];\n",
  2642. iStroke-elem->nStrokes,iStroke,
  2643. wch, wchPrev, nPrevStrokes,
  2644. (elem->tuneScores.afl[VTUNE_FREE_SEG_UNIGRAM] + elem->tuneScores.afl[VTUNE_FREE_SEG_BIGRAM])
  2645. * elem->iPathLength,
  2646. elem->tuneScores.afl[VTUNE_FREE_PROB] * elem->iPathLength,
  2647. elem->tuneScores.afl[VTUNE_FREE_SHAPE_UNIGRAM] * elem->iPathLength,
  2648. elem->tuneScores.afl[VTUNE_FREE_SHAPE_BIGRAM] * elem->iPathLength,
  2649. elem->tuneScores.afl[VTUNE_UNIGRAM] * elem->iPathLength,
  2650. elem->tuneScores.afl[VTUNE_FREE_SMOOTHING_UNIGRAM] * elem->iPathLength,
  2651. elem->tuneScores.afl[VTUNE_FREE_BIGRAM] * elem->iPathLength,
  2652. elem->tuneScores.afl[VTUNE_FREE_CLASS_BIGRAM] * elem->iPathLength,
  2653. elem->logProb,
  2654. elem->logProbPath * elem->iPathLength,
  2655. color);
  2656. }
  2657. }
  2658. }
  2659. void DumpLattice(char *filename, LATTICE *lat)
  2660. {
  2661. if (lat->fSepMode)
  2662. {
  2663. int iStroke, iAlt, i;
  2664. FILE *f;
  2665. ASSERT(filename!=NULL);
  2666. ASSERT(lat!=NULL);
  2667. f=fopen(filename,"w");
  2668. if (f == NULL)
  2669. {
  2670. fprintf(stderr,"Couldn't open '%s' to dump the lattice.",filename);
  2671. return;
  2672. }
  2673. fprintf(f, "digraph lattice {\n");
  2674. // Dump out the answer
  2675. for (i = 0; i < (int)wcslen(lat->wszAnswer); i++)
  2676. {
  2677. if (i > 0)
  2678. {
  2679. fprintf(f, " -> ");
  2680. }
  2681. fprintf(f, "\"U+%04X\"", lat->wszAnswer[i]);
  2682. }
  2683. fprintf(f, "\n");
  2684. // This produces a timeline
  2685. fprintf(f, "\"Stroke -1\"");
  2686. for (iStroke = 0; iStroke < lat->nStrokes; iStroke++)
  2687. {
  2688. fprintf(f, " -> \"Stroke %d\"", iStroke);
  2689. }
  2690. fprintf(f, "\n");
  2691. // This forces the nodes to be shown in time order
  2692. for (iStroke = -1; iStroke < lat->nStrokes; iStroke++)
  2693. {
  2694. fprintf(f, "{rank=same; \"%d\"; \"Stroke %d\";}\n", iStroke, iStroke);
  2695. if (iStroke >= 0)
  2696. {
  2697. fprintf(f, "\"Stroke %d\" [ label=\"%d-%d\" ];\n",
  2698. iStroke, lat->pStroke[iStroke].iOrder, lat->pStroke[iStroke].iLast);
  2699. }
  2700. }
  2701. // This produces the actual lattice
  2702. for (iStroke = lat->nStrokes - 1; iStroke >= 0; iStroke--)
  2703. {
  2704. for (iAlt = 0; iAlt < lat->pAltList[iStroke].nUsed; iAlt++)
  2705. {
  2706. DumpLatticeElement(f,lat,iStroke,iAlt);
  2707. }
  2708. }
  2709. fprintf(f,"}\n");
  2710. fclose(f);
  2711. }
  2712. }
  2713. #else
  2714. void DumpLatticeElement(FILE *f, LATTICE *lat, int iStroke, int iAlt)
  2715. {
  2716. LATTICE_ELEMENT *elem=&(lat->pAltList[iStroke].alts[iAlt]);
  2717. if (elem->fUsed) {
  2718. fprintf(f,"Stroke %d alternate %d",iStroke,iAlt);
  2719. if (elem->fCurrentPath) fprintf(f," on current path\n"); else fprintf(f,"\n");
  2720. fprintf(f," nStrokes =%2d wChar =U+%04X score=%g\n",elem->nStrokes,LocRunDense2Unicode(&g_locRunInfo,elem->wChar),elem->score);
  2721. fprintf(f," nPrevStrokes=%2d wPrevChar=U+%04X\n",elem->nPrevStrokes,LocRunDense2Unicode(&g_locRunInfo,elem->wPrevChar));
  2722. fprintf(f," prob=%g probpath=%g\n",elem->logProb,elem->logProbPath);
  2723. fprintf(f," iPrevAlt=%d\n",elem->iPrevAlt);
  2724. fprintf(f," bbox xrange=%d-%d, yrange=%d-%d\n",elem->bbox.left,elem->bbox.right,elem->bbox.top,elem->bbox.bottom);
  2725. fprintf(f," space=%d area=%d\n",elem->space,elem->area);
  2726. }
  2727. }
  2728. void DumpLattice(char *filename, LATTICE *lat)
  2729. {
  2730. int iStroke, iAlt;
  2731. FILE *f;
  2732. ASSERT(filename!=NULL);
  2733. ASSERT(lat!=NULL);
  2734. f=fopen(filename,"w");
  2735. if (f==NULL) {
  2736. fprintf(stderr,"Couldn't open '%s' to dump the lattice.",filename);
  2737. return;
  2738. }
  2739. for (iStroke=lat->nStrokes-1; iStroke>=0; iStroke--) {
  2740. for (iAlt=0; iAlt<lat->pAltList[iStroke].nUsed; iAlt++) {
  2741. DumpLatticeElement(f,lat,iStroke,iAlt);
  2742. fprintf(f,"\n");
  2743. }
  2744. fprintf(f,"\n");
  2745. }
  2746. fclose(f);
  2747. }
  2748. #endif
  2749. #endif
  2750. // Return the current path.
  2751. // When called after ProcessLattice, returns the highest probability path.
  2752. // The memory for the path should be freed by the caller.
  2753. BOOL GetCurrentPath(LATTICE *lat, LATTICE_PATH **pPath)
  2754. {
  2755. int nChars, iStroke, iChar, iAlt, iTestStroke=-1;
  2756. LATTICE_PATH *path;
  2757. RECT rectBox, rectPrevBox;
  2758. BOOL bSpaceEnabled;
  2759. // currently spaces are only enabled in korean free mode
  2760. if (!lat->fUseGuide && !lat->fSepMode && !wcsicmp (g_szRecognizerLanguage, L"KOR"))
  2761. {
  2762. bSpaceEnabled = TRUE;
  2763. }
  2764. else
  2765. {
  2766. bSpaceEnabled = FALSE;
  2767. }
  2768. ASSERT(lat!=NULL);
  2769. #ifdef DUMP_LATTICE
  2770. DumpLattice(LATTICE_FILENAME,lat);
  2771. #endif
  2772. #ifdef DUMP_BBOXES
  2773. {
  2774. FILE *f = fopen("e:/bbox.txt", "w");
  2775. fprintf(f, "canvas .c -width 1000 -height 1000;\n");
  2776. fprintf(f, "pack .c;\n");
  2777. fclose(f);
  2778. }
  2779. #endif
  2780. nChars = 0;
  2781. for (iStroke=0; iStroke<lat->nStrokes; iStroke++)
  2782. {
  2783. // Wipe out all spaces so far
  2784. lat->pAltList[iStroke].fSpaceAfterStroke = FALSE;
  2785. for (iAlt=0; iAlt<lat->pAltList[iStroke].nUsed; iAlt++)
  2786. {
  2787. if (lat->pAltList[iStroke].alts[iAlt].fCurrentPath)
  2788. {
  2789. nChars++;
  2790. // Make sure this element of the path connects up to the previous one.
  2791. ASSERT(iStroke - lat->pAltList[iStroke].alts[iAlt].nStrokes == iTestStroke);
  2792. iTestStroke = iStroke;
  2793. }
  2794. }
  2795. }
  2796. // we'll allocate memory for 2 * nChars in anticipation of spaces between chars
  2797. path=AllocatePath(2 * nChars);
  2798. if (path==NULL)
  2799. {
  2800. return FALSE;
  2801. }
  2802. iChar = 0;
  2803. for (iStroke=0; iStroke<lat->nStrokes; iStroke++)
  2804. {
  2805. for (iAlt=0; iAlt<lat->pAltList[iStroke].nUsed; iAlt++)
  2806. {
  2807. if (lat->pAltList[iStroke].alts[iAlt].fCurrentPath)
  2808. {
  2809. wchar_t dch;
  2810. // are spaces enabled
  2811. if (bSpaceEnabled)
  2812. {
  2813. // do we have a space before us
  2814. rectBox = lat->pAltList[iStroke].alts[iAlt].bbox;
  2815. // we can only have a space before us if we are not the 1st char
  2816. if (iChar > 0)
  2817. {
  2818. int xDel;
  2819. int iAvgHgt;
  2820. xDel = rectBox.left - rectPrevBox.right;
  2821. iAvgHgt = 1 + ( (rectBox.right - rectBox.left) +
  2822. (rectPrevBox.right - rectPrevBox.left)
  2823. ) / 2;
  2824. if ( xDel >= ((int)(iAvgHgt * SPACE_RATIO)) ||
  2825. rectBox.right < rectPrevBox.left
  2826. )
  2827. {
  2828. // create a phantom path element
  2829. path->pElem[iChar].iBoxNum = -1;
  2830. path->pElem[iChar].iStroke = iStroke - lat->pAltList[iStroke].alts[iAlt].nStrokes;
  2831. path->pElem[iChar].nStrokes = 0;
  2832. path->pElem[iChar].iAlt = SPACE_ALT_ID;
  2833. path->pElem[iChar].wChar = SYM_SPACE;
  2834. // mark the location of the space in the lattice
  2835. lat->pAltList[path->pElem[iChar].iStroke].fSpaceAfterStroke = TRUE;
  2836. iChar++;
  2837. }
  2838. }
  2839. // save the rectangle of the current char
  2840. rectPrevBox = rectBox;
  2841. }
  2842. dch = lat->pAltList[iStroke].alts[iAlt].wChar;
  2843. if (dch==SYM_UNKNOWN)
  2844. {
  2845. // In separator mode, we need to return a special symbol for the
  2846. // skipped ink cases.
  2847. path->pElem[iChar].wChar = SYM_UNKNOWN;
  2848. }
  2849. else
  2850. {
  2851. ASSERT(LocRunIsDenseCode(&g_locRunInfo, dch));
  2852. path->pElem[iChar].wChar = LocRunDense2Unicode(&g_locRunInfo,dch);
  2853. }
  2854. path->pElem[iChar].nStrokes = lat->pAltList[iStroke].alts[iAlt].nStrokes;
  2855. path->pElem[iChar].iBoxNum = lat->pStroke[iStroke].iBox;
  2856. path->pElem[iChar].iStroke = iStroke;
  2857. path->pElem[iChar].iAlt = iAlt;
  2858. // path->pElem[iChar].scores = lat->pAltList[iStroke].alts[iAlt].score;
  2859. // path->pElem[iChar].bbox=lat->pAltList[iStroke].alts[iAlt].bbox;
  2860. #ifdef DUMP_BBOXES
  2861. {
  2862. int i;
  2863. RECT r = lat->pAltList[iStroke].alts[iAlt].writingBox;
  2864. double s = 1.0 / 100.0;
  2865. FILE *f = fopen("e:/bbox.txt","a");
  2866. fprintf(f, ".c create rect %f %f %f %f -outline black -fill {};\n",
  2867. r.left * s, r.top * s, r.right * s, r.bottom * s);
  2868. for (i = iStroke - lat->pAltList[iStroke].alts[iAlt].nStrokes + 1; i <= iStroke; i++)
  2869. {
  2870. int j;
  2871. fprintf(f, ".c create line");
  2872. for (j = 0; j < lat->pStroke[i].nInk; j++)
  2873. {
  2874. fprintf(f, " %f %f",
  2875. lat->pStroke[i].pts[j].x * s,
  2876. lat->pStroke[i].pts[j].y * s);
  2877. }
  2878. fprintf(f, " -fill red\n");
  2879. }
  2880. fclose(f);
  2881. }
  2882. #endif
  2883. iChar++;
  2884. }
  2885. }
  2886. }
  2887. path->nChars = iChar;
  2888. *pPath=path;
  2889. return TRUE;
  2890. }
  2891. // Given a lattice and a path through it, for characters iStartChar (inclusive) through iEndChar
  2892. // (exclusive), return the time stamps of the first and last strokes in those characters.
  2893. // Returns FALSE if there are no strokes associated with the characters (eg, spaces)
  2894. BOOL GetCharacterTimeRange(LATTICE *lat, LATTICE_PATH *path, int iStartChar, int iEndChar,
  2895. DWORD *piStartTime, DWORD *piEndTime)
  2896. {
  2897. int iStartStroke=-1, iEndStroke=-1;
  2898. ASSERT(lat!=NULL);
  2899. ASSERT(path!=NULL);
  2900. ASSERT(iStartChar>=0 && iStartChar<path->nChars);
  2901. ASSERT(iEndChar>iStartChar && iEndChar<=path->nChars);
  2902. while (iStartChar < path->nChars && path->pElem[iStartChar].iAlt == SPACE_ALT_ID)
  2903. {
  2904. iStartChar++;
  2905. }
  2906. while (iEndChar > 0 && path->pElem[iEndChar - 1].iAlt == SPACE_ALT_ID)
  2907. {
  2908. iEndChar--;
  2909. }
  2910. if (iStartChar >= iEndChar)
  2911. {
  2912. return FALSE;
  2913. }
  2914. iStartStroke=path->pElem[iStartChar].iStroke - path->pElem[iStartChar].nStrokes + 1;
  2915. iEndStroke=path->pElem[iEndChar-1].iStroke;
  2916. ASSERT(iStartStroke>=0);
  2917. ASSERT(iEndStroke>=0);
  2918. ASSERT(iStartStroke<lat->nStrokes);
  2919. ASSERT(iEndStroke<lat->nStrokes);
  2920. *piStartTime=lat->pStroke[iStartStroke].timeStart;
  2921. *piEndTime=lat->pStroke[iEndStroke].timeEnd;
  2922. return TRUE;
  2923. }
  2924. // Get the number of strokes which have been added to the lattice.
  2925. int GetLatticeStrokeCount(LATTICE *lat)
  2926. {
  2927. ASSERT(lat!=NULL);
  2928. return lat->nStrokes;
  2929. }
  2930. // Given a character in the current path, determine a "guide box" around that character.
  2931. BOOL GetBoxOfAlternateInCurrentPath(LATTICE *lat, LATTICE_PATH *path, int iChar, RECT *pRect)
  2932. {
  2933. int iStroke = path->pElem[iChar].iStroke;
  2934. int iAlt = path->pElem[iChar].iAlt;
  2935. // Get the inferred writing box, using either centipede or the old baseline/height database.
  2936. RECT writingBox = lat->pAltList[iStroke].alts[iAlt].writingBox;
  2937. // Adjust the writing box so the top is the midline:
  2938. writingBox.top = (writingBox.top + writingBox.bottom + 1) / 2;
  2939. // Make sure the box isn't zero or negative sized:
  2940. if (writingBox.bottom <= writingBox.top)
  2941. writingBox.bottom = writingBox.top + 1;
  2942. if (writingBox.right <= writingBox.left)
  2943. writingBox.right = writingBox.left + 1;
  2944. *pRect = writingBox;
  2945. return TRUE;
  2946. }
  2947. // Look at the lattice column containing the given character, and find alternates with the same number
  2948. // of strokes. The alternates are returned in the given array, and the number of alternates found is
  2949. // returned.
  2950. int GetAlternatesForCharacterInCurrentPath(LATTICE *lat, LATTICE_PATH *path, int iChar, int nAlts, wchar_t *pwAlts)
  2951. {
  2952. int iStroke = path->pElem[iChar].iStroke;
  2953. int nStrokesInChar = path->pElem[iChar].nStrokes;
  2954. int cAlts = 0, iAlt;
  2955. ASSERT(lat!=NULL);
  2956. ASSERT(lat->nStrokes>0);
  2957. ASSERT(path!=NULL);
  2958. // if this is a space char, then we will produce a single entry alt list
  2959. if (path->pElem[iChar].iAlt == SPACE_ALT_ID)
  2960. {
  2961. pwAlts[0] = path->pElem[iChar].wChar;
  2962. return 1;
  2963. }
  2964. // Run through the list of alternates, and find those with
  2965. // the same number of strokes
  2966. for (iAlt=0; iAlt<lat->pAltList[iStroke].nUsed; iAlt++)
  2967. {
  2968. if (lat->pAltList[iStroke].alts[iAlt].nStrokes==nStrokesInChar)
  2969. {
  2970. int i;
  2971. BOOL found=FALSE;
  2972. for (i=0; i<cAlts; i++)
  2973. {
  2974. if (pwAlts[i]==lat->pAltList[iStroke].alts[iAlt].wChar)
  2975. {
  2976. found=TRUE;
  2977. }
  2978. }
  2979. if (!found)
  2980. {
  2981. pwAlts[cAlts++]=lat->pAltList[iStroke].alts[iAlt].wChar;
  2982. if (cAlts==nAlts)
  2983. {
  2984. goto breakLoop;
  2985. }
  2986. }
  2987. }
  2988. }
  2989. breakLoop:
  2990. // Now convert the dense codes to Unicode.
  2991. for (iAlt = 0; iAlt < cAlts; ++iAlt)
  2992. {
  2993. // We need to return a special symbol
  2994. // for the skipped ink case.
  2995. if (pwAlts[iAlt] != SYM_UNKNOWN)
  2996. {
  2997. ASSERT(LocRunIsDenseCode(&g_locRunInfo, pwAlts[iAlt]));
  2998. pwAlts[iAlt] = LocRunDense2Unicode(&g_locRunInfo, pwAlts[iAlt]);
  2999. }
  3000. }
  3001. return cAlts;
  3002. }
  3003. void FreeLatticePath(LATTICE_PATH *path)
  3004. {
  3005. ASSERT(path!=NULL);
  3006. if (path->pElem!=NULL) ExternFree(path->pElem);
  3007. ExternFree(path);
  3008. }
  3009. // Structure used to hold information for the DTW.
  3010. typedef struct tagDTW {
  3011. float logProb; // Score for this location in the table
  3012. int nSubstitutions; // How many character substitutions occurred along this path
  3013. int iAlt; // Alt list entry in the lattice for this table entry
  3014. int iPrevChar, iPrevStroke; // Prev character and score for trace back
  3015. } DTW;
  3016. // Given a lattice and a string of unicode characters, find the best path through the lattice
  3017. // which gives that sequence of characters. Baring that, it will find the most likely path
  3018. // through the lattice with the same number of characters and the minimum number of mismatches
  3019. // to the prompt. In case no such path can be found, the current path becomes empty.
  3020. // The function returns the number of substitutions used, or -1 if there is no path with
  3021. // the desired number of characters, -2 if a memory allocation error occurs, or -3 if a
  3022. // file write error occurs.
  3023. int SearchForTargetResultInternal(LATTICE *lat, wchar_t *wszTarget)
  3024. {
  3025. wchar_t *wsz;
  3026. int i, j;
  3027. int iAlt, iStroke, iChar, nSubs = -1;
  3028. DTW *pTable, *dtw;
  3029. ASSERT(lat!=NULL);
  3030. ASSERT(wszTarget!=NULL);
  3031. if (wcslen(wszTarget)==0 || lat->nStrokes == 0)
  3032. {
  3033. return -1;
  3034. }
  3035. // fprintf(stderr,"Beginning SearchForTargetResult\n");
  3036. // Allocate the string
  3037. wsz = (wchar_t*)ExternAlloc((wcslen(wszTarget)+1)*sizeof(wchar_t));
  3038. if (wsz==NULL) return -2;
  3039. // Get rid of unwanted characters (spaces)
  3040. j=0;
  3041. for (i = 0; i <= (int)wcslen(wszTarget); i++)
  3042. if (wszTarget[i]==0 || !iswspace(wszTarget[i]))
  3043. wsz[j++] = wszTarget[i];
  3044. if (wcslen(wsz) == 0)
  3045. {
  3046. ExternFree(wsz);
  3047. return -1;
  3048. }
  3049. // Allocate the DTW table
  3050. pTable=(DTW*)ExternAlloc(sizeof(DTW)*lat->nStrokes*wcslen(wsz));
  3051. if (pTable==NULL) {
  3052. ExternFree(wsz);
  3053. return -2;
  3054. }
  3055. // For each table entry, find the best path to get there
  3056. for (iStroke=0; iStroke<lat->nStrokes; iStroke++) {
  3057. for (iChar=0; iChar<(int)wcslen(wsz); iChar++) {
  3058. // Convert the character we're looking for to a dense code
  3059. SYM dense=LocRunUnicode2Dense(&g_locRunInfo,wsz[iChar]);
  3060. // To record the best value for this table entry
  3061. BOOL first=TRUE;
  3062. int bestAlt=-1;
  3063. float bestLogProb=0;
  3064. int bestNSubstitutions=0;
  3065. int bestPrevChar=-1, bestPrevStroke=-1;
  3066. // Run through the alternates at this stroke, and see if anything matches
  3067. for (iAlt=0; iAlt<lat->pAltList[iStroke].nUsed; iAlt++) {
  3068. float prevLogProb=0;
  3069. int prevNSubstitutions=0;
  3070. // Get the information about the best path leading to this alternate
  3071. int iPrevChar=iChar-1;
  3072. int iPrevStroke=iStroke-lat->pAltList[iStroke].alts[iAlt].nStrokes;
  3073. // Make sure the first character starts at the first stroke.
  3074. if (iPrevChar==-1 && iPrevStroke!=-1) continue;
  3075. if (iPrevStroke==-1 && iPrevChar!=-1) continue;
  3076. // if (iPrevStroke>=0 && iPrevChar==-1) prevLogProb=(float)iPrevStroke+1;
  3077. // if (iPrevStroke==-1 && iPrevChar>=0) prevLogProb=(float)iPrevChar+1;
  3078. // Check the previous best path
  3079. if (iPrevStroke>=0 && iPrevChar>=0) {
  3080. // If there wasn't a path, then skip this alternative
  3081. if (pTable[iPrevChar*lat->nStrokes+iPrevStroke].iAlt==-1) continue;
  3082. prevLogProb=pTable[iPrevChar*lat->nStrokes+iPrevStroke].logProb;
  3083. prevNSubstitutions=pTable[iPrevChar*lat->nStrokes+iPrevStroke].nSubstitutions;
  3084. }
  3085. // If the alternate matches, then record it without increasing the number of substitutions
  3086. if (lat->pAltList[iStroke].alts[iAlt].wChar==dense) {
  3087. float thisLogProb=prevLogProb;
  3088. int thisNSubstitutions=prevNSubstitutions;
  3089. if (first || thisNSubstitutions<bestNSubstitutions || (thisNSubstitutions==bestNSubstitutions && thisLogProb>bestLogProb)) {
  3090. bestAlt=iAlt;
  3091. bestLogProb=thisLogProb;
  3092. bestPrevStroke=iPrevStroke;
  3093. bestPrevChar=iPrevChar;
  3094. bestNSubstitutions=thisNSubstitutions;
  3095. first=FALSE;
  3096. }
  3097. } else {
  3098. // Otherwise it is a substitution, so record as such
  3099. float thisLogProb=/*lat->pAltList[iStroke].alts[iAlt].nStrokes*/prevLogProb;
  3100. int thisNSubstitutions=prevNSubstitutions+1;
  3101. if (first || thisNSubstitutions<bestNSubstitutions || (thisNSubstitutions==bestNSubstitutions && thisLogProb>bestLogProb)) {
  3102. bestAlt=iAlt;
  3103. bestLogProb=thisLogProb;
  3104. bestPrevStroke=iPrevStroke;
  3105. bestPrevChar=iPrevChar;
  3106. bestNSubstitutions=thisNSubstitutions;
  3107. first=FALSE;
  3108. }
  3109. }
  3110. // skip ink
  3111. /* prevLogProb=(float)lat->pAltList[iStroke].alts[iAlt].nStrokes;
  3112. if (iPrevStroke>=0) prevLogProb+=pTable[iChar*lat->nStrokes+iPrevStroke].logProb;
  3113. if (first || prevLogProb<bestLogProb) {
  3114. bestAlt=-1;
  3115. bestLogProb=prevLogProb;
  3116. bestPrevStroke=iPrevStroke;
  3117. bestPrevChar=iChar;
  3118. first=FALSE;
  3119. } */
  3120. }
  3121. // skip char
  3122. /* prevLogProb=1;
  3123. if (iPrevChar>=0) prevLogProb+=pTable[iPrevChar*lat->nStrokes+iStroke].logProb;
  3124. if (first || prevLogProb<bestLogProb) {
  3125. bestAlt=-1;
  3126. bestLogProb=prevLogProb;
  3127. bestPrevStroke=iStroke;
  3128. bestPrevChar=iPrevChar;
  3129. first=FALSE;
  3130. } */
  3131. // Record the best path
  3132. pTable[iChar*lat->nStrokes+iStroke].logProb=bestLogProb;
  3133. pTable[iChar*lat->nStrokes+iStroke].iAlt=bestAlt;
  3134. pTable[iChar*lat->nStrokes+iStroke].iPrevStroke=bestPrevStroke;
  3135. pTable[iChar*lat->nStrokes+iStroke].iPrevChar=bestPrevChar;
  3136. pTable[iChar*lat->nStrokes+iStroke].nSubstitutions=bestNSubstitutions;
  3137. }
  3138. }
  3139. // Dump out some debugging information about which characters were matched.
  3140. #ifdef DUMP_DTW
  3141. {
  3142. int iChar, iStroke;
  3143. FILE *f=fopen("/dtw.txt","w");
  3144. iStroke = lat->nStrokes-1;
  3145. iChar = wcslen(wsz)-1;
  3146. // Go back through the DTW lattice to mark the matched path
  3147. while (iStroke!=-1 && iChar!=-1) {
  3148. DTW *dtw=pTable+(iChar*lat->nStrokes+iStroke);
  3149. if (dtw->iAlt!=-1) {
  3150. fprintf(f,"Character %d (U+%04X) found at iStroke=%d iAlt=%d\n",
  3151. iChar,wsz[iChar],iStroke,dtw->iAlt);
  3152. } else {
  3153. fprintf(f,"Character %d (U+%04X) not found (iAlt=%d)\n",
  3154. iChar,wsz[iChar],dtw->iAlt);
  3155. }
  3156. iStroke=dtw->iPrevStroke;
  3157. iChar=dtw->iPrevChar;
  3158. }
  3159. fclose(f);
  3160. }
  3161. #endif
  3162. // Go back through the DTW lattice to mark the best matched path
  3163. iStroke = lat->nStrokes-1;
  3164. iChar = wcslen(wsz)-1;
  3165. dtw=pTable+(iChar*lat->nStrokes+iStroke);
  3166. if (dtw->iAlt==-1) {
  3167. // If we didn't find any path all the way through, don't return any path.
  3168. nSubs=-1;
  3169. } else {
  3170. #ifdef HWX_TUNE
  3171. if (g_pTuneFile == NULL || g_iTuneMode == 3)
  3172. #endif
  3173. {
  3174. // Wipe out the old path
  3175. for (iStroke=0; iStroke<lat->nStrokes; iStroke++)
  3176. for (iAlt=0; iAlt<lat->pAltList[iStroke].nUsed; iAlt++)
  3177. lat->pAltList[iStroke].alts[iAlt].fCurrentPath=FALSE;
  3178. }
  3179. // Get the first step in the path
  3180. iStroke = lat->nStrokes-1;
  3181. iChar = wcslen(wsz)-1;
  3182. dtw=pTable+(iChar*lat->nStrokes+iStroke);
  3183. nSubs=dtw->nSubstitutions;
  3184. while (iStroke!=-1 && iChar!=-1) {
  3185. dtw = pTable + (iChar * lat->nStrokes + iStroke);
  3186. if (dtw->iAlt!=-1) {
  3187. #ifdef HWX_TUNE
  3188. if (g_pTuneFile == NULL || g_iTuneMode == 3)
  3189. #endif
  3190. {
  3191. // If there was a path to this point, mark the alternate
  3192. lat->pAltList[iStroke].alts[dtw->iAlt].fCurrentPath=TRUE;
  3193. }
  3194. #ifdef HWX_TUNE
  3195. // If there was an correct path up to this point, then dump it out along
  3196. // with the wrong alternates.
  3197. if (g_pTuneFile != NULL && g_iTuneMode != 3)
  3198. {
  3199. if (lat->fUseGuide || dtw->nSubstitutions == 0)
  3200. {
  3201. wchar_t dchCorrect = LocRunUnicode2Dense(&g_locRunInfo, wsz[iChar]);
  3202. INT idch = dchCorrect;
  3203. INT iCorrect = -1;
  3204. int iAlt;
  3205. BOOL okay;
  3206. INT nAlts = lat->pAltList[iStroke].nUsed;
  3207. for (iAlt = 0; iAlt < nAlts; iAlt++)
  3208. {
  3209. LATTICE_ELEMENT *elem = lat->pAltList[iStroke].alts + iAlt;
  3210. if (elem->wChar == dchCorrect)
  3211. {
  3212. iCorrect = iAlt;
  3213. }
  3214. }
  3215. // Write out the correct answer
  3216. okay = (fwrite(&idch, sizeof(INT), 1, g_pTuneFile) == 1);
  3217. // Write out the index of the correct alternate
  3218. okay = okay && (fwrite(&iCorrect, sizeof(INT), 1, g_pTuneFile) == 1);
  3219. // Write out the number of alternates
  3220. okay = okay && (fwrite(&nAlts, sizeof(INT), 1, g_pTuneFile) == 1);
  3221. // Write out the alternates
  3222. for (iAlt = 0; iAlt < nAlts; iAlt++)
  3223. {
  3224. LATTICE_ELEMENT *elem = lat->pAltList[iStroke].alts + iAlt;
  3225. // int iTem = (iAlt == dtw->iAlt);
  3226. // int iTem = (elem->wChar == dchCorrect);
  3227. // okay = okay && (fwrite(&iTem, sizeof(int), 1, g_pTuneFile) == 1);
  3228. // okay = okay && (fwrite(&elem->logProbPath, sizeof(FLOAT), 1, g_pTuneFile) == 1);
  3229. // okay = okay && (fwrite(&elem->tuneScores, sizeof(VOLCANO_WEIGHTS), 1, g_pTuneFile) == 1);
  3230. if (g_iTuneMode == 1)
  3231. {
  3232. okay = okay && VTuneCompressTuningRecord(g_pTuneFile, &elem->tuneScores);
  3233. }
  3234. if (g_iTuneMode == 2)
  3235. {
  3236. okay = okay && (fwrite(&elem->logProb, sizeof(FLOAT), 1, g_pTuneFile) == 1);
  3237. }
  3238. }
  3239. if (!okay)
  3240. {
  3241. ExternFree(wsz);
  3242. ExternFree(pTable);
  3243. return -3;
  3244. }
  3245. }
  3246. }
  3247. #endif
  3248. }
  3249. iStroke=dtw->iPrevStroke;
  3250. iChar=dtw->iPrevChar;
  3251. }
  3252. }
  3253. ExternFree(wsz);
  3254. ExternFree(pTable);
  3255. return nSubs;
  3256. }
  3257. // Configuration info
  3258. VOLCANO_CONFIG g_latticeConfigInfo;
  3259. // Initialize the lattice configuration to default values.
  3260. // These defaults may in the future depend on the language
  3261. // that is loaded.
  3262. void LatticeConfigInit()
  3263. {
  3264. int i;
  3265. // Also go through the stroke counts setting no recognizer.
  3266. for (i = 0; i <= VOLCANO_CONFIG_MAX_STROKE_COUNT; i++) {
  3267. if (i == 0 || i > GLYPH_CFRAMEMAX) {
  3268. g_latticeConfigInfo.iRecognizers[i] = VOLCANO_CONFIG_ZILLA;
  3269. } else {
  3270. g_latticeConfigInfo.iRecognizers[i] = VOLCANO_CONFIG_OTTER;
  3271. }
  3272. }
  3273. }
  3274. // Update the probabilities in the lattice, including setting current
  3275. // path to the most likely path so far (including language model).
  3276. BOOL ProcessLatticeRange (LATTICE *lat, int iStrtStroke, int iEndStroke)
  3277. {
  3278. int iStroke, iAlt;
  3279. ASSERT(lat!=NULL);
  3280. // invalid stroke range
  3281. if (iStrtStroke < 0 || iEndStroke >= lat->nStrokes || iStrtStroke > iEndStroke)
  3282. {
  3283. return FALSE;
  3284. }
  3285. // can only be called in panel free mode
  3286. if (lat->fUseGuide || lat->fWordMode || lat->fSepMode)
  3287. {
  3288. return FALSE;
  3289. }
  3290. // Always use probability mode for free input
  3291. lat->fProbMode=TRUE;
  3292. // For each stroke within the range
  3293. for (iStroke = iStrtStroke; iStroke <= iEndStroke; iStroke++)
  3294. {
  3295. int maxDist = 0;
  3296. int nChar1;
  3297. RECT bbox;
  3298. INTERVALS xIntervals, yIntervals;
  3299. // Wipe out the current path
  3300. ClearAltList(lat->pAltList + iStroke);
  3301. bbox = lat->pStroke[iStroke].bbox;
  3302. EmptyIntervals(&xIntervals,bbox.left,bbox.right);
  3303. EmptyIntervals(&yIntervals,bbox.top,bbox.bottom);
  3304. // Run through all possible numbers of strokes for this
  3305. // character
  3306. for (nChar1=1; nChar1<=MaxStrokesPerCharacter && iStroke-nChar1+1>=iStrtStroke; nChar1++)
  3307. {
  3308. VOLCANO_WEIGHTS tuneScores;
  3309. // Determine the features for this proposed character
  3310. LATTICE_ELEMENT elem;
  3311. if (nChar1>1)
  3312. {
  3313. RECT other=lat->pStroke[iStroke-nChar1+1].bbox;
  3314. int xdist=__max(other.left-bbox.right,bbox.left-other.right);
  3315. int ydist=__max(other.top-bbox.bottom,bbox.top-other.bottom);
  3316. int dist=__max(xdist,ydist);
  3317. bbox.left=__min(bbox.left,other.left);
  3318. bbox.right=__max(bbox.right,other.right);
  3319. bbox.top=__min(bbox.top,other.top);
  3320. bbox.bottom=__max(bbox.bottom,other.bottom);
  3321. dist=xdist/(bbox.right-bbox.left);
  3322. maxDist=__max(dist,maxDist);
  3323. ExpandIntervalsRange(&xIntervals,other.left,other.right);
  3324. ExpandIntervalsRange(&yIntervals,other.top,other.bottom);
  3325. RemoveInterval(&xIntervals,other.left,other.right);
  3326. RemoveInterval(&yIntervals,other.top,other.bottom);
  3327. }
  3328. elem.fUsed=TRUE;
  3329. elem.fCurrentPath=FALSE;
  3330. elem.nStrokes=nChar1;
  3331. elem.nPrevStrokes=0;
  3332. elem.wChar=SYM_UNKNOWN;
  3333. elem.wPrevChar=0;
  3334. elem.bbox=bbox;
  3335. ASSERT(bbox.top<=bbox.bottom);
  3336. ASSERT(bbox.left<=bbox.right);
  3337. elem.iPrevAlt=-1;
  3338. elem.area=TotalRange(&xIntervals)+TotalRange(&yIntervals);
  3339. elem.space=TotalPresent(&xIntervals)+TotalPresent(&yIntervals);
  3340. elem.maxDist=maxDist;
  3341. // If this is the first character in the lattice, use single
  3342. // character statistics
  3343. if (iStroke-nChar1+1==0)
  3344. {
  3345. VTuneZeroWeights(&tuneScores);
  3346. CheapUnaryProb(nChar1,bbox,elem.space,elem.area, &tuneScores);
  3347. // Equalize probs across different numbers of strokes
  3348. elem.logProb = VTuneComputeScoreNoLM(&g_vtuneInfo.pTune->weights, &tuneScores);
  3349. elem.logProbPath = elem.logProb;
  3350. elem.iPathLength = 1;
  3351. InsertElement(lat,iStroke,&elem);
  3352. }
  3353. else
  3354. {
  3355. // Otherwise go through all the previous characters in the
  3356. // lattice as alternates.
  3357. BOOL first=TRUE;
  3358. for (iAlt=0; iAlt<lat->pAltList[iStroke-nChar1].nUsed; iAlt++)
  3359. {
  3360. LATTICE_ELEMENT *prevElem = &lat->pAltList[iStroke-nChar1].alts[iAlt];
  3361. float prob, probPath;
  3362. // Only disallow overlapping boxes in normal recognition mode
  3363. if (IsOverlappedPath(lat, iStroke - nChar1, iAlt, bbox))
  3364. {
  3365. continue;
  3366. }
  3367. VTuneZeroWeights(&tuneScores);
  3368. // If we're not in separator mode, then prune based on the
  3369. // segmentation score
  3370. CheapBinaryProb(nChar1,bbox,elem.space,elem.area,
  3371. prevElem->nStrokes,prevElem->bbox,prevElem->space,prevElem->area,
  3372. &tuneScores);
  3373. prob = VTuneComputeScoreNoLM(&g_vtuneInfo.pTune->weights, &tuneScores);
  3374. // Equalize probs across different numbers of strokes
  3375. probPath = (prob + prevElem->logProbPath * prevElem->iPathLength) /
  3376. (prevElem->iPathLength + 1);
  3377. if (first || probPath > elem.logProbPath)
  3378. {
  3379. first=FALSE;
  3380. elem.iPathLength = prevElem->iPathLength + 1;
  3381. elem.logProb=prob;
  3382. elem.logProbPath=probPath;
  3383. elem.nPrevStrokes=prevElem->nStrokes;
  3384. elem.iPrevAlt=iAlt;
  3385. }
  3386. }
  3387. if (!first)
  3388. {
  3389. InsertElement(lat,iStroke,&elem);
  3390. }
  3391. }
  3392. }
  3393. }
  3394. PruneLattice(lat, iStrtStroke, iEndStroke);
  3395. // For each stroke in the lattice
  3396. for (iStroke = iStrtStroke; iStroke <= iEndStroke; iStroke++)
  3397. {
  3398. BuildRecogAlts(lat, iStroke, TRUE);
  3399. }
  3400. if (lat->pAltList[iEndStroke].nUsed==0)
  3401. {
  3402. // Well, the recognizer came back and said there were no candidates.
  3403. // Let's put in a dummy alt so the user will see something, and also so
  3404. // the language model will have a path to work with.
  3405. FakeRecogResult(lat, iEndStroke, iEndStroke - iStrtStroke + 1, -PENALTY_SKIP_INK);
  3406. }
  3407. return TRUE;
  3408. }
  3409. void FixupBackPointers (LATTICE *pLat)
  3410. {
  3411. int iStrk,
  3412. iAlt,
  3413. iPrevStrk,
  3414. iPrevAlt,
  3415. cChar;
  3416. iPrevStrk = 0;
  3417. iPrevAlt = -1;
  3418. cChar = 0;
  3419. // all strokes
  3420. for (iStrk = 0; iStrk < pLat->nStrokes; iStrk++)
  3421. {
  3422. // every alt at this stroke
  3423. for (iAlt = 0; iAlt < pLat->pAltList[iStrk].nUsed; iAlt++)
  3424. {
  3425. // is it part of the best path
  3426. if (pLat->pAltList[iStrk].alts[iAlt].fCurrentPath)
  3427. {
  3428. // the number of strokes should be correct because this is
  3429. // how we set the fCurrent flags, so assert otherwise
  3430. ASSERT (pLat->pAltList[iStrk].alts[iAlt].nStrokes == (iStrk - iPrevStrk + 1));
  3431. // set the prev alt
  3432. pLat->pAltList[iStrk].alts[iAlt].iPrevAlt = iPrevAlt;
  3433. pLat->pAltList[iStrk].alts[iAlt].iPathLength = (++cChar);
  3434. // update the prev strk and alt
  3435. iPrevStrk = iStrk + 1;
  3436. iPrevAlt = iAlt;
  3437. }
  3438. }
  3439. }
  3440. }