Penlets.com provides resources for users and developers of the Pulse smart pen from LiveScribe.

 

Subscribe: RSS Feed - Developers Group

Tutorial

Using Intelligent Character Recognition (ICR)

Tutorial by Robert Hanson

Page 1 » Page 2


Next, lets add the initICRContext and destroyICRContext methods. This is where we start to get to the heart of what we are trying to accomplish.

private void initICRContext ()
{
    this.icrContext = this.context.getICRContext(1000, this);  /* [1] */

    Resource[] resources = {  /* [2] */
        this.icrContext.getDefaultAlphabetKnowledgeResource(),
        this.icrContext.createSKSystemResource(ICRContext.SYSRES_SK_ALPHA),
        this.icrContext.createLKSystemResource(ICRContext.SYSRES_LK_WORDLIST_100K)
    };
    this.icrContext.addResourceSet(resources);
    
    this.context.addStrokeListener(this);  /* [3] */
}

private void destroyICRContext ()
{
    icrContext.dispose();  /* [4] */
    icrContext = null;
}

So here we have our two methods. In the initICRContext method we start by getting an ICR Context [1]. We do this by calling getICRContext on the context field, which resides in our super-class Penlet . The context field is a protected field of type PenletContext . The two arguments passed to getICRContext are a timeout and an instance of HWRListener . The timeout is used to detect writing pauses, and in this case we are indicating that 1000 milliseconds (1 second) constituted a pause. The second argument is a HWRListener that will accept hand-writing recognition events, namely word results and detected pauses. Note that we pass this as the second argument, implying that our class will need to extend HWRListener . Indeed it will, and we will show that shortly.

Next we need to define the resources [2] that we want to use and pass them to the ICR Context. This is where the "AK" (alphabet knowledge), "SK" (subset knowledge), and "LK" (lexicon knowledge) resources that we discussed fit in. Here we tell the recognition engine to use the default alphabet, including only alpha characters (letters), and to use the built-in 100,000 word lexicon.

Now as I mentioned earlier, WE need to feed the ICR Context with pen strokes, it won't do it on its own. So we need to also register a stroke listener [3] that will capture pen strokes and pass them on to the ICR Context. Here we pass this , implying that our class will also implement the StrokeListener interface, and indeed it will.

In comparison, the destroyICRContext [4] is kinda boring. Here we simply dispose of the ICR Context and set the field to null.

Next up we want to have out class implement the StrokeListener interface so that it can pass strokes to the ICR Context.

public class ICRDemo extends Penlet
    implements StrokeListener
{

	... 

    public void strokeCreated (long startTime, Region region,
        PageInstance pageInstance)
    {
        this.icrContext.addStroke(pageInstance, startTime);
    }

}

Well, I think the code speaks for itself. Nothing complicated, just passing along the stroke data to the ICR Context as we receive it.

Now we are close to the end, and have only one loose end to finish. We need our class to implement the HWRListener interface so that we can be notified when the recognition engine matches the strokes to words.

public class ICRDemo extends Penlet
    implements StrokeListener, HWRListener
{

    ...
    
    /* [1] */
    public void hwrResult (long time, String result)
    {
        this.label.draw(result);
    }

    /* [2] */
    public void hwrUserPause (long time, String result)
    {
        this.icrContext.clearStrokes();
        this.label.draw(result + " :)", true);
    }

    public void hwrCrossingOut (long time, String result) { }

    public void hwrError (long time, String error) { }

}

There are four methods that make up the HWRListener interface, of which we only provide code for two. In the hwrResult [1] method we update the display with the value of the result parameter. The result is the recognition engine's best guess based on the strokes that have been fed to it. If you watch the display as you write you will see that partial words result in bad guesses, and as each new stroke is added it updates its guess.

You might recall that when we created the ICR Context, we defined a timeout of 1000 milliseconds (or 1 second). When the user stops writing for this one second, the hwrUserPause method [2] is called. We equate a pause with an indication that the user has finished writing the word. To indicate the pause in the pen's display we show the result with a smiley face. In addition to this we clear the collected pen strokes from the ICR Context so that the user can write a new word.

Next Steps

Once you get this application running on your pen you should experiment with the various AK, SK, and LK resources available. Some of these are defined at getters in the ICRContext class, like getDefaultAlphabetKnowledgeResource and getMultiWordDefaultResourceSet . Others are defined as constants of the ICRContext class and added via the create*KSystemResource methods. These include SYSRES_LK_BIGRAM_100K an alternate 100K lexicon using a Bigram frequency table ( Bigram at Wikipedia), SYSRES_LK_LMEM for cursive writing, and SYSRES_LK_OUT_OF_LEXICON allows recognition of words not in the lexicon. Try combining them in various ways, and even try creating your own lexicon files, which will be covered in a separate tutorial.

Happy coding!


Page 1 » Page 2


Comments (View)
blog comments powered by Disqus

Project Information

Tested for use with: PreRelease-SDK 0.6

Download the source code for this tutorial.
Download Source Code (zip)

New Tutorials

Using a Shared Library
Learn how to create a library of utils that you can share between projects.

Capturing Drawn Shapes
Learn how to capture shapes drawn by the pen and determine relationships.

Penlets 101
Never written a penlet before? Then start here with Penlets 101!

Creating a Custom Vocabulary
Learn how to create a custom vocabulary for your ICR applications.

Using Properties Files
J2ME lacks a Properties class. In this tutorial we roll our own, along with split and chomp functions.