diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/libscribble/li_recognizer.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/libscribble/li_recognizer.c')
-rwxr-xr-x | sys/src/libscribble/li_recognizer.c | 2313 |
1 files changed, 2313 insertions, 0 deletions
diff --git a/sys/src/libscribble/li_recognizer.c b/sys/src/libscribble/li_recognizer.c new file mode 100755 index 000000000..e7a20805d --- /dev/null +++ b/sys/src/libscribble/li_recognizer.c @@ -0,0 +1,2313 @@ +/* + * li_recognizer.c + * + * Copyright 2000 Compaq Computer Corporation. + * Copying or modifying this code for any purpose is permitted, + * provided that this copyright notice is preserved in its entirety + * in all copies or modifications. + * COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR + * IMPLIED, AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR + * + * + * Adapted from cmu_recognizer.c by Jay Kistler. + * + * Where is the CMU copyright???? Gotta track it down - Jim Gettys + * + * Credit to Dean Rubine, Jim Kempf, and Ari Rapkin. + */ + +#include <u.h> +#include <libc.h> +#include <stdio.h> +#include <draw.h> +#include <scribble.h> +#include "scribbleimpl.h" + +#include "hre_internal.h" +#include "li_recognizer_internal.h" + +int lidebug = 0; + +#define LI_MAGIC 0xACCBADDD + +#define CHECK_LI_MAGIC(_a) \ + ((_a) != nil && ((li_recognizer*)(_a))->li_magic == LI_MAGIC) + + +static void lialg_initialize(rClassifier *); +static int lialg_read_classifier_digest(rClassifier *); +static int lialg_canonicalize_examples(rClassifier *); +static char *lialg_recognize_stroke(rClassifier *, point_list *); +static void lialg_compute_lpf_parameters(void); + + +char* li_err_msg = nil; + +#define bcopy(s1,s2,n) memmove(s2,s1,n) + +/*Freeing classifier*/ + +static void +free_rClassifier(rClassifier* rc); + +/* + * Point List Support +*/ + +static point_list* +add_example(point_list* l,int npts,pen_point* pts) +{ + pen_point* lpts = mallocz(npts*sizeof(pen_point), 1); + point_list *p = malloc(sizeof(point_list)); + + p->npts = npts; + p->pts = lpts; + p->next = l; /*Order doesn't matter, so we stick on end.*/ + + /*Copy points.*/ + + bcopy(pts, lpts, npts * sizeof(pen_point)); + + return(p); +} + + +static void +delete_examples(point_list* l) +{ + point_list* p; + + for( ; l != nil; l = p ) { + p = l->next; + free(l->pts); + free(l); + } +} + +/* + * Local functions + */ + +/* + * recognize_internal-Form Vector, use Classifier to classify, return char. + */ + +static char* +recognize_internal(rClassifier* rec, Stroke* str, int*) +{ + char *res; + point_list *stroke; + + stroke = add_example(nil, str->npts, str->pts); + if (stroke == nil) return(nil); + + res = lialg_recognize_stroke(rec, stroke); + + delete_examples(stroke); + return(res); +} + +/* + * file_path-Construct pathname, check for proper extension. + */ + +static int + file_path(char* dir,char* filename,char* pathname) +{ + char* dot; + + /*Check for proper extension on file name.*/ + + dot = strrchr(filename,'.'); + + if( dot == nil ) { + return(-1); + } + + /*Determine whether a gesture or character classifier.*/ + + if( strcmp(dot,LI_CLASSIFIER_EXTENSION) != 0 ) { + return(-1); + } + + /*Concatenate directory and filename into pathname.*/ + + strcpy(pathname,dir); + strcat(pathname,"/"); + strcat(pathname,filename); + + return(0); +} + +/*read_classifier_points-Read points so classifier can be extended.*/ + +static int +read_classifier_points(FILE* fd,int nclss,point_list** ex,char** cnames) +{ + int i,j,k; + char buf[BUFSIZ]; + int nex = 0; + char* names[MAXSCLASSES]; + point_list* examples[MAXSCLASSES]; + pen_point* pts; + int npts; + + /*Initialize*/ + + for( i = 0; i < MAXSCLASSES; i++ ) { + names[i] = nil; + examples[i] = nil; + } + + /*Go thru classes.*/ + + for( k = 0; k < nclss; k++ ) { + + /*Read class name and number of examples.*/ + + if( fscanf(fd,"%d %s",&nex,buf) != 2 ) + goto unallocate; + + /*Save class name*/ + + names[k] = strdup(buf); + + /*Read examples.*/ + + for( i = 0; i < nex; i++ ) { + + /*Read number of points.*/ + + if( fscanf(fd,"%d",&npts) != 1 ) + goto unallocate; /*Boy would I like exceptions!*/ + + /*Allocate array for points.*/ + + if( (pts = mallocz(npts*sizeof(pen_point), 1)) == nil ) + goto unallocate; + + /*Read in points.*/ + + for( j = 0; j < npts; j++ ) { + int x,y; + if( fscanf(fd,"%d %d",&x,&y) != 2 ) { + delete_pen_point_array(pts); + goto unallocate; + } + pts[j].Point = Pt(x, y); + } + + /*Add example*/ + + if( (examples[k] = add_example(examples[k],npts,pts)) == nil ) { + delete_pen_point_array(pts); + goto unallocate; + } + + delete_pen_point_array(pts); + + } + } + +/* ari -- end of list of classes */ +/* fprint(2, "]\n"); */ + + /*Transfer to recognizer.*/ + + bcopy(examples,ex,sizeof(examples)); + bcopy(names,cnames,sizeof(names)); + + return(0); + + /*Error. Deallocate memory and return.*/ + +unallocate: + for( ; k >= 0; k-- ) { + delete_examples(examples[k]); + free(names[k]); + } + + return(-1); +} + +/*read_classifier-Read a classifier file.*/ + +static int read_classifier(FILE* fd,rClassifier* rc) +{ + + li_err_msg = nil; + + /*Read in classifier file.*/ + + if(fscanf(fd, "%d", &rc->nclasses) != 1) + return -1; + + /*Read in the example points, so classifier can be extended.*/ + + if( read_classifier_points(fd,rc->nclasses,rc->ex,rc->cnames) != 0 ) + return -1; + + return(0); +} + +/* + * Extension Functions +*/ + +/* getClasses and clearState are by Ari */ + +static int +recognizer_getClasses (recognizer r, char ***list, int *nc) +{ + int i, nclasses; + li_recognizer* rec; + char **ret; + + rec = (li_recognizer*)r->recognizer_specific; + + /*Check for LI recognizer.*/ + + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return(-1); + } + + *nc = nclasses = rec->li_rc.nclasses; + ret = malloc(nclasses*sizeof(char*)); + + for (i = 0; i < nclasses; i++) + ret[i] = rec->li_rc.cnames[i]; /* only the 1st char of the cname */ + *list = ret; + return 0; +} + +static int +recognizer_clearState (recognizer) +{ + /*This operation isn't supported by the LI recognizer.*/ + + li_err_msg = "Clearing state is not supported by the LI recognizer"; + + return(-1); +} + +static bool isa_li(recognizer r) +{ return(CHECK_LI_MAGIC(r)); } + +static int +recognizer_train(recognizer, rc*, uint, Stroke*, rec_element*, bool) +{ + /*This operation isn't supported by the LI recognizer.*/ + + li_err_msg = "Training is not supported by the LI recognizer"; + + return(-1); +} + +int +li_recognizer_get_example (recognizer r, + int class, + int instance, + char **name, + pen_point **points, + int *npts) +{ + li_recognizer *rec = (li_recognizer*)r->recognizer_specific; + int nclasses = rec->li_rc.nclasses; + point_list *pl; + + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return(-1); + } + if (class > nclasses) + return -1; + pl = rec->li_rc.canonex[class]; + while (instance && pl) + { + pl = pl->next; + instance--; + } + if (!pl) + return -1; + *name = rec->li_rc.cnames[class]; + *points = pl->pts; + *npts = pl->npts; + return pl->npts; /* I hope [sjm] */ +} + +/* + * API Functions + */ + + +/*li_recognizer_load-Load a classifier file.*/ + +static int li_recognizer_load(recognizer r, char* dir, char* filename) +{ + FILE *fd; + char* pathname; + li_recognizer* rec; + rClassifier* rc; + + rec = (li_recognizer*)r->recognizer_specific; + + /*Make sure recognizer's OK*/ + + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return(-1); + } + + rc = &(rec->li_rc); + + /*Check parameters.*/ + + if( filename == nil ) { + li_err_msg = "Invalid parameters"; + return(-1); + } + + /*We let the directory be null.*/ + + if( dir == nil || (int)strlen(dir) <= 0 ) { + dir = "."; + } + + if(0)fprint(2, "dir = %s, filename = %s\n", + dir, filename); + + /*Make full pathname and check filename*/ + + pathname = malloc((strlen(dir) + strlen(filename) + 2)*sizeof(char)); + if( file_path(dir, filename, pathname) == -1 ) { + free(pathname); + li_err_msg = "Not a LI recognizer classifier file"; + return -1; + } + + /* Try to short-circuit the full classifier-file processing. */ + rc->file_name = pathname; + if (lialg_read_classifier_digest(rc) == 0) + return(0); + rc->file_name = nil; + + /*Open the file*/ + + if( (fd = fopen(pathname,"r")) == nil ) { + li_err_msg = "Can't open classifier file"; + if(0)fprint(2, "Can't open %s.\n", pathname); + free(pathname); + return(-1); + } + + /*If rClassifier is OK, then delete it first.*/ + + if( rc->file_name != nil ) { + free_rClassifier(rc); + } + + /*Read classifier.*/ + + if( read_classifier(fd,rc) < 0 ) { + free(pathname); + return(-1); + } + + /*Close file.*/ + + fclose(fd); + + /*Add classifier name.*/ + + rc->file_name = pathname; + + /* Canonicalize examples. */ + if (lialg_canonicalize_examples(rc) != 0) { + free(pathname); + rc->file_name = nil; + return -1; + } + + return(0); +} + +/*li_recognizer_save-Save a classifier file.*/ + +static int li_recognizer_save(recognizer, char*, char*) +{ + /*This operation isn't supported by the LI recognizer.*/ + + li_err_msg = "Saving is not supported by the LI recognizer"; + return -1; +} + +static wordset +li_recognizer_load_dictionary(recognizer, char*, char*) +{ + /*This operation isn't supported by the LI recognizer.*/ + + li_err_msg = "Dictionaries are not supported by the LI recognizer"; + return nil; +} + +static int +li_recognizer_save_dictionary(recognizer, char*, char*, wordset) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Dictionaries are not supported by the LI recognizer"; + return -1; +} + +static int +li_recognizer_free_dictionary(recognizer, wordset) +{ + /*This operation isn't supported by the LI recognizer.*/ + + li_err_msg = "Dictionaries are not supported by the LI recognizer"; + + return -1; + +} + +static int +li_recognizer_add_to_dictionary(recognizer, letterset*, wordset) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Dictionaries are not supported by the LI recognizer"; + return -1; +} + +static int +li_recognizer_delete_from_dictionary(recognizer, letterset*, wordset) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Dictionaries are not supported by the LI recognizer"; + return -1; +} + +static char* +li_recognizer_error(recognizer rec) +{ + char* ret = li_err_msg; + + /*Check for LI recognizer.*/ + + if( !CHECK_LI_MAGIC(rec->recognizer_specific) ) { + li_err_msg = "Not a LI recognizer"; + return nil; + } + li_err_msg = nil; + return ret; +} + +static int +li_recognizer_clear(recognizer r, bool) +{ + li_recognizer* rec; + + rec = (li_recognizer*)r->recognizer_specific; + /*Check for LI recognizer.*/ + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return 0; + } + return 0; +} + +static int +li_recognizer_set_context(recognizer, rc*) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Contexts are not supported by the LI recognizer"; + return -1; +} + +static rc* +li_recognizer_get_context(recognizer) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Contexts are not supported by the LI recognizer"; + return nil; +} + +static int +li_recognizer_get_buffer(recognizer, uint*, Stroke**) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Buffer get/set are not supported by the LI recognizer"; + return -1; +} + +static int +li_recognizer_set_buffer(recognizer, uint, Stroke*) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Buffer get/set are not supported by the LI recognizer"; + return -1; +} + +static int +li_recognizer_translate(recognizer r, uint ncs, Stroke* tps, bool, int* nret, rec_alternative** ret) +{ + char* clss; + li_recognizer* rec; + int conf; + rClassifier* rc; + + rec = (li_recognizer*)r->recognizer_specific; + + *nret = 0; + *ret = nil; + + /*Check for LI recognizer.*/ + + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return(-1); + } + + rc = &(rec->li_rc); + + /*Check for valid parameters.*/ + if (ncs < 1) { + li_err_msg = "Invalid parameters: ncs"; + return(-1); + } + if( tps == nil) { + li_err_msg = "Invalid parameters: tps"; + return(-1); + } + if( nret == nil) { + li_err_msg = "Invalid parameters: nret"; + return(-1); + } + if( ret == nil) { + li_err_msg = "Invalid parameters: ret"; + return(-1); + } + + /* + * Go through the stroke array and recognize. Since this is a single + * stroke recognizer, each stroke is treated as a separate + * character or gesture. We allow only characters or gestures + * to be recognized at one time, since otherwise, handling + * the display of segmentation would be difficult. + */ + clss = recognize_internal(rc,tps,&conf); + if (clss == nil) { + *nret = 1; + return(0); + } + + /*Return values.*/ + *nret = 1; + return(*clss); +} + + +static rec_fn* +li_recognizer_get_extension_functions(recognizer rec) +{ + rec_fn* ret; + + /*Check for LI recognizer.*/ + + if( !CHECK_LI_MAGIC(rec->recognizer_specific) ) { + li_err_msg = "Not a LI recognizer"; + return(nil); + } + + ret = make_rec_fn_array(LI_NUM_EX_FNS); + +/* ari -- clearState & getClasses are mine */ + ret[LI_GET_CLASSES] = (rec_fn)recognizer_getClasses; + ret[LI_CLEAR] = (rec_fn)recognizer_clearState; + ret[LI_ISA_LI] = (rec_fn)isa_li; + ret[LI_TRAIN] = (rec_fn)recognizer_train; + + return(ret); +} + +static char** +li_recognizer_get_gesture_names(recognizer) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Gestures are not supported by the LI recognizer"; + return nil; +} + +static xgesture +li_recognizer_set_gesture_action(recognizer, char*, xgesture, void*) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Gestures are not supported by the LI recognizer"; + return nil; +} + +/* + * Exported Functions +*/ + +/*RECOGNIZER_INITIALIZE-Initialize the recognizer.*/ + +/* note from ari: this expands via pre-processor to + * + * recognizer __recognizer_internal_initialize(rec_info* ri) + */ + +RECOGNIZER_INITIALIZE(ri) +{ + recognizer r; + li_recognizer* rec; + int i; + + /*Check that locale matches.*/ + + if( strcmp(ri->ri_locale,LI_SUPPORTED_LOCALE) != 0 ) { + li_err_msg = "Not a supported locale"; +/* fprint(2, "Locale error.\n");*/ + return(nil); + } + + /* + * Check that character sets match. Note that this is only approximate, + * since the classifier file will have more information. + */ + + if( ri->ri_subset != nil ) { + for(i = 0; ri->ri_subset[i] != nil; i++ ) { + + if( strcmp(ri->ri_subset[i],UPPERCASE) != 0 && + strcmp(ri->ri_subset[i],LOWERCASE) != 0 && + strcmp(ri->ri_subset[i],DIGITS) != 0 && + strcmp(ri->ri_subset[i],GESTURE) != 0 ) { + li_err_msg = "Not a supported character set"; +/* fprint(2, "charset error.\n"); */ + + return(nil); + } + } + } + +/* ari */ + r = make_recognizer(ri); +/* fprint(2, "past make_recognizer.\n"); */ + + if( r == nil ) { + li_err_msg = "Can't allocate storage"; + + return(nil); + } + + /*Make a LI recognizer structure.*/ + + + /* rec = (li_recognizer*)safe_malloc(sizeof(li_recognizer))) == nil ); */ + + rec = malloc(sizeof(li_recognizer)); + + r->recognizer_specific = rec; + + rec->li_rc.file_name = nil; + rec->li_rc.nclasses = 0; + + /*Initialize the recognizer struct.*/ + + r->recognizer_load_state = li_recognizer_load; + r->recognizer_save_state = li_recognizer_save; + r->recognizer_load_dictionary = li_recognizer_load_dictionary; + r->recognizer_save_dictionary = li_recognizer_save_dictionary; + r->recognizer_free_dictionary = li_recognizer_free_dictionary; + r->recognizer_add_to_dictionary = li_recognizer_add_to_dictionary; + r->recognizer_delete_from_dictionary = li_recognizer_delete_from_dictionary; + r->recognizer_error = li_recognizer_error; + r->recognizer_translate = li_recognizer_translate; + r->recognizer_get_context = li_recognizer_get_context; + r->recognizer_set_context = li_recognizer_set_context; + r->recognizer_get_buffer = li_recognizer_get_buffer; + r->recognizer_set_buffer = li_recognizer_set_buffer; + r->recognizer_clear = li_recognizer_clear; + r->recognizer_get_extension_functions = + li_recognizer_get_extension_functions; + r->recognizer_get_gesture_names = li_recognizer_get_gesture_names; + r->recognizer_set_gesture_action = + li_recognizer_set_gesture_action; + + /*Initialize LI Magic Number.*/ + + rec->li_magic = LI_MAGIC; + + /*Initialize rClassifier.*/ + + rec->li_rc.file_name = nil; + + for( i = 0; i < MAXSCLASSES; i++ ) { + rec->li_rc.ex[i] = nil; + rec->li_rc.cnames[i] = nil; + } + + lialg_initialize(&rec->li_rc); + + /*Get rid of error message. Not needed here.*/ + li_err_msg = nil; + + return(r); +} + +/*free_rClassifier-Free the rClassifier.*/ + +static void +free_rClassifier(rClassifier* rc) +{ + int i; + + if( rc->file_name != nil) { + free(rc->file_name); + } + + for( i = 0; rc->ex[i] != nil; i++) { + delete_examples(rc->ex[i]); + free(rc->cnames[i]); + } + +} + +/*RECOGNIZER_FINALIZE-Deallocate the recognizer, finalize.*/ + +RECOGNIZER_FINALIZE(r) +{ + li_recognizer* rec = (li_recognizer*)r->recognizer_specific; + + /*Make sure this is a li_recognizer first*/ + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return(-1); + } + + /*Deallocate rClassifier resources.*/ + + free_rClassifier(&(rec->li_rc)); + + /*Deallocate the li_recognizer struct.*/ + free(rec); + + /*Deallocate the recognizer*/ + delete_recognizer(r); + + return(0); +} + + +/* ************************************************** + + Implementation of the Li/Yeung recognition algorithm + +************************************************** */ + +#define WORST_SCORE 0x7fffffff + +/* Dynamic programming parameters */ +#define DP_BAND 3 +#define MIN_SIM 0 +#define MAX_DIST 0x7fffffff +#define SIM_THLD 60 /* x 100 */ +#define DIST_THLD 3200 /* x 100 */ + +/* Low-pass filter parameters -- empirically derived */ +#define LP_FILTER_WIDTH 6 +#define LP_FILTER_ITERS 8 +#define LP_FILTER_THLD 250 /* x 100 */ +#define LP_FILTER_MIN 5 + +/* Pseudo-extrema parameters -- empirically derived */ +#define PE_AL_THLD 1500 /* x 100 */ +#define PE_ATCR_THLD 135 /* x 100 */ + +/* Contour-angle derivation parameters */ +#define T_ONE 1 +#define T_TWO 20 + +/* Pre-processing and canonicalization parameters */ +#define CANONICAL_X 108 +#define CANONICAL_Y 128 +#define DIST_SQ_THRESHOLD (3*3) /* copied from fv.h */ +#define NCANONICAL 50 + +/* Tap-handling parameters */ +#define TAP_CHAR "." +#define TAP_TIME_THLD 150 /* msec */ +#define TAP_DIST_THLD 75 /* dx * dx + dy * dy */ +#define TAP_PATHLEN 1000 /* x 100 */ + + +/* region types */ +#define RGN_CONVEX 0 +#define RGN_CONCAVE 1 +#define RGN_PLAIN 2 +#define RGN_PSEUDO 3 + + +typedef struct RegionList { + int start; + int end; + int type; + struct RegionList *next; +} region_list; + + +/* direction-code table; indexed by dx, dy */ +static int lialg_dctbl[3][3] = {{1, 0, 7}, {2, 0x7FFFFFFF, 6}, {3, 4, 5}}; + +/* low-pass filter weights */ +static int lialg_lpfwts[2 * LP_FILTER_WIDTH + 1]; +static int lialg_lpfconst = -1; + + +static int lialg_preprocess_stroke(point_list *); +static point_list *lialg_compute_dominant_points(point_list *); +static point_list *lialg_interpolate_points(point_list *); +static void lialg_bresline(pen_point *, pen_point *, point_list *, int *); +static void lialg_compute_chain_code(point_list *); +static void lialg_compute_unit_chain_code(point_list *); +static region_list *lialg_compute_regions(point_list *); +static point_list *lialg_compute_dompts(point_list *, region_list *); +static int *lialg_compute_contour_angle_set(point_list *, region_list *); +static void lialg_score_stroke(point_list *, point_list *, int *, int *); +static int lialg_compute_similarity(point_list *, point_list *); +static int lialg_compute_distance(point_list *, point_list *); + +static int lialg_read_classifier_digest(rClassifier *); + +static int lialg_canonicalize_examples(rClassifier *); +static int lialg_canonicalize_example_stroke(point_list *); +static int lialg_compute_equipoints(point_list *); + +static int lialg_compute_pathlen(point_list *); +static int lialg_compute_pathlen_subset(point_list *, int, int); +static int lialg_filter_points(point_list *); +static int lialg_translate_points(point_list *, int, int, int, int); +static void lialg_get_bounding_box(point_list *, int *, int *, int *, int *); +static void lialg_compute_lpf_parameters(); +static int isqrt(int); +static int likeatan(int, int); +static int quadr(int); + + +/************************************************************* + + Core routines for the Li/Yeung recognition algorithm + + *************************************************************/ + +static void lialg_initialize(rClassifier *rec) { + int i; + + /* Initialize the dompts arrays. */ + for (i = 0; i < MAXSCLASSES; i++) { + rec->dompts[i] = nil; + } +} + + +/* + * Main recognition routine -- called by HRE API. + */ +static char *lialg_recognize_stroke(rClassifier *rec, point_list *stroke) { + int i; + char *name = nil; + point_list *input_dompts = nil; + char *best_name = nil; + int best_score = WORST_SCORE; + char *curr_name; + point_list *curr_dompts; + + /* (void)gettimeofday(&stv, nil);*/ + + if (stroke->npts < 1) goto done; + + /* Check for tap. */ + + /* First thing is to filter out ``close points.'' */ + if (lialg_filter_points(stroke) != 0) return(nil); + + /* Unfortunately, we don't have the actual time that each point */ + /* was recorded (i.e., dt is invalid). Hence, we have to use a */ + /* heuristic based on total distance and the number of points. */ + if (stroke->npts == 1 || lialg_compute_pathlen(stroke) < TAP_PATHLEN) + return(TAP_CHAR); + + /* Pre-process input stroke. */ + if (lialg_preprocess_stroke(stroke) != 0) goto done; + + /* Compute its dominant points. */ + input_dompts = lialg_compute_dominant_points(stroke); + if (input_dompts == nil) goto done; + + /* Score input stroke against every class in classifier. */ + for (i = 0, curr_name = rec->cnames[i], curr_dompts = rec->dompts[i]; + i < MAXSCLASSES && curr_name != nil && curr_dompts != nil; + i++, curr_name = rec->cnames[i], curr_dompts = rec->dompts[i]) { + int sim; + int dist; + int curr_score; + + lialg_score_stroke(input_dompts, curr_dompts, &sim, &dist); + curr_score = dist; + + if (lidebug && curr_score < DIST_THLD) + fprint(2, "(%s, %d, %d) ", curr_name, sim, dist); + + /* Is it the best so far? */ + if (curr_score < best_score && curr_score <= DIST_THLD) { + best_score = curr_score; + best_name = curr_name; + } + } + + if (lidebug) + fprint(2, "\n"); + + /* No errors. */ + name = best_name; + +done: + delete_examples(input_dompts); + return(name); +} + + +static int lialg_preprocess_stroke(point_list *points) { + int minx, miny, maxx, maxy, xrange, yrange, scale, xoff, yoff; + + /* Filter out points that are too close. */ + /* We did this earlier, when we checked for a tap. */ +/* + if (lialg_filter_points(points) != 0) return(-1); +*/ + +/* assert(points->npts > 0);*/ + + /* Scale up to avoid conversion errors. */ + lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy); + xrange = maxx - minx; + yrange = maxy - miny; + scale = ( ((100 * xrange + CANONICAL_X / 2) / CANONICAL_X) > + ((100 * yrange + CANONICAL_Y / 2) / CANONICAL_Y)) + ? (100 * CANONICAL_X + xrange / 2) / xrange + : (100 * CANONICAL_Y + yrange / 2) / yrange; + if (lialg_translate_points(points, minx, miny, scale, scale) != 0) + return(-1); + + /* Center the stroke. */ + lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy); + xrange = maxx - minx; + yrange = maxy - miny; + xoff = -((CANONICAL_X - xrange + 1) / 2); + yoff = -((CANONICAL_Y - yrange + 1) / 2); + if (lialg_translate_points(points, xoff, yoff, 100, 100) != 0) return(-1); + + /* Store the x and y ranges in the point list. */ + xrange = maxx - minx; + yrange = maxy - miny; + points->xrange = xrange; + points->yrange = yrange; + + if (lidebug) { + int i; + fprint(2, "After pre-processing: %d %d %d %d\n", + minx, miny, maxx, maxy); + for (i = 0; i < points->npts; i++) + fprint(2, " (%P)\n", points->pts[i].Point); + fflush(stderr); + } + + return(0); +} + + +static point_list *lialg_compute_dominant_points(point_list *points) { + point_list *ipts; + region_list *regions; + point_list *dpts; + + /* Interpolate points. */ + ipts = lialg_interpolate_points(points); + if (ipts == nil) return(nil); + if (lidebug) { + int j; + fprint(2, "After interpolation: %d ipts\n", ipts->npts); + for (j = 0; j < ipts->npts; j++) { + fprint(2, " (%P), %lud\n", ipts->pts[j].Point, ipts->pts[j].chaincode); + } + fflush(stderr); + } + + /* Compute regions. */ + regions = lialg_compute_regions(ipts); +/* assert(regions != nil);*/ + + /* Compute dominant points. */ + dpts = lialg_compute_dompts(ipts, regions); + if (lidebug) { + int j; + fprint(2, "Dominant points: "); + for (j = 0; j < dpts->npts; j++) { + fprint(2, "%P (%lud) ", dpts->pts[j].Point, dpts->pts[j].chaincode); + } + fprint(2, "\n"); + fflush(stderr); + } + + /* Delete region data structure. */ + { + region_list *curr, *next; + for (curr = regions; curr != nil; ) { + next = curr->next; + free(curr); + curr = next; + } + } + delete_examples(ipts); + return(dpts); +} + +/* Input points are assumed to be integer-valued! */ +static point_list *lialg_interpolate_points(point_list *points) { + int i, j; + int maxpts; + point_list *newpts; + + /* Compute an upper-bound on the number of interpolated points. */ + maxpts = 0; + for (i = 0; i < (points->npts - 1); i++) { + pen_point *pta = &(points->pts[i]); + pen_point *ptb = &(points->pts[i+1]); + maxpts += abs(pta->x - ptb->x) + abs(pta->y - ptb->y); + } + + /* Allocate an array of the requisite size. */ + maxpts += points->npts; + /* newpts = (point_list *)safe_malloc(sizeof(point_list)); */ + newpts = malloc(sizeof(point_list)); + newpts->pts = mallocz(maxpts*sizeof(pen_point), 1); + if (newpts->pts == nil) { + free(newpts); + return(nil); + } + newpts->npts = maxpts; + newpts->next = nil; + + /* Interpolate each of the segments. */ + j = 0; + for (i = 0; i < (points->npts - 1); i++) { + pen_point *startpt = &(points->pts[i]); + pen_point *endpt = &(points->pts[i+1]); + + lialg_bresline(startpt, endpt, newpts, &j); + + j--; /* end point gets recorded as start point of next segment! */ + } + + /* Add-in last point. */ + newpts->pts[j++] = points->pts[points->npts - 1]; + newpts->npts = j; + + /* Compute the chain code for P (the list of points). */ + lialg_compute_unit_chain_code(newpts); + + return(newpts); +} + + +/* This implementation is due to Kenny Hoff. */ +static void lialg_bresline(pen_point *startpt, pen_point *endpt, + point_list *newpts, int *j) { + int Ax, Ay, Bx, By, dX, dY, Xincr, Yincr; + + Ax = startpt->x; + Ay = startpt->y; + Bx = endpt->x; + By = endpt->y; + + /* INITIALIZE THE COMPONENTS OF THE ALGORITHM THAT ARE NOT AFFECTED */ + /* BY THE SLOPE OR DIRECTION OF THE LINE */ + dX = abs(Bx-Ax); /* store the change in X and Y of the line endpoints */ + dY = abs(By-Ay); + + /* DETERMINE "DIRECTIONS" TO INCREMENT X AND Y (REGARDLESS OF DECISION) */ + if (Ax > Bx) { Xincr=-1; } else { Xincr=1; } /* which direction in X? */ + if (Ay > By) { Yincr=-1; } else { Yincr=1; } /* which direction in Y? */ + + /* DETERMINE INDEPENDENT VARIABLE (ONE THAT ALWAYS INCREMENTS BY 1 (OR -1) ) */ + /* AND INITIATE APPROPRIATE LINE DRAWING ROUTINE (BASED ON FIRST OCTANT */ + /* ALWAYS). THE X AND Y'S MAY BE FLIPPED IF Y IS THE INDEPENDENT VARIABLE. */ + if (dX >= dY) { /* if X is the independent variable */ + int dPr = dY<<1; /* amount to increment decision if right is chosen (always) */ + int dPru = dPr - (dX<<1); /* amount to increment decision if up is chosen */ + int P = dPr - dX; /* decision variable start value */ + + /* process each point in the line one at a time (just use dX) */ + for (; dX>=0; dX--) { + newpts->pts[*j].x = Ax; + newpts->pts[*j].y = Ay; + (*j)++; + + if (P > 0) { /* is the pixel going right AND up? */ + Ax+=Xincr; /* increment independent variable */ + Ay+=Yincr; /* increment dependent variable */ + P+=dPru; /* increment decision (for up) */ + } else { /* is the pixel just going right? */ + Ax+=Xincr; /* increment independent variable */ + P+=dPr; /* increment decision (for right) */ + } + } + } else { /* if Y is the independent variable */ + int dPr = dX<<1; /* amount to increment decision if right is chosen (always) */ + int dPru = dPr - (dY<<1); /* amount to increment decision if up is chosen */ + int P = dPr - dY; /* decision variable start value */ + + /* process each point in the line one at a time (just use dY) */ + for (; dY>=0; dY--) { + newpts->pts[*j].x = Ax; + newpts->pts[*j].y = Ay; + (*j)++; + + if (P > 0) { /* is the pixel going up AND right? */ + Ax+=Xincr; /* increment dependent variable */ + Ay+=Yincr; /* increment independent variable */ + P+=dPru; /* increment decision (for up) */ + } else { /* is the pixel just going up? */ + Ay+=Yincr; /* increment independent variable */ + P+=dPr; /* increment decision (for right) */ + } + } + } +} + +static void lialg_compute_chain_code(point_list *pts) { + int i; + + for (i = 0; i < (pts->npts - 1); i++) { + pen_point *startpt = &(pts->pts[i]); + pen_point *endpt = &(pts->pts[i+1]); + int dx = endpt->x - startpt->x; + int dy = endpt->y - startpt->y; + int tmp = quadr(likeatan(dy, dx)); + int dircode = (12 - tmp) % 8; + + startpt->chaincode = dircode; + } +} + + +static void lialg_compute_unit_chain_code(point_list *pts) { + int i; + + for (i = 0; i < (pts->npts - 1); i++) { + pen_point *startpt = &(pts->pts[i]); + pen_point *endpt = &(pts->pts[i+1]); + int dx = endpt->x - startpt->x; + int dy = endpt->y - startpt->y; + int dircode = lialg_dctbl[dx+1][dy+1]; + + startpt->chaincode = dircode; + } +} + + +static region_list *lialg_compute_regions(point_list *pts) { + region_list *regions; + region_list *curr_reg; + int *R[2 + LP_FILTER_ITERS]; + int *junk; + int *curr, *next; + int i, j; + + /* Initialize low-pass filter parameters if necessary. */ + if (lialg_lpfconst == -1) + lialg_compute_lpf_parameters(); + + /* Allocate a 2 x pts->npts array for use in computing the (filtered) Angle set, A_n. */ + junk = malloc((2 + LP_FILTER_ITERS) * pts->npts*sizeof(int)); + for (i = 0; i < (2 + LP_FILTER_ITERS); i++) + R[i] = junk + (i * pts->npts); + curr = R[0]; + + /* Compute the Angle set, A, in the first element of array R. */ + /* Values in R are in degrees, x 100. */ + curr[0] = 18000; /* a_0 */ + for (i = 1; i < (pts->npts - 1); i++) { + int d_i = pts->pts[i].chaincode; + int d_iminusone = pts->pts[i-1].chaincode; + int a_i; + + if (d_iminusone < d_i) + d_iminusone += 8; + + a_i = (d_iminusone - d_i) % 8; + + /* convert to degrees, x 100 */ + curr[i] = ((12 - a_i) % 8) * 45 * 100; + } + curr[pts->npts - 1] = 18000; /* a_L-1 */ + + /* Perform a number of filtering iterations. */ + next = R[1]; + for (j = 0; j < LP_FILTER_ITERS; j++, curr = R[j], next = R[j+1]) { + for (i = 0; i < pts->npts; i++) { + int k; + + next[i] = 0; + + for (k = i - LP_FILTER_WIDTH; k <= i + LP_FILTER_WIDTH; k++) { + int oldval = (k < 0 || k >= pts->npts) ? 18000 : curr[k]; + next[i] += oldval * lialg_lpfwts[k - (i - LP_FILTER_WIDTH)]; /* overflow? */ + } + + next[i] /= lialg_lpfconst; + } + } + + /* Do final thresholding around PI. */ + /* curr and next are set-up correctly at end of previous loop! */ + for (i = 0; i < pts->npts; i++) + next[i] = (abs(curr[i] - 18000) < LP_FILTER_THLD) ? 18000 : curr[i]; + curr = next; + + /* Debugging. */ + if (lidebug > 1) { + for (i = 0; i < pts->npts; i++) { + fprint(2, "%3d: (%P) %lud ", + i, pts->pts[i].Point, pts->pts[i].chaincode); + for (j = 0; j < 2 + LP_FILTER_ITERS; j++) + fprint(2, "%d ", R[j][i]); + fprint(2, "\n"); + } + } + + /* Do the region segmentation. */ + { + int start, end; + int currtype; + +#define RGN_TYPE(val) (((val)==18000)?RGN_PLAIN:((val)<18000?RGN_CONCAVE:RGN_CONVEX)) + + start = 0; + currtype = RGN_TYPE(curr[0]); + regions = malloc(sizeof(region_list)); + curr_reg = regions; + curr_reg->start = start; + curr_reg->end = 0; + curr_reg->type = currtype; + curr_reg->next = nil; + for (i = 1; i < pts->npts; i++) { + int nexttype = RGN_TYPE(curr[i]); + + if (nexttype != currtype) { + region_list *next_reg; + + end = i - 1; + curr_reg->end = end; + if (lidebug > 1) + fprint(2, " (%d, %d), %d\n", start, end, currtype); + + start = i; + currtype = nexttype; + next_reg = malloc(sizeof(region_list)); + next_reg->start = start; + next_reg->end = 0; + next_reg->type = nexttype; + next_reg->next = nil; + + curr_reg->next = next_reg; + curr_reg = next_reg; + } + } + end = i - 1; + curr_reg->end = end; + if (lidebug > 1) + fprint(2, " (%d, %d), %d\n", start, end, currtype); + + /* Filter out convex/concave regions that are too short. */ + for (curr_reg = regions; curr_reg; curr_reg = curr_reg->next) + if (curr_reg->type == RGN_PLAIN) { + region_list *next_reg; + + for (next_reg = curr_reg->next; + next_reg != nil && + (next_reg->end - next_reg->start) < LP_FILTER_MIN; + next_reg = curr_reg->next) { + /* next_reg must not be plain, and it must be followed by a plain */ + /* assert(next_reg->type != RGN_PLAIN); */ + /* assert(next_reg->next != nil && (next_reg->next)->type == RGN_PLAIN); */ + + curr_reg->next = (next_reg->next)->next; + curr_reg->end = (next_reg->next)->end; + + free(next_reg->next); + free(next_reg); + } + } + + /* Add-in pseudo-extremes. */ + { + region_list *tmp, *prev_reg; + + tmp = regions; + regions = nil; + prev_reg = nil; + for (curr_reg = tmp; curr_reg; curr_reg = curr_reg->next) { + if (curr_reg->type == RGN_PLAIN) { + int arclen = lialg_compute_pathlen_subset(pts, + curr_reg->start, + curr_reg->end); + int dx = pts->pts[curr_reg->end].x - + pts->pts[curr_reg->start].x; + int dy = pts->pts[curr_reg->end].y - + pts->pts[curr_reg->start].y; + int chordlen = isqrt(10000 * (dx * dx + dy * dy)); + int atcr = (chordlen == 0) ? 0 : (100 * arclen + chordlen / 2) / chordlen; + + if (lidebug) + fprint(2, "%d, %d, %d\n", arclen, chordlen, atcr); + + /* Split region if necessary. */ + if (arclen >= PE_AL_THLD && atcr >= PE_ATCR_THLD) { + int mid = curr_reg->start + (curr_reg->end - curr_reg->start) / 2; + int end = curr_reg->end; + region_list *saved_next = curr_reg->next; + + curr_reg->end = mid - 1; + if (prev_reg == nil) + regions = curr_reg; + else + prev_reg->next = curr_reg; + prev_reg = curr_reg; + + /* curr_reg = (region_list *)safe_malloc(sizeof(region_list));*/ + curr_reg = malloc(sizeof(region_list)); + curr_reg->start = mid; + curr_reg->end = mid; + curr_reg->type = RGN_PSEUDO; + curr_reg->next = nil; + prev_reg->next = curr_reg; + prev_reg = curr_reg; + + /* curr_reg = (region_list *)malloc(sizeof(region_list)); */ + curr_reg = malloc(sizeof(region_list)); + curr_reg->start = mid + 1; + curr_reg->end = end; + curr_reg->type = RGN_PLAIN; + curr_reg->next = nil; + prev_reg->next = curr_reg; + prev_reg = curr_reg; + + curr_reg->next = saved_next; + continue; + } + } + + if (prev_reg == nil) + regions = curr_reg; + else + prev_reg->next = curr_reg; + prev_reg = curr_reg; + } + } + } + + free(junk); + return(regions); +} + + +static point_list *lialg_compute_dompts(point_list *pts, region_list *regions) { + point_list *dpts; + int ndpts; + int *cas; + int nonplain; + region_list *r; + region_list *curr; + int dp; + int previx; + int currix; + + /* Compute contour angle set. */ + cas = lialg_compute_contour_angle_set(pts, regions); + + /* Dominant points include: start_pt, end_pt, extrema_of_non_plain_regions, midpts of the preceding. */ + nonplain = 0; + for (r = regions; r != nil; r = r->next) + if (r->type != RGN_PLAIN) + nonplain++; + ndpts = 2 * (2 + nonplain) - 1; + /* dpts = (point_list *)safe_malloc(sizeof(point_list)); */ + dpts = malloc(sizeof(point_list)); + dpts->pts = mallocz(ndpts*sizeof(pen_point), 1); + if (dpts->pts == nil) { + free(dpts); + return(nil); + } + dpts->npts = ndpts; + dpts->next = nil; + + /* Pick out dominant points. */ + + /* Record start point. */ + dp = 0; + previx = 0; + dpts->pts[dp++] = pts->pts[previx]; + + for (curr = regions; curr != nil; curr = curr->next) + if (curr->type != RGN_PLAIN) { + int max_v = 0; + int min_v = 0x7fffffff; /* maxint */ + int max_ix = -1; + int min_ix = -1; + int i; + + for (i = curr->start; i <= curr->end; i++) { + int v = cas[i]; + if (v > max_v) { max_v = v; max_ix = i; } + if (v < min_v) { min_v = v; min_ix = i; } + if (lidebug > 1) + fprint(2, " %d\n", v); + } + + currix = (curr->type == RGN_CONVEX ? max_ix : min_ix); + + /* Record midpoint. */ + dpts->pts[dp++] = pts->pts[previx + (currix - previx) / 2]; + + /* Record extreme point. */ + dpts->pts[dp++] = pts->pts[currix]; + + previx = currix; + } + + /* Record last mid-point and end point. */ + currix = pts->npts - 1; + dpts->pts[dp++] = pts->pts[previx + (currix - previx) / 2]; + dpts->pts[dp] = pts->pts[currix]; + + /* Compute chain-code. */ + lialg_compute_chain_code(dpts); + + free(cas); + return(dpts); +} + + +static int *lialg_compute_contour_angle_set(point_list *pts, + region_list *regions) { + int *V; + region_list *curr_reg; + int i; + + V = malloc(pts->npts*sizeof(int)); + + V[0] = 18000; + for (curr_reg = regions; curr_reg != nil; curr_reg = curr_reg->next) { + for (i = curr_reg->start; i <= curr_reg->end; i++) { + if (curr_reg->type == RGN_PLAIN) { + V[i] = 18000; + } else { + /* For now, simply choose the mid-point. */ + int isMidPt = i == (curr_reg->start + + (curr_reg->end - curr_reg->start) / 2); + V[i] = (curr_reg->type == RGN_CONVEX) + ? (isMidPt ? 18000 : 0) + : (isMidPt ? 0 : 18000); + } + } + } + V[pts->npts - 1] = 18000; + + return(V); +} + + +/* + * First compute the similarity between the two strings. + * If it's above a threshold, compute the distance between + * the two and return it as the ``score.'' + * Otherwise, return the constant WORST_SCORE. + * + */ +static void lialg_score_stroke(point_list *input_dompts, point_list *curr_dompts, int *sim, int *dist) { + *sim = MIN_SIM; + *dist = MAX_DIST; + + *sim = lialg_compute_similarity(input_dompts, curr_dompts); + if (*sim < SIM_THLD) goto done; + + *dist = lialg_compute_distance(input_dompts, curr_dompts); + +done: + if (lidebug) + fprint(2, "%d, %d\n", *sim, *dist); +} + + +static int lialg_compute_similarity(point_list *input_dompts, point_list *curr_dompts) { + int sim; + point_list *A, *B; + int N, M; + int **G; + int *junk; + int i, j; + + /* A is the longer sequence, length N. */ + /* B is the shorter sequence, length M. */ + if (input_dompts->npts >= curr_dompts->npts) { + A = input_dompts; + N = input_dompts->npts; + B = curr_dompts; + M = curr_dompts->npts; + } else { + A = curr_dompts; + N = curr_dompts->npts; + B = input_dompts; + M = input_dompts->npts; + } + + /* Allocate and initialize the Gain matrix, G. */ + /* The size of G is M x (N + 1). */ + /* Note that row 0 is unused. */ + /* Similarities are x 10. */ + G = malloc(M*sizeof(int *)); + junk = malloc(M * (N + 1) * sizeof(int)); + for (i = 0; i < M; i++) + G[i] = junk + (i * (N + 1)); + + for (i = 1; i < M; i++) { + int bval = B->pts[i-1].chaincode; + + /* Source column. */ + G[i][0] = 0; + + for (j = 1; j < N; j++) { + int aval = A->pts[j-1].chaincode; + int diff = abs(bval - aval); + if (diff > 4) diff = 8 - diff; + + G[i][j] = (diff == 0) + ? 10 + : (diff == 1) + ? 6 + : 0; + } + + /* Sink column. */ + G[i][N] = 0; + } + + /* Do the DP algorithm. */ + /* Proceed in column order, from highest column to the lowest. */ + /* Within each column, proceed from the highest row to the lowest. */ + /* Skip the highest column. */ + for (j = N - 1; j >= 0; j--) + for (i = M - 1; i > 0; i--) { + int max = G[i][j + 1]; + + if (i < (M - 1)) { + int tmp = G[i + 1][j + 1]; + if (tmp > max) max = tmp; + } + + G[i][j] += max; + } + + sim = (10 * G[1][0] + (N - 1) / 2) / (N - 1); + + if (G != nil) + free(G); + if (junk != nil) + free(junk); + return(sim); +} + + +static int lialg_compute_distance(point_list *input_dompts, + point_list *curr_dompts) { + int dist; + point_list *A, *B; + int N, M; + int **C; + int *junk; + int *BE; + int *TE; + int i, j; + + /* A is the longer sequence, length N. */ + /* B is the shorter sequence, length M. */ + if (input_dompts->npts >= curr_dompts->npts) { + A = input_dompts; + N = input_dompts->npts; + B = curr_dompts; + M = curr_dompts->npts; + } + else { + A = curr_dompts; + N = curr_dompts->npts; + B = input_dompts; + M = input_dompts->npts; + } + + /* Construct the helper vectors, BE and TE, which say for each column */ + /* what are the ``bottom'' and ``top'' rows of interest. */ + BE = malloc((N + 1)*sizeof(int)); + TE = malloc((N + 1)*sizeof(int)); + + for (j = 1; j <= N; j++) { + int bot, top; + + bot = j + (M - DP_BAND); + if (bot > M) bot = M; + BE[j] = bot; + + top = j - (N - DP_BAND); + if (top < 1) top = 1; + TE[j] = top; + } + + /* Allocate and initialize the Cost matrix, C. */ + /* The size of C is (M + 1) x (N + 1). */ + /* Note that row and column 0 are unused. */ + /* Costs are x 100. */ + /* C = (int **)safe_malloc((M + 1) * sizeof(int *)); */ + C = malloc((M + 1)*sizeof( int *)); + junk = malloc((M + 1) * (N + 1)*sizeof(int)); + for (i = 0; i <= M; i++) + C[i] = junk + (i * (N + 1)); + + for (i = 1; i <= M; i++) { + int bx = B->pts[i-1].x; + int by = B->pts[i-1].y; + + for (j = 1; j <= N; j++) { + int ax = A->pts[j-1].x; + int ay = A->pts[j-1].y; + int dx = bx - ax; + int dy = by - ay; + int dist = isqrt(10000 * (dx * dx + dy * dy)); + + C[i][j] = dist; + } + } + + /* Do the DP algorithm. */ + /* Proceed in column order, from highest column to the lowest. */ + /* Within each column, proceed from the highest row to the lowest. */ + for (j = N; j > 0; j--) + for (i = M; i > 0; i--) { + int min = MAX_DIST; + + if (i > BE[j] || i < TE[j] || (j == N && i == M)) + continue; + + if (j < N) { + if (i >= TE[j+1]) { + int tmp = C[i][j+1]; + if (tmp < min) + min = tmp; + } + + if (i < M) { + int tmp = C[i+1][j+1]; + if (tmp < min) + min = tmp; + } + } + + if (i < BE[j]) { + int tmp = C[i+1][j]; + if (tmp < min) min = tmp; + } + + C[i][j] += min; + } + + dist = (C[1][1] + N / 2) / N; + + if (C != nil) free(C); + if (junk != nil) free(junk); + if (BE != nil) free(BE); + if (TE != nil) free(TE); + return(dist); +} + + +/************************************************************* + + Digest-processing routines + + *************************************************************/ + +static int lialg_read_classifier_digest(rClassifier *rec) { + int nclasses; + FILE *fp; + + /* Try to open the corresponding digest file. */ + { + char *clx_path; + char *dot; + + /* Get a copy of the filename, with some room on the end. */ + /* clx_path = safe_malloc(strlen(rec->file_name) + 5); */ + clx_path = malloc((strlen(rec->file_name) + 5) *sizeof(char)); + strcpy(clx_path, rec->file_name); + + /* Truncate the path after the last dot. */ + dot = strrchr(clx_path, '.'); + if (dot == nil) { free(clx_path); return(-1); } + *(dot + 1) = 0; + + /* Append the classifier-digest extension. */ + strcat(clx_path, "clx"); + + fp = fopen(clx_path, "r"); + if (fp == nil) { + free(clx_path); + return(-1); + } + + free(clx_path); + } + + /* Read-in the name and dominant points for each class. */ + for (nclasses = 0; !feof(fp); nclasses++) { + point_list *dpts = nil; + char class[BUFSIZ]; + int npts; + int j; + + if (fscanf(fp, "%s %d", class, &npts) != 2) { + if (feof(fp)) break; + + goto failed; + } + rec->cnames[nclasses] = strdup(class); + + /* Allocate a dominant-points list. */ + /* dpts = (point_list *)safe_malloc(sizeof(point_list)); */ + dpts = malloc(sizeof(point_list)); + dpts->pts = mallocz(npts*sizeof(pen_point), 1); + if (dpts->pts == nil) goto failed; + dpts->npts = npts; + dpts->next = nil; + + /* Read in each point. */ + for (j = 0; j < npts; j++) { + int x, y; + + if (fscanf(fp, "%d %d", &x, &y) != 2) goto failed; + dpts->pts[j].x = x; + dpts->pts[j].y = y; + } + + /* Compute the chain-code. */ + lialg_compute_chain_code(dpts); + + /* Store the list in the rec data structure. */ + rec->dompts[nclasses] = dpts; + + continue; + +failed: + fprint(2, "read_classifier_digest failed...\n"); + for (; nclasses >= 0; nclasses--) { + if (rec->cnames[nclasses] != nil) { + free(rec->cnames[nclasses]); + rec->cnames[nclasses] = nil; + } + if (rec->dompts[nclasses] != nil) { + delete_examples(rec->dompts[nclasses]); + rec->dompts[nclasses] = nil; + } + } + if (dpts != nil) + delete_examples(dpts); + fclose(fp); + return(-1); + } + + fclose(fp); + return(0); +} + + +/************************************************************* + + Canonicalization routines + + *************************************************************/ + +static int lialg_canonicalize_examples(rClassifier *rec) { + int i; + int nclasses; + + if (lidebug) { + fprint(2, "lialg_canonicalize_examples working on %s\n", + rec->file_name); + } + /* Initialize canonical-example arrays. */ + for (i = 0; i < MAXSCLASSES; i++) { + rec->canonex[i] = nil; + } + + /* Figure out number of classes. */ + for (nclasses = 0; + nclasses < MAXSCLASSES && rec->cnames[nclasses] != nil; + nclasses++) + ; + + /* Canonicalize the examples for each class. */ + for (i = 0; i < nclasses; i++) { + int j, k; + int nex; + point_list *pts, *tmp, *avg; + int maxxrange, maxyrange; + int minx, miny, maxx, maxy; + int avgxrange, avgyrange, avgxoff, avgyoff, avgscale; + + + if (lidebug) { + fprint(2, "lialg_canonicalize_examples working on class %s\n", + rec->cnames[i]); + } + /* Make a copy of the examples. */ + pts = nil; + tmp = rec->ex[i]; + for (nex = 0; tmp != nil; nex++, tmp = tmp->next) { + if ((pts = add_example(pts, tmp->npts, tmp->pts)) == nil) { + delete_examples(pts); + return(-1); + } + } + + /* Canonicalize each example, and derive the max x and y ranges. */ + maxxrange = 0; + maxyrange = 0; + for (j = 0, tmp = pts; j < nex; j++, tmp = tmp->next) { + if (lialg_canonicalize_example_stroke(tmp) != 0) { + if (lidebug) { + fprint(2, "lialg_canonicalize_example_stroke returned error\n"); + } + return(-1); + } + + if (tmp->xrange > maxxrange) maxxrange = tmp->xrange; + if (tmp->yrange > maxyrange) maxyrange = tmp->yrange; + } + + /* Normalize max ranges. */ + if (((100 * maxxrange + CANONICAL_X / 2) / CANONICAL_X) > + ((100 * maxyrange + CANONICAL_Y / 2) / CANONICAL_Y)) { + maxyrange = (maxyrange * CANONICAL_X + maxxrange / 2) / maxxrange; + maxxrange = CANONICAL_X; + } + else { + maxxrange = (maxxrange * CANONICAL_Y + maxyrange / 2) / maxyrange; + maxyrange = CANONICAL_Y; + } + + /* Re-scale each example to max ranges. */ + for (j = 0, tmp = pts; j < nex; j++, tmp = tmp->next) { + int scalex = (tmp->xrange == 0) ? 100 : (100 * maxxrange + tmp->xrange / 2) / tmp->xrange; + int scaley = (tmp->yrange == 0) ? 100 : (100 * maxyrange + tmp->yrange / 2) / tmp->yrange; + if (lialg_translate_points(tmp, 0, 0, scalex, scaley) != 0) { + delete_examples(pts); + return(-1); + } + } + + /* Average the examples; leave average in first example. */ + avg = pts; /* careful aliasing!! */ + for (k = 0; k < NCANONICAL; k++) { + int xsum = 0; + int ysum = 0; + + for (j = 0, tmp = pts; j < nex; j++, tmp = tmp->next) { + xsum += tmp->pts[k].x; + ysum += tmp->pts[k].y; + } + + avg->pts[k].x = (xsum + j / 2) / j; + avg->pts[k].y = (ysum + j / 2) / j; + } + + /* Compute BB of averaged stroke and re-scale. */ + lialg_get_bounding_box(avg, &minx, &miny, &maxx, &maxy); + avgxrange = maxx - minx; + avgyrange = maxy - miny; + avgscale = (((100 * avgxrange + CANONICAL_X / 2) / CANONICAL_X) > + ((100 * avgyrange + CANONICAL_Y / 2) / CANONICAL_Y)) + ? (100 * CANONICAL_X + avgxrange / 2) / avgxrange + : (100 * CANONICAL_Y + avgyrange / 2) / avgyrange; + if (lialg_translate_points(avg, minx, miny, avgscale, avgscale) != 0) { + delete_examples(pts); + return(-1); + } + + /* Re-compute the x and y ranges and center the stroke. */ + lialg_get_bounding_box(avg, &minx, &miny, &maxx, &maxy); + avgxrange = maxx - minx; + avgyrange = maxy - miny; + avgxoff = -((CANONICAL_X - avgxrange + 1) / 2); + avgyoff = -((CANONICAL_Y - avgyrange + 1) / 2); + if (lialg_translate_points(avg, avgxoff, avgyoff, 100, 100) != 0) { + delete_examples(pts); + return(-1); + } + + /* Create a point list to serve as the ``canonical representation. */ + if ((rec->canonex[i] = add_example(nil, avg->npts, avg->pts)) == nil) { + delete_examples(pts); + return(-1); + } + (rec->canonex[i])->xrange = maxx - minx; + (rec->canonex[i])->yrange = maxy - miny; + + if (lidebug) { + fprint(2, "%s, avgpts = %d\n", rec->cnames[i], avg->npts); + for (j = 0; j < avg->npts; j++) { + fprint(2, " (%P)\n", avg->pts[j].Point); + } + } + + /* Compute dominant points of canonical representation. */ + rec->dompts[i] = lialg_compute_dominant_points(avg); + + /* Clean up. */ + delete_examples(pts); + } + + /* Sanity check. */ + for (i = 0; i < nclasses; i++) { + char *best_name = lialg_recognize_stroke(rec, rec->canonex[i]); + + if (best_name != rec->cnames[i]) + fprint(2, "%s, best = %s\n", rec->cnames[i], best_name); + } + + return(0); +} + + +static int lialg_canonicalize_example_stroke(point_list *points) { + int minx, miny, maxx, maxy, xrange, yrange, scale; + + /* Filter out points that are too close. */ + if (lialg_filter_points(points) != 0) return(-1); + + /* Must be at least two points! */ + if (points->npts < 2) { + if (lidebug) { + fprint(2, "lialg_canonicalize_example_stroke: npts=%d\n", + points->npts); + } + return(-1); + } + + /* Scale up to avoid conversion errors. */ + lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy); + xrange = maxx - minx; + yrange = maxy - miny; + scale = (((100 * xrange + CANONICAL_X / 2) / CANONICAL_X) > + ((100 * yrange + CANONICAL_Y / 2) / CANONICAL_Y)) + ? (100 * CANONICAL_X + xrange / 2) / xrange + : (100 * CANONICAL_Y + yrange / 2) / yrange; + if (lialg_translate_points(points, minx, miny, scale, scale) != 0) { + if (lidebug) { + fprint(2, "lialg_translate_points (minx=%d,miny=%d,scale=%d) returned error\n", minx, miny, scale); + } + return(-1); + } + + /* Compute an equivalent stroke with equi-distant points. */ + if (lialg_compute_equipoints(points) != 0) return(-1); + + /* Re-translate the points to the origin. */ + lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy); + if (lialg_translate_points(points, minx, miny, 100, 100) != 0) { + if (lidebug) { + fprint(2, "lialg_translate_points (minx=%d,miny=%d) returned error\n", minx, miny); + } + return(-1); + } + + /* Store the x and y ranges in the point list. */ + xrange = maxx - minx; + yrange = maxy - miny; + points->xrange = xrange; + points->yrange = yrange; + + if (lidebug) { + int i; + fprint(2, "Canonicalized: %d, %d, %d, %d\n", minx, miny, maxx, maxy); + for (i = 0; i < points->npts; i++) + fprint(2, " (%P)\n", points->pts[i].Point); + fflush(stderr); + } + + return(0); +} + + +static int lialg_compute_equipoints(point_list *points) { + pen_point *equipoints = mallocz(NCANONICAL*sizeof(pen_point), 1); + int nequipoints = 0; + int pathlen = lialg_compute_pathlen(points); + int equidist = (pathlen + (NCANONICAL - 1) / 2) / (NCANONICAL - 1); + int i; + int dist_since_last_eqpt; + int remaining_seglen; + int dist_to_next_eqpt; + + if (equipoints == nil) { + fprint(2, "can't allocate memory in lialg_compute_equipoints"); + return(-1); + } + + if (lidebug) { + fprint(2, "compute_equipoints: npts = %d, pathlen = %d, equidist = %d\n", + points->npts, pathlen, equidist); + fflush(stderr); + } + + /* First original point is an equipoint. */ + equipoints[0] = points->pts[0]; + nequipoints++; + dist_since_last_eqpt = 0; + + for (i = 1; i < points->npts; i++) { + int dx1 = points->pts[i].x - points->pts[i-1].x; + int dy1 = points->pts[i].y - points->pts[i-1].y; + int endx = 100 * points->pts[i-1].x; + int endy = 100 * points->pts[i-1].y; + remaining_seglen = isqrt(10000 * (dx1 * dx1 + dy1 * dy1)); + dist_to_next_eqpt = equidist - dist_since_last_eqpt; + + while (remaining_seglen >= dist_to_next_eqpt) { + if (dx1 == 0) { + /* x-coordinate stays the same */ + if (dy1 >= 0) + endy += dist_to_next_eqpt; + else + endy -= dist_to_next_eqpt; + } + else { + int slope = (100 * dy1 + dx1 / 2) / dx1; + int tmp = isqrt(10000 + slope * slope); + int dx = (100 * dist_to_next_eqpt + tmp / 2) / tmp; + int dy = (slope * dx + 50) / 100; + + if (dy < 0) dy = -dy; + if (dx1 >= 0) + endx += dx; + else + endx -= dx; + if (dy1 >= 0) + endy += dy; + else + endy -= dy; + } + + equipoints[nequipoints].x = (endx + 50) / 100; + equipoints[nequipoints].y = (endy + 50) / 100; + nequipoints++; +/* assert(nequipoints <= NCANONICAL);*/ + dist_since_last_eqpt = 0; + remaining_seglen -= dist_to_next_eqpt; + dist_to_next_eqpt = equidist; + } + + dist_since_last_eqpt += remaining_seglen; + } + + /* Take care of last equipoint. */ + if (nequipoints == NCANONICAL) { + /* Good. */ + } else if (nequipoints == (NCANONICAL - 1)) { + /* Make last original point the last equipoint. */ + equipoints[nequipoints] = points->pts[points->npts - 1]; + } else { + if (lidebug) { + fprint(2,"lialg_compute_equipoints: nequipoints = %d\n", + nequipoints); + } +/* assert(false);*/ + return(-1); + } + + points->npts = NCANONICAL; + delete_pen_point_array(points->pts); + points->pts = equipoints; + return(0); +} + + +/************************************************************* + + Utility routines + + *************************************************************/ + +/* Result is x 100. */ +static int lialg_compute_pathlen(point_list *points) { + return(lialg_compute_pathlen_subset(points, 0, points->npts - 1)); +} + + +/* Result is x 100. */ +static int lialg_compute_pathlen_subset(point_list *points, + int start, int end) { + int pathlen; + int i; + + pathlen = 0; + for (i = start + 1; i <= end; i++) { + int dx = points->pts[i].x - points->pts[i-1].x; + int dy = points->pts[i].y - points->pts[i-1].y; + int dist = isqrt(10000 * (dx * dx + dy * dy)); + pathlen += dist; + } + + return(pathlen); +} + + +/* Note that this does NOT update points->xrange and points->yrange! */ +static int lialg_filter_points(point_list *points) { + int filtered_npts; + pen_point *filtered_pts = mallocz(points->npts*sizeof(pen_point), 1); + int i; + + if (filtered_pts == nil) { + fprint(2, "can't allocate memory in lialg_filter_points"); + return(-1); + } + + filtered_pts[0] = points->pts[0]; + filtered_npts = 1; + for (i = 1; i < points->npts; i++) { + int j = filtered_npts - 1; + int dx = points->pts[i].x - filtered_pts[j].x; + int dy = points->pts[i].y - filtered_pts[j].y; + int magsq = dx * dx + dy * dy; + + if (magsq >= DIST_SQ_THRESHOLD) { + filtered_pts[filtered_npts] = points->pts[i]; + filtered_npts++; + } + } + + points->npts = filtered_npts; + delete_pen_point_array(points->pts); + points->pts = filtered_pts; + return(0); +} + + +/* scalex and scaley are x 100. */ +/* Note that this does NOT update points->xrange and points->yrange! */ +static int lialg_translate_points(point_list *points, + int minx, int miny, + int scalex, int scaley) { + int i; + + for (i = 0; i < points->npts; i++) { + points->pts[i].x = ((points->pts[i].x - minx) * scalex + 50) / 100; + points->pts[i].y = ((points->pts[i].y - miny) * scaley + 50) / 100; + } + + return(0); +} + + +static void lialg_get_bounding_box(point_list *points, + int *pminx, int *pminy, + int *pmaxx, int *pmaxy) { + int minx, miny, maxx, maxy; + int i; + + minx = maxx = points->pts[0].x; + miny = maxy = points->pts[0].y; + for (i = 1; i < points->npts; i++) { + pen_point *pt = &(points->pts[i]); + if (pt->x < minx) minx = pt->x; + else if (pt->x > maxx) maxx = pt->x; + if (pt->y < miny) miny = pt->y; + else if (pt->y > maxy) maxy = pt->y; + } + + *pminx = minx; + *pminy = miny; + *pmaxx = maxx; + *pmaxy = maxy; +} + + +int wtvals[] = {100, 104, 117, 143, 189, 271, 422}; + +static void lialg_compute_lpf_parameters(void) { + int i; + + for (i = LP_FILTER_WIDTH; i >= 0; i--) { +// double x = 0.04 * (i * i); +// double tmp = 100.0 * exp(x); +// int wt = floor((double)tmp); + int wt = wtvals[i]; + lialg_lpfwts[LP_FILTER_WIDTH - i] = wt; + lialg_lpfwts[LP_FILTER_WIDTH + i] = wt; + } + lialg_lpfconst = 0; + for (i = 0; i < (2 * LP_FILTER_WIDTH + 1); i++) { + lialg_lpfconst += lialg_lpfwts[i]; + } +} + + +/* Code from Joseph Hall (jnhall@sat.mot.com). */ +static int isqrt(int n) { + register int i; + register long k0, k1, nn; + + for (nn = i = n, k0 = 2; i > 0; i >>= 2, k0 <<= 1) + ; + nn <<= 2; + for (;;) { + k1 = (nn / k0 + k0) >> 1; + if (((k0 ^ k1) & ~1) == 0) + break; + k0 = k1; + } + return (int) ((k1 + 1) >> 1); +} + + +/* Helper routines from Mark Hayter. */ +static int likeatan(int tantop, int tanbot) { + int t; + /* Use tan(theta)=top/bot --> order for t */ + /* t in range 0..0x40000 */ + + if ((tantop == 0) && (tanbot == 0)) + t = 0; + else + { + t = (tantop << 16) / (abs(tantop) + abs(tanbot)); + if (tanbot < 0) + t = 0x20000 - t; + else + if (tantop < 0) t = 0x40000 + t; + } + return t; +} + + +static int quadr(int t) { + return (8 - (((t + 0x4000) >> 15) & 7)) & 7; +} |