Kofax Capture erweiterte Scan Api: Eine erste Annäherung
Der folgende Artikel zeigt einen möglichen Anwendungsfall für die Anwendung der neuen Api Erweiterung für das Scan-Modul, die mit SP1 von Kofax Capture 10 zur Verfügung steht. Eine Beispielanwendung des Herstellers liegt im Quellcode vor und ist im bekannten Verzeichnis zu finden unter …\Source\Sample Projects\StdCust\ScanApiSamplePanel. Der Anwednungsfall, den wir lösen wollten ist wie folgt beschrieben:
Wir wollen duplex scannen und trennen die Dokumente per Patchcode. Für ein folgendes CustomModule müssen wir alle Seiten mit einem CustomStorageString versehen, der aussagen soll, ob die Seite von der Vorder- oder Rückseitenkamera aufgenommen wurde. Falls das Tiff von der Vorderseitenkamera aufgenommen wurde, setzen wir den Wert auf „0“, andernfalls auf „1“.
Um diese Information aufbereiten zu können wird auf das neue Event KfxOcxEventScanPageEnd
zurückgegriffen. Für jede gescannte Seite wird dieses Event gefeuert, bevor die Seite auf der Festplatte abgelegt wird. Das gilt beispielsweise auch für die Seiten, die einen Patchcode enthalten. Wie gesagt, zu dieser Zeit befindet sich die Seite noch im Speicher und ist nicht im Images Verzeichnis des Stapels abgelegt. Hier hat man nun die Möglichkeit über die Funktionen GetKImgp()
und GetKScan()
des AscentCaptureModule.Application
Objektes auf erweiterte Informationen zuzugreifen.
Sollten Sie mit c# arbeiten, dann muss ihr Projekt mindestens gegen das .NET Framework 4 gehen, auch wenn offiziell nur .NET 3.5 für Kofax freigegeben ist. Der Grund ist die DLR (Dynamic Language Runtime) und die Verwendung der Klasse DynamicObject.
dynamic kimgp = _kofaxApplication.GetKImgp(); kimgp.LifeIsGood();
kimgp
ist als dynamisch gekennzeichnet und somit kennt der Compiler zur Entwurfszeit nicht den Typ der Deklaration. Der Compiler kennt nicht mal das Member LifeIsGood()
. Erst zur Laufzeit wird die Methode aufgelöst, basierend auf dem aktuellen Objekt. Vergleichbar ist das mit dem LateBinding aus VB via CreateObject()
. Falls man fälschlicherweise kimgp.LifeIsGoo();
kodiert, so wird das erst einen Fehler zur Laufzeit auslösen. Für uns bedeutet das aber auch, dass man die Eigenschaften und Methoden des Objektes nicht genau kennt, da man auch auf das Feature IntelliSense nicht zurückgreifen kann. Aber es gibt zwei Dokumentationen, die hier Abhilfe schaffen (Language Reference and Programmers Guide). Diese sind verfügbar unter: http://www.kofax.com/support/products/imagecontrols/3.1/
Kommen wir zurück zum Anwendungsfall. Zuerst haben wir eine einfache Behelfsklasse erstellt mit der wir die Informationen jeder gescannten Seite persistieren wollen:
1class PageFlag
2{
3 public bool IsPatch { get; private set; }
4 public bool IsFrontPage { get; private set; }
5 public PageFlag(bool isPatch, bool isFrontPage)
6 {
7 IsPatch = isPatch;
8 IsFrontPage = isFrontPage;
9 }
10}
Dann machen wir uns eine GenericList zu Nutze.
1using System.Collections.Generic;
2private List<PageFlag> _pageList = new List<PageFlag>();
Zu guter Letzt brauchen wir eine Art Zähler für die jeweilige Seite. Dieser muss auf Null zurückgesetzt werden, wenn ein neuer Stapel geöffnet wird.
1private int _pageNumber;
Es folgt nun das zuvor erwähnte Event in der ActionEvent Funktion:
1case (int)KfxOcxEvent.KfxOcxEventScanPageEnd: 2 _pageNumber += 1; 3 bool frontPage = false; 4 try 5 { 6 dynamic kscan = _kofaxApplication.GetKScan(); 7 frontPage = kscan.PEFront; 8 } 9 catch (Exception ex) 10 { 11 MessageBox.Show(ex.ToString()); 12 } 13 finally 14 { 15 Marshal.ReleaseComObject(kscan); 16 } 17 18 try 19 { 20 dynamic kimgp = _kofaxApplication.GetKImgp(); 21 int patchCodeType = kimgp.PEPatchCode; 22 } 23 catch (Exception ex) 24 { 25 MessageBox.Show(ex.ToString()); 26 } 27 finally 28 { 29 Marshal.ReleaseComObject(kimgp); 30 } 31 try 32 { 33 // we know that we will only get the PatchCode Type 2 (set in the batchclass) 34 _pageList.Add(new PageFlag(patchCodeType==2, frontPage)); 35 } 36 catch (Exception ex) 37 { 38 MessageBox.Show(ex.ToString()); 39 } 40 break;
Nachdem die letzte Seite gescannt wurde, haben wir alle Informationen für alle gescannten Seiten während des Scan-Vorgangs persistiert. Im Event KfxOcxEvent.KfxOcxEventScanStopped
müssen wir nun mittels eines Timer Objektes eine Folgeverarbeitung anstoßen. Leider müssen wir das über diese Art und Weise regeln und können das nicht direkt in der ActionEvent Funktion bewerkstelligen.
1… 2 … 3 … 4 case (int)KfxOcxEvent.KfxOcxEventScanStopped: 5 timerScanStopped.Enabled = true; 6 break; 7 … 8 … 9 …
1private void timerScanStopped_Tick(object sender, EventArgs e)
2 {
3 timerScanStopped.Enabled = false;
4 foreach (Kofax.AscentCaptureModule.Document document
5 in _kofaxApplication.ActiveBatch.Documents)
6 {
7 int index = 0;
8 foreach (Kofax.AscentCaptureModule.Page page in document.Pages)
9 {
10 index += 1;
11 if (IsFrontPage(index))
12 {
13 page.set_CustomStorageString("Paradatec-Backside", "0");
14 }
15 else
16 {
17 page.set_CustomStorageString("Paradatec-Backside", "1");
18 }
19 }
20 }
21 }
Die Funktion IsFrontPage
hilft zu erkennen, ob die Seite von der Vorderseitenkamera stammt oder nicht. Hier müssen wir dem Umstand Genüge tragen, dass wir wahrscheinlich mehr Events bekommen, als Seiten in dem Stapel sind, da auch die Events für Seiten mit Patchcode gefeuert wurden, diese Seiten aber nicht im Stapel enthalten sind. Deshalb müssen wir die Information, ob die Seite einen Patchcode enthielt, berücksichtigen.
1private bool IsFrontPage(int pageIdx)
2 {
3 bool previousPatch = false;
4 int idx = 0;
5 for (int i = 0; i < _pageList.Count; i++)
6 {
7 if (previousPatch)
8 {
9 if (_pageList[i].IsPatch==false)
10 {
11 if (_pageList[i].IsFrontPage)
12 {
13 idx += 1;
14 if (idx==pageIdx)
15 {
16 return _pageList[i].IsFrontPage;
17 }
18 }
19 previousPatch = false;
20 }
21 }
22 else
23 {
24 if (_pageList[i].IsPatch)
25 {
26 previousPatch = true;
27 }
28 else
29 {
30 idx += 1;
31 if (idx == pageIdx)
32 {
33 return _pageList[i].IsFrontPage;
34 }
35 }
36 }
37 }
38 return false;
39 }
Ein Beispiel: Wir scannen zwei Dokumente (duplex) getrennt über Patchcodes. Das erste Dokument besteht aus einem Blatt und das zweite Dokument besteht aus zwei Blättern.:
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.
Ein weiteres Beispiel: Wir scannen dieselben Dokumente (duplex) getrennt über Patchcodes aber lassen VRS leere Rückseiten enternen. Werden leere Seiten über VRS gelöscht, werden dafür keine Events gefeuert.
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.
Leider gibt es Einschränkungen mit dem dargestellten Code. Er funktioniert perfekt, wenn man in einen leeren Stapel scannt, allerdings kann der Code einfach erweitert werden, um auch das mehrfache Scannen hintereinander in einen Stapel zu unterstützen. Schwieriger wird es aber, die Funktion des Ersetzens einer einzelnen vorhandenen Seite zu unterstützen oder gar das Einfügen von Seiten. Stellen wir uns vor, eine Rückseite muss neu gescannt werden. Dazu legt man die Rückseite auf den Scanner und scannt diese mit der Vorderseitenkamera. Man erhält zudem zwei Events, für die Vorder- und Rückseite, aber nur die Vorderseite ersetzt die Rückseite. Es ist nachher unsere Aufgabe, die ersetzte Seite ausfindig zumachen und zu flaggen (CustomStorageString). Hier könnte man dann vor dem Scannen einer Seite oder eines Stapels einen Snapshot erstellen, der mir Informationen bereitstellt für jedes Dokument und jede Seite im Stapel mit Dateigröße und des Zeitstempels der letzten Änderung. Entsprechende Events sind hierfür vorhanden. Dasselbe würde man dann nach dem Scannen machen und könnte dann relativ sicher die ersetzte Seite ausmachen.
Im Übrigen lohnt es sich, einen Blick in die Beispielanwendung des Herstellers Kofax zu werfen. Die neuen Möglichkeiten sind größer, als hier beschrieben. So kann man über ein CustomScanApi Xml Interface Dokument erstellen oder die Labels für die Dokumente in der Stapelansicht verändern, zum Beispiel auf den Inhalt eines Barcodes. Außerdem kann man mit Scannerprofilen arbeiten und vieles mehr.
Zu guter Letzt möchte ich noch erwähnen, dass der oben geschilderte Anwendungsfall von uns mit einem High-Speed-Scanner (170 ppm, DIN A4, duplex) getestet wurde, ohne dass es hier zu Fehlern kam.
Weitere Beiträge
von Stefan Blank
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Stefan Blank
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.