How to use the Tomoe handwriting recognition library

July 19th, 2008

Introduction

Tomoe is a handwriting recognition library for Chinese and Japanese characters (han character, kanji). It is published under the LGPL license, which means that it is free software that can be used by both proprietary and free applications (provided that you use it as a dynamic library).

The Tomoe project provides the library itself as well as a character stroke database, a handwriting recognition canvas for GTK applications, a character stroke data editor, and modules for common Unix/Linux input methods. Unfortunately, very little programming documentation is provided in Tomoe, and there are almost no source code comments. It can be time consuming to get started, especially if you want to use libtomoe directly from your own code without using the GUI components from Tomoe.

Tomoe takes advantage of the data structures and object oriented programming features provided by the glib library. Since I don’t use glib in my own projects - l learned just enough of glib to be able to read Tomoe code - the code snippets below may contain errors; I’d be grateful for corrections, both to the code and to my assumptions about the how the library functions.

Anyway, this is how I managed to interface my C++ project to libtomoe, and I hope that this information can help you get started as well.

Tomoe classes that we will be using

  • TomoeContext - Interface to the character recognition functions.
  • TomoeWriting - A handwritten character. You fill this in with coordinates from the user’s handwriting.
  • TomoeQuery - The query that you pass to a TomoeContext to perform recognition. Contains the TomoeWriting that you wish to perform recognition on.
  • TomoeChar - Tomoe’s character wrapper.
  • TomoeCandidate - A candidate character, together with a rating. A list of these is returned to you from TomoeContext after an attempted recognition.

Performing character recognition in your application

I assume that you’re linking to libtomoe and that you have tomoe.h in your header search path. First, you should create a configuration file for Tomoe. The one that I use is very simple:

[config]
language = ja

I named it “tomoe.config”; you can choose any name you like.

Now we get to the actual source code. Make sure that you include the library’s main header file in your source file:

#include <tomoe.h>

Before calling any other Tomoe functions, initialize it by calling

tomoe_init();

Then, you need to let Tomoe know where its data file(s) are located.

tomoe_dict_set_default_module_dir("/path/to/data/files");

Create a TomoeContext:

TomoeContext *context = tomoe_context_new();

…and let it load the configuration file. When it has read the configuration file, it loads the required character stroke data depending on which language you specify in the configuration file.

tomoe_context_load_config(context, "tomoe.config");

Now we create a TomoeWriting that we will use to record the user’s strokes.

TomoeWriting *writing = tomoe_writing_new();

Here is where you couple your own GUI to Tomoe. I assume that you provide some kind of canvas where the user can draw characters, and that the origin (0,0) is in the top left corner. The size of your canvas is not critical, since we will scale the coordinates before passing them to Tomoe.

A handwritten character, represented by TomoeWriting, contains of one or more strokes, which in turn consists of a number of coordinates. Your GUI needs to keep track of what the user is doing, and record it in a TomoeWriting. You use

tomoe_writing_move_to(writing, x, y);

to make TomoeWriting prepare to record the coordinates of a new stroke, and then

tomoe_writing_draw_to(writing, x, y);

when the user has moved the input device to a new coordinate (while holding the “button” down). In other words:

The user has put the stylus / finger / mouse button down somewhere on the canvas to begin a stroke:

tomoe_writing_move_to(writing, x, y);

The user has kept the stylus down and drawn a line to a new coordinate (but not necessarily released it yet):

tomoe_writing_draw_to(writing, x, y);

Now, you have a TomoeWriting full of values, and it’s almost time to ask libtomoe to run the actual character recognition algorithms on it. However, first we must scale the coordinates so that that they lie between 0 and TOMOE_WRITING_WIDTH for x values, and TOMOE_WRITING_HEIGHT for y values. You can do this any way you like; I use the following code, originally from the Tomoe’s GTK canvas code:

TomoeWriting *newScaledWriting(TomoeWriting *writing, gdouble sx, gdouble sy)
{
    const GList *strokes, *list;
    TomoeWriting *newWriting = tomoe_writing_new ();

    strokes = tomoe_writing_get_strokes (writing);
    for (list= strokes; list; list = g_list_next (list)) {
        GList *points = (GList *) list->data;
        GList *point;
        gboolean first = TRUE;
        for ( point = points; point; point = g_list_next (point)) {
            TomoePoint *cp = (TomoePoint *) point->data;
            if (!first)
                tomoe_writing_line_to (newWriting, cp->x * sx, cp->y * sy);
		else
		    tomoe_writing_move_to (newWriting, cp->x * sx, cp->y * sy);
            first = FALSE;

        }
    }
    return newWriting;
 }

I then call this function like this:

TomoeWriting *scaledWriting = newScaledWriting(writing, (double)TOMOE_WRITING_WIDTH / (double)MyCanvasWidth,
  (double)TOMOE_WRITING_HEIGHT / (double)MyCanvasHeight);

We don’t pass this scaled TomoeWriting directly to TomoeContext for processing; first, we wrap it in a TomoeQuery.

TomoeQuery *query = tomoe_query_new();
tomoe_query_set_writing (query, scaledWriting);

We’re finally ready to do the recognition. TomoeContext delivers the result to us as a GList of TomoeCandidate.

GList *res;
res = tomoe_context_search (context, query);

Now you’re done, and you have only to inspect the result. Don’t forget to call

g_object_unref()

on objects you’ve allocated when you don’t need them anymore. Finally, when you’re done with libtomoe, call

tomoe_dict_quit()

Good luck!

ps. Stop leaving nonsensical comments in Russian. I can’t read it and I don’t care for it.