Kofax Capture Advanced Scan Api: A first approach - codecentric AG Blog

:

The following article shows a possible use case in using the new scan api, coming with SP1 of Kofax Capture 10.

A sample application can be found at the Source directory of the Kofax Capture installation under …\Source\Sample Projects\StdCust\ScanApiSamplePanel.

The business case we want to solve is described as follows:

We want to scan duplex and for document separation we use patch codes. For a following custom module we need to flag all pages with a CustomStorageString. If the tiff was grabbed by the front camera we want to set its value to “0” and to “1” if the tiff was grabbed by the rear camera.

To preprocess this information we need to use the new available event KfxOcxEventScanPageEnd.

For each scanned page this event is fired. This applies also to the pages containing the patch code. During that time the tiff file is not even placed in the image folder of the batch. When this event is fired you can use the functions GetKImgp() and GetKScan() of the AscentCaptureModule.Application object.

If you work with c# then your project has to be at least .NET 4. The reason is the dynamic language runtime and the use of the DynamicObject class.
dynamic kimgp = _kofaxApplication.GetKImgp();
kimgp.LifeIsGood();

kimgp is marked as dynamic and the compiler does not know the type of it. He also does not know of its member LifeIsGood(). Only at runtime this method is resolved based on the actual object! It is comparable to the late binding in VB with CreateObject() . If you coded wrongly kimgp.LifeIsGoo(); then this will only cause an error at runtime – the compiler does not mark this line as faulty!

Moreover that means that you don’t know about the properties or methods of these objects during design time. No IntelliSense can be used. But there are two documents (Language Reference and Programmers Guide) available that give detailed information at: http://www.kofax.com/support/products/imagecontrols/3.1/

Coming back to the use case. We first created a small helper class to persist the information of each scanned page:

class PageFlag
{
    public bool IsPatch { get; private set; }
    public bool IsFrontPage { get; private set; }
    public PageFlag(bool isPatch, bool isFrontPage)
    {
        IsPatch = isPatch;
        IsFrontPage = isFrontPage;
    }
}

class PageFlag {     public bool IsPatch { get; private set; }     public bool IsFrontPage { get; private set; }     public PageFlag(bool isPatch, bool isFrontPage)     {         IsPatch = isPatch;         IsFrontPage = isFrontPage;     } }

Then we need a generic list containing instances of this class:

using System.Collections.Generic;
private List<PageFlag> _pageList = new List<PageFlag>();

using System.Collections.Generic; private List<PageFlag> _pageList = new List<PageFlag>();

Additionally we need a counter for the current page that needs to be reset to zero if a new batch is opened.

Here follows the event in the ActionEvent function:

case (int)KfxOcxEvent.KfxOcxEventScanPageEnd:
    _pageNumber += 1;
    bool frontPage = false;
    try
    {
        dynamic kscan = _kofaxApplication.GetKScan();
        frontPage = kscan.PEFront;       
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
    finally
    {
        Marshal.ReleaseComObject(kscan);
    }
 
    try
    {
        dynamic kimgp = _kofaxApplication.GetKImgp();
        int patchCodeType = kimgp.PEPatchCode;
    }  
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
    finally
    {
        Marshal.ReleaseComObject(kimgp);
    }
    try
    {
        // we know that we will only get the PatchCode Type 2 (set in the batchclass)
        _pageList.Add(new PageFlag(patchCodeType==2, frontPage));
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
    break;

case (int)KfxOcxEvent.KfxOcxEventScanPageEnd:     _pageNumber += 1;    bool frontPage = false;     try    {     dynamic kscan = _kofaxApplication.GetKScan();        frontPage = kscan.PEFront;          }    catch (Exception ex)    {     MessageBox.Show(ex.ToString());    }    finally    {        Marshal.ReleaseComObject(kscan);    }   try    {        dynamic kimgp = _kofaxApplication.GetKImgp();        int patchCodeType = kimgp.PEPatchCode;    }     catch (Exception ex)    {        MessageBox.Show(ex.ToString());    }    finally    {        Marshal.ReleaseComObject(kimgp);    }     try     {     // we know that we will only get the PatchCode Type 2 (set in the batchclass)        _pageList.Add(new PageFlag(patchCodeType==2, frontPage));    }    catch (Exception ex)    {        MessageBox.Show(ex.ToString());    }     break;

After the last page was scanned we have all the information persisted for all scanned pages during the scan process.

In the KfxOcxEvent.KfxOcxEventScanStopped event we need to start a timer for the subsequent process. We have to do it with a Timer object, as we cannot do this work in the scope of the ActionEvent function.

     …
     …
     …
         case (int)KfxOcxEvent.KfxOcxEventScanStopped:
             timerScanStopped.Enabled = true;
             break;
     …
     …
     …

…     …     …     case (int)KfxOcxEvent.KfxOcxEventScanStopped:        timerScanStopped.Enabled = true;      break;     …     …     …

    private void timerScanStopped_Tick(object sender, EventArgs e)
    {
        timerScanStopped.Enabled = false;
        foreach (Kofax.AscentCaptureModule.Document document
                  in _kofaxApplication.ActiveBatch.Documents)
        {
            int index = 0;
            foreach (Kofax.AscentCaptureModule.Page page in document.Pages)
            {
                index += 1;
                if (IsFrontPage(index))
                {
                    page.set_CustomStorageString("Paradatec-Backside", "0");
                }
                else
                {
                    page.set_CustomStorageString("Paradatec-Backside", "1");
                }
            }
        }
    }

    private void timerScanStopped_Tick(object sender, EventArgs e)    {     timerScanStopped.Enabled = false;        foreach (Kofax.AscentCaptureModule.Document document in _kofaxApplication.ActiveBatch.Documents) {            int index = 0; foreach (Kofax.AscentCaptureModule.Page page in document.Pages) { index += 1; if (IsFrontPage(index)) { page.set_CustomStorageString("Paradatec-Backside", "0"); } else { page.set_CustomStorageString("Paradatec-Backside", "1"); } } } }

The function IsFrontPage helps to detect if the page was a front page or not. We need to deal with the circumstance, that we probably have more events than pages, as an event of a scanned page with a patchcode is used for document separation but then this page is not part of the batch. That is why we needed to detect if the page contained a patchcode.

    
    private bool IsFrontPage(int pageIdx)
    {
        bool previousPatch = false;
        int idx = 0;
        for (int i = 0; i < _pageList.Count; i++)
        {
            if (previousPatch)
            {
                if (_pageList[i].IsPatch==false)
                {
                    if (_pageList[i].IsFrontPage)
                    {
                        idx += 1;
                        if (idx==pageIdx)
                        {
                            return _pageList[i].IsFrontPage;
                        }
                    }
                    previousPatch = false;
                }
            }
            else
            {
                if (_pageList[i].IsPatch)
                {
                    previousPatch = true;
                }
                else
                {
                    idx += 1;
                    if (idx == pageIdx)
                    {
                        return _pageList[i].IsFrontPage;
                    } 
                }        
            }        
        }
        return false;
    }

     private bool IsFrontPage(int pageIdx)    {        bool previousPatch = false;        int idx = 0;        for (int i = 0; i < _pageList.Count; i++)        {            if (previousPatch)            {                if (_pageList[i].IsPatch==false)                {                    if (_pageList[i].IsFrontPage)                    {                        idx += 1;                        if (idx==pageIdx)                        {                            return _pageList[i].IsFrontPage;                        }                    }                    previousPatch = false;                }            }            else            {                if (_pageList[i].IsPatch)                {                    previousPatch = true;                }                else                {                    idx += 1;                    if (idx == pageIdx)                    {                        return _pageList[i].IsFrontPage;                    }                }                   }               }        return false;    }

An example:

We scan two documents (duplex scanning) separated by patchcodes. The first document contains one sheet and the second document conatains two sheets:

 

 

 

 

Events fired:

Event 1 PatchType=2 FrontCamera=True    -> Deleted in case of document separation.
Event 2 PatchType=0 FrontCamera=False   -> Deleted in case of document separation.
Event 3 PatchType=0 FrontCamera=True    -> Document 1, page 1.
Event 4 PatchType=0 FrontCamera=False   -> Document 1, page 2.
Event 5 PatchType=0 FrontCamera=True    -> Deleted in case of document separation.
Event 6 PatchType=0 FrontCamera=False   -> Deleted in case of document separation.
Event 7 PatchType=2 FrontCamera=True    -> Document 2, page 1.
Event 8 PatchType=0 FrontCamera=False   -> Document 2, page 2.
Event 9 PatchType=0 FrontCamera=True    -> Document 2, page 3.
Event 10 PatchType=0 FrontCamera=False  -> Document 2, page 4.

Another example:

We scan the same documents (duplex scanning) separated by patchcodes but let VRS delete blank pages.

If blank page deletion in duplex mode is used then there are no events fired for this deleted pages.

 

 

 

 

 

Events fired:

Event 1 PatchType=2 FrontCamera=True    -> Deleted in case of document separation.
Event 2 PatchType=0 FrontCamera=True    -> Document 1, page 1.
Event 3 PatchType=2 FrontCamera=True    -> Deleted in case of document separation.
Event 4 PatchType=0 FrontCamera=True    -> Document 2, page 1.
Event 5 PatchType=0 FrontCamera=True    -> Document 2, page 2.

Unfortunately we have limitations of this code so far. It will only work from scratch, which means only scanning in an empty batch. The code can easily be extended for using it with scanning more times in one single batch. But what becomes more difficult is how to deal with replacing an existing page. Imagine that the page to be replaced is a back page. Therefore the page is put into the scanner and is scanned as a front page with the front camera! You also get two events for the front and the rear camera but only the front page replaces the back page. To solve this issue you could make a snapshot of the batch, its documents and pages with information about file size and the last write time before the scanning of a new page or batch is about to start. Corresponding events are available to take this snapshot. Then you could detect a replaced page by comparing the snapshot before scanning and after scanning.

At least I would like to add that it is worth to take a look at the example source code. The CustomScanApi Xml interface allows you to to create documents or to specify the label of a document e.g. the content of a barcode in the event KfxOcxEventScanPageEnd . And it is possible to work with scanner profiles.

In the end I would like to mention that I tested the api with a high speed scanner (170 ppm, DIN A4, duplex) without running into an error.

BatchGenerator