chiark / gitweb /
Do not flush in ocr debug if debug not enabled
[ypp-sc-tools.web-live.git] / pctb / ocr.c
index 27271c7..92b0903 100644 (file)
@@ -36,7 +36,7 @@ typedef struct {
 typedef struct DatabaseNode {
   char *str;
   int nlinks, alinks;
-  unsigned match:1, defined:1, endsword:1;
+  unsigned match:1, defined:1, endsword:1, local:1;
   DatabaseLink *links;
 } DatabaseNode;
 
@@ -44,7 +44,7 @@ typedef struct {
   OcrReader *rd;
   OcrCellType ct;
   int w;
-  Pixcol *cols;
+  const Pixcol *cols;
   int x;
   unsigned ctxmap;
 } FindCharArgs;
@@ -55,8 +55,8 @@ typedef struct {
 
 #define FOR_EACH_CONTEXT(EACH)                 \
   EACH(Word)                                   \
-  EACH(Upper)                                  \
   EACH(Lower)                                  \
+  EACH(Upper)                                  \
   EACH(Digit)
 
 #define FEC_ENUM(Context) ct_##Context,
@@ -83,6 +83,8 @@ struct OcrCellTypeInfo {
 struct OcrReader {
   int h;
   DatabaseNode contexts[NCONTEXTS];
+  char *result;
+  int lresult, aresult;
   OcrResultGlyph *results;
   int aresults, nresults;
 };
@@ -99,7 +101,7 @@ static void cleardb_node(DatabaseNode *n) {
     cleardb_node(n->links[i].then);
 }
 
-static void readdb1(OcrReader *rd, const char *which);
+static void readdb1(OcrReader *rd, const char *which, int local);
 
 static void readdb(OcrReader *rd) {
   int ctxi;
@@ -107,11 +109,11 @@ static void readdb(OcrReader *rd) {
   for (ctxi=0; ctxi<NCONTEXTS; ctxi++)
     cleardb_node(&rd->contexts[ctxi]);
 
-  readdb1(rd, "master");
-  readdb1(rd, "local");
+  readdb1(rd, "master", 0);
+  readdb1(rd, "local",  1);
 }
 
-static void readdb1(OcrReader *rd, const char *which) {
+static void readdb1(OcrReader *rd, const char *which, int local) {
   int nchrs;
   DatabaseNode *current, *additional;
   char chrs[100];
@@ -120,14 +122,17 @@ static void readdb1(OcrReader *rd, const char *which) {
   int h, endsword;
   char lbuf[100];
 
-  char *dbfname= masprintf("%s/#%s-char%d#.txt",
+  char *dbfname= masprintf("%s/_%s-char%d.txt",
                           get_vardir(), which, rd->h);
   
   if (!dbfile_open(dbfname))
     goto x;
 
+  if (local)
+    o_flags |= ff_charset_havelocal;
+
   FGETSLINE;
-  dbassert(!strcmp(lbuf,"# ypp-sc-tools pctb font v1"));
+  dbassert(!strcmp(lbuf,"# ypp-sc-tools pctb font v3 depth=" STRING(AADEPTH)));
 
   dbassert( dbfile_scanf("%d", &h) == 1);
   dbassert(h==rd->h);
@@ -161,12 +166,20 @@ static void readdb1(OcrReader *rd, const char *which) {
     for (;;) {
       FGETSLINE;
       if (!lbuf[0]) { dbassert(current != &rd->contexts[ctxi]); break; }
-      char *ep;
-      cv= strtoul(lbuf,&ep,16);  dbassert(!*ep);
-      dbassert(!(cv & ~((1UL << rd->h)-1)));
+      dbassert( strlen(lbuf) == rd->h );
+      FILLZERO(cv);
+      int y;
+      for (y=0; y<h; y++) {
+       char pb[2];
+       pb[0]= lbuf[y];
+       pb[1]= 0;
+       char *ep;
+       unsigned pv= strtoul(pb,&ep,16);  dbassert(!*ep);
+       pixcol_p_add(&cv, y, pv);
+      }
       
       for (j=0; j<current->nlinks; j++)
-       if (current->links[j].col == cv) {
+       if (!pixcol_cmp(&cv, &current->links[j].col)) {
          current= current->links[j].then;
          goto found_link;
        }
@@ -197,6 +210,7 @@ static void readdb1(OcrReader *rd, const char *which) {
       current->str= 0;
       current->defined= 1;
       current->match= 0;
+      current->local= local;
 
       if (nchrs) {
        current->str= mmalloc(nchrs+1);
@@ -212,6 +226,84 @@ static void readdb1(OcrReader *rd, const char *which) {
   free(dbfname);
 }
 
+typedef struct Rejection Rejection;
+struct Rejection {
+  struct Rejection *next;
+  const char *fname;
+  int lno;
+  pcre *re;
+};
+
+Rejection *rejections;
+
+static void load_rejections(const char *which) {
+  char lbuf[1000];
+  char *fname= masprintf("%s/_%s-reject.txt", get_vardir(), which);
+  int c, lno=0;
+  Rejection *rej;
+  
+  if (!dbfile_open(fname)) { free(fname); return; }
+  
+  while ((c= fgetc(dbfile))!=EOF) {
+    ungetc(c,dbfile);
+    lno++;
+    dbfile_getsline(lbuf,sizeof(lbuf),fname,lno);
+
+    if (!lbuf[0] || isspace(lbuf[0] || lbuf[0]=='#'))
+      continue;
+
+    rej= mmalloc(sizeof(*rej));
+    rej->next= rejections;
+    rej->fname= fname;
+    rej->lno= lno;
+
+    const char *err;
+    int erroffset;
+    rej->re= pcre_compile(lbuf, PCRE_NO_AUTO_CAPTURE|PCRE_UTF8,
+                         &err, &erroffset, 0);
+    if (!rej->re) {
+      char *what= masprintf("invalid regexp at offset %d: %s\n",
+                           erroffset, err);
+      dbfile_assertfail(fname, lno, what);
+    }
+    debugf("OCR LOADED REJECTION %s:%d `%s' %p\n", fname,lno, lbuf, rej->re);
+
+    rejections= rej;
+  }
+  sysassert(feof(dbfile));
+  dbfile_close();
+}
+  
+static int should_reject(OcrReader *rd) {
+  static int rejections_loaded;
+  int ovector[30];
+
+  if (!rejections_loaded) {
+    fetch_with_rsync("reject");
+    load_rejections("master");
+    load_rejections("local");
+    rejections_loaded=1;
+  }
+
+  debugf("[OCR REJECTION `%s'%d", rd->result, rd->lresult);
+  Rejection *rej;
+  for (rej=rejections; rej; rej=rej->next) {
+    debugf(" (%p)", rej);
+
+    int res= pcre_exec(rej->re, 0, rd->result,rd->lresult, 0,
+                      0, ovector, ARRAYSIZE(ovector));
+    if (res==PCRE_ERROR_NOMATCH) continue;
+    sysassert(res>0);
+
+    debugf(" MATCH]\n");
+    fprintf(stderr,"Rejecting OCR result `%s' (due to %s:%d)\n",
+           rd->result, rej->fname,rej->lno);
+    return 1;
+  }
+  debugf(" OK]");
+  return 0;
+}
+
 static void cu_pr_ctxmap(FILE *resolver, unsigned ctxmap) {
   fprintf(resolver,"{");
   const char *spc="";
@@ -224,15 +316,17 @@ static void cu_pr_ctxmap(FILE *resolver, unsigned ctxmap) {
   fprintf(resolver,"}");
 }
 
-static void callout_unknown(OcrReader *rd, int w, Pixcol cols[],
+static void callout_unknown(OcrReader *rd, int w, const Pixcol cols[],
                            int unk_l, int unk_r, unsigned unk_ctxmap) {
   int c,i, x,y;
   const OcrResultGlyph *s;
   const char *p;
-  Pixcol pv;
 
   FILE *resolver= resolve_start();
-  if (!resolver || !(o_flags & ff_editcharset))
+  if (!resolver ||
+      !((o_flags & ff_charset_edit) ||
+       ((o_flags & ff_charset_allowedit) &&
+        (o_flags & ff_charset_havelocal))))
     fatal("OCR failed - unrecognised characters or ligatures.\n"
          "Character set database needs to be updated or augmented.\n"
          "See README.charset.\n");
@@ -246,7 +340,7 @@ static void callout_unknown(OcrReader *rd, int w, Pixcol cols[],
     fprintf(resolver," %d %d ",s->l,s->r);
     cu_pr_ctxmap(resolver, 1u << s->match);
     fprintf(resolver," ");
-    cu_pr_ctxmap(resolver, 1u << s->ctxmap);
+    cu_pr_ctxmap(resolver, s->ctxmap);
     fprintf(resolver," ");
     for (p=s->s; (c= *p); p++) {
       if (c=='\\') fprintf(resolver,"\\%c",c);
@@ -257,20 +351,12 @@ static void callout_unknown(OcrReader *rd, int w, Pixcol cols[],
   fputc('\n',resolver);
 
   fprintf(resolver,
-         "/* XPM */\n"
-         "static char *t[] = {\n"
-         "/* columns rows colors chars-per-pixel */\n"
-         "\"%d %d 2 1\",\n"
-         "\"  c black\",\n"
-         "\"o c white\",\n",
-         w,rd->h);
-  for (y=0, pv=1; y<rd->h; y++, pv<<=1) {
-    fputc('"',resolver);
+         "P2\n%d %d %d\n", w, rd->h, AAMAXVAL);
+  for (y=0; y<rd->h; y++) {
     for (x=0; x<w; x++)
-      fputc(cols[x] & pv ? 'o' : ' ', resolver);
-    fputs("\",\n",resolver);
+      fprintf(resolver, " %d", pixcol_p_get(&cols[x], y));
+    fputc('\n',resolver);
   }
-  fputs("};\n",resolver);
 
   resolve_finish();
   readdb(rd);
@@ -288,6 +374,18 @@ static void add_result(OcrReader *rd, const char *s, int l, int r,
   rd->results[rd->nresults].match= match;
   rd->results[rd->nresults].ctxmap= ctxmap;
   rd->nresults++;
+
+  if (!s) return; /* just the sentinel for the caller */
+
+  int sl= strlen(s);
+  int newlresult= rd->lresult + sl;
+  if (newlresult >= rd->aresult) {
+    rd->aresult= (newlresult << 1) + 1;
+    rd->result= mrealloc(rd->result, rd->aresult);
+  }
+  memcpy(rd->result + rd->lresult, s, sl);
+  rd->lresult= newlresult;
+  rd->result[rd->lresult]= 0;
 }
 
 
@@ -299,13 +397,13 @@ static DatabaseNode *findchar_1ctx(const FindCharArgs *fca,
   int x= fca->x;
 
   for (;;) {
-    debug_flush();
+    if (DEBUGP(ocr)) debug_flush();
     debugf(" | x=%d",x);
     if (x > fca->w) break;
     Pixcol cv= fca->cols[x];
-    debugf(" cv=%"PSPIXCOL(PRIx),cv);
+    debugf(" cv="PIXCOL_PRFMT, PIXCOL_PRVAL(cv));
     for (i=0; i<current->nlinks; i++)
-      if (current->links[i].col == cv)
+      if (!pixcol_cmp(&cv, &current->links[i].col))
        goto found;
     /* not found */
     debugf(" ?");
@@ -362,12 +460,30 @@ static DatabaseNode *findchar(const FindCharArgs *fca,
 
 static int findchar_select_text(const FindCharArgs *fca,
                                const FindCharResults results[]) {
-  if (fca->ctxmap != 017) return -1;
+  
+  dbassert(! results[ct_Digit].match ); /* digits are supposedly unambiguous */
+
+  switch (fca->ctxmap) {
 
-  dbassert(! results[ct_Digit].match );
-  if (results[ct_Word].match) return ct_Word;
-  if (results[ct_Lower].rx > results[ct_Upper].rx) return ct_Lower;
-  if (results[ct_Upper].rx > results[ct_Lower].rx) return ct_Upper;
+#define RETURN_IF_LONGER(this,that) do{                        \
+    if (results[ct_##this].rx > results[ct_##that].rx) \
+       return ct_##this;                               \
+  }while(0)
+
+  case ctf_Digit | ctf_Upper | ctf_Lower | ctf_Word:
+    /* Start of word.  Prefer Word match; failing that, take the longest */
+    if (results[ct_Word].match) return ct_Word;
+    RETURN_IF_LONGER(Lower,Upper);
+    RETURN_IF_LONGER(Upper,Lower);
+    break;
+
+  case ctf_Digit | ctf_Upper | ctf_Lower:
+    /* Mid-word.  Prefer longer match; failing that, match lower. */
+    RETURN_IF_LONGER(Upper,Lower);
+    return ct_Lower;
+  }
+
+  /* oh well */
   return -1;
 }
 
@@ -380,7 +496,7 @@ const struct OcrCellTypeInfo ocr_celltype_number= {
 const struct OcrCellTypeInfo ocr_celltype_text= {
   .initial=  ctf_Digit | ctf_Upper,
   .nextword= ctf_Digit | ctf_Upper | ctf_Lower | ctf_Word,
-  .midword=  ctf_Digit | ctf_Lower,
+  .midword=  ctf_Digit | ctf_Upper | ctf_Lower,
   .space_spaces= 4,
   .name= "text",
   .findchar_select= findchar_select_text
@@ -389,7 +505,8 @@ const struct OcrCellTypeInfo ocr_celltype_text= {
 
 const char *ocr_celltype_name(OcrCellType ct) { return ct->name; }
 
-OcrResultGlyph *ocr(OcrReader *rd, OcrCellType ct, int w, Pixcol cols[]) {
+OcrResultGlyph *ocr(OcrReader *rd, OcrCellType ct, int w,
+                   const Pixcol cols[]) {
   int nspaces;
   int x;
 
@@ -405,19 +522,21 @@ OcrResultGlyph *ocr(OcrReader *rd, OcrCellType ct, int w, Pixcol cols[]) {
   nspaces=- w;
   fca.ctxmap= ct->initial;
   rd->nresults=0;
+  rd->lresult=0;
+  rd->result[0]=0;
   debugf("OCR h=%d w=%d",rd->h,w);
-  for (x=0; x<w; x++) debugf(" %"PSPIXCOL(PRIx),cols[x]);
+  for (x=0; x<w; x++) debugf(" "PIXCOL_PRFMT, PIXCOL_PRVAL(cols[x]));
   debugf("\n");
   debug_flush();
 
   x=0;
   for (;;) {
-    debug_flush();
+    if (DEBUGP(ocr)) debug_flush();
     /* skip spaces */
     if (x>=w)
       break;
 
-    if (!cols[x]) {
+    if (!pixcol_nonzero(&cols[x])) {
       nspaces++;
       x++;
       if (nspaces == ct->space_spaces) {
@@ -441,6 +560,10 @@ OcrResultGlyph *ocr(OcrReader *rd, OcrCellType ct, int w, Pixcol cols[]) {
     if (match) {
       debugf(" || YES");
       add_result(rd, match->str, x, match_rx, match_ctxi, fca.ctxmap);
+      if (should_reject(rd)) {
+       callout_unknown(rd, w,cols, match_rx+1,match_rx, 0);
+       goto restart;
+      }
       x= match_rx+1;
       if (match->match) fca.ctxmap= ct->midword;
       else debugf(" (empty)");
@@ -453,7 +576,7 @@ OcrResultGlyph *ocr(OcrReader *rd, OcrCellType ct, int w, Pixcol cols[]) {
     } else {
       int rx;
       debugf(" || UNKNOWN");
-      for (rx=x; rx<w && cols[rx]; rx++);
+      for (rx=x; rx<w && pixcol_nonzero(&cols[rx]); rx++);
       debugf(" x=%d ctxmap=%x %d..%d\n",x, fca.ctxmap, x,rx);
       debug_flush();
       callout_unknown(rd, w,cols, x,rx-1, fca.ctxmap);
@@ -470,16 +593,138 @@ OcrResultGlyph *ocr(OcrReader *rd, OcrCellType ct, int w, Pixcol cols[]) {
 OcrReader *ocr_init(int h) {
   OcrReader *rd;
 
+  assert(h <= OCR_MAX_H);
+
   if (o_flags & ff_dict_fetch) {
     char *fetchfile= masprintf("char%d",h);
-    progress("Updating %s...",fetchfile);
     fetch_with_rsync(fetchfile);
     free(fetchfile);
   }
 
   rd= mmalloc(sizeof(*rd));
   memset(rd,0,sizeof(*rd));
+
   rd->h= h;
+
+  rd->aresult= 10;
+  rd->result= mmalloc(rd->aresult);
+
   readdb(rd);
   return rd;
 }
+
+/*---------- character set dump ----------*/
+
+static void show_recurse(const DatabaseNode *t, int *count,
+                        const DatabaseNode **store_ary) {
+  if (t->defined) {
+    if (store_ary) store_ary[*count]= t;
+    (*count)++;
+  }
+  int l;
+  for (l=0; l<t->nlinks; l++)
+    show_recurse(t->links[l].then, count,store_ary);
+}
+
+static int show_char_compar(const void *av, const void *bv) {
+  const DatabaseNode *const *ap= av;  const DatabaseNode *a= *ap;
+  const DatabaseNode *const *bp= bv;  const DatabaseNode *b= *bp;
+  return strcmp(a->str, b->str) ?:
+    ((int)a->match -    (int)b->match) ?:
+    ((int)a->endsword - (int)b->endsword) ?:
+    ((int)a->local -    (int)b->local) ?:
+    0;
+}
+
+void ocr_showcharsets(void) {
+  DIR *d;
+  struct dirent *de;
+  char found[OCR_MAX_H];
+  pcre *fnpat;
+  int matchvec[10];
+  char hbuf[10];
+  const char *pcre_err;
+  int pcre_erroffset;
+
+  memset(found,0,sizeof(found));
+  
+  fnpat= pcre_compile("_(?:master|local)\\-char([1-9]\\d{0,2})\\.txt$",
+                     PCRE_ANCHORED|PCRE_DOLLAR_ENDONLY,
+                     &pcre_err,&pcre_erroffset, 0);
+  debugf("pcre_compile %p %s\n",fnpat,pcre_err);
+  assert(fnpat);
+
+  sysassert( d= opendir(get_vardir()) );
+  for (;;) {
+    errno=0; de= readdir(d);  if (!de) break;
+
+    int rer= pcre_exec(fnpat,0, de->d_name,strlen(de->d_name), 0,0,
+                      matchvec,ARRAYSIZE(matchvec));
+    debugf("pcre_exec `%s' => %d\n", de->d_name,rer);
+
+    if (rer==PCRE_ERROR_NOMATCH || rer==PCRE_ERROR_BADUTF8) continue;
+    assert(rer==2);
+
+    rer= pcre_copy_substring(de->d_name,matchvec,rer, 1, hbuf,sizeof(hbuf));
+    debugf("pcre_copy_substring => %d\n", rer);
+    assert(rer>0);
+
+    int h= atoi(hbuf);
+    if (h >= ARRAYSIZE(found)) continue;
+    
+    found[h]= 1;
+  }
+
+  int h;
+  for (h=0; h<ARRAYSIZE(found); h++) {
+    if (!found[h]) continue;
+
+    OcrReader *rd= ocr_init(h); /* we leak this but never mind */
+    progress("");
+
+    int ctxi;
+    for (ctxi=0; ctxi<NCONTEXTS; ctxi++) {
+      int nchars= 0;
+      show_recurse(&rd->contexts[ctxi], &nchars, 0);
+      const DatabaseNode **chars= mmalloc(sizeof(*chars) * nchars);
+      int chari= 0;
+      show_recurse(&rd->contexts[ctxi], &chari, chars);
+      assert(chari==nchars);
+      qsort(chars, nchars, sizeof(*chars), show_char_compar);
+
+      int local;
+      for (local=0; local<2; local++) {
+       printf("%2d %-6s %-6s ", h, context_names[ctxi],
+              local?"local":"master");
+       for (chari=0; chari<nchars; chari++) {
+         const DatabaseNode *t= chars[chari];
+         static const char accept[]=
+           "abcdefghijklmnopqrstuvwxyz"
+           "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+           "0123456789" "'!&-+.,>";
+
+         if (t->local != local) continue;
+
+         if (!t->match)
+           printf(" [nomatch]");
+         else if (!t->endsword && strspn(t->str, accept) == strlen(t->str))
+           printf(" %s",t->str);
+         else {
+           printf(" \"");
+           char *p= t->str;
+           int c;
+           while ((c=*p++)) {
+             if (c=='"' || c=='\\') printf("\\%c",c);
+             else if (c>=' ' && c<=126) putchar(c);
+             else printf("\\x%02x", (unsigned char)c);
+           }
+           if (t->endsword) putchar(' ');
+           putchar('"');
+         }
+       }
+       putchar('\n');
+      }
+      free(chars);
+    }
+  }
+}