Using Tesseract OCR and Java Gateway
Tags: ocr, java, tutorial
Published: 2022-06-19 11:58:39

The InterSystems IRIS can be extended using Java or .NET components and its frameworks inside Object Script source code.

I created an application called OCR Service. It built with Docker and installs Google Tesseract inside docker instance configured with english and portuguese dialects, but is possible install more than other 100 dialects. Google Tesseract can receive images and return text extracted from it, using OCR. The results are very good with the trained dialects. But you can train Tesseract to read car plates and any other textual patterns and load it to extract text. Java has a framework called Tess4J to enable Java call Tesseract instances and functions.  See running:

OCR in Action


See the Java code to the OCR service:

```
private String extractTextFromImage(File tempFile) throws TesseractException {

  ITesseract tesseract = new Tesseract();
  tesseract.setDatapath("/usr/share/tessdata/"); //directory to trained models
  tesseract.setLanguage("eng+por"); // choose your language/trained model
  
  return tesseract.doOCR(tempFile); //call tesseract function doOCR() //passing the file to be processed with OCR technique
  
}


```
Tess4J take care all low level communication with Tesseract with ITesseract interface class and we need only set the OCR model to be used and call doOCR() passing the file. Easy!

For our luck, we don't have to create a Tess4ObjectScript framework to interact with Tesseract, it is possible, using C callin/callout, but you need to understand how to talk at low level with Tesseract, I prefer use Java Gateway to save many work days!

To use Java Gateway, you have to install Java into your IRIS OS or docker instance, set JAVA_HOME (variable to know where Java is installed), set the CLASSPATH (variable to point to your Java class, Tess4J java archive and other dependencies) and execute a Java Gateway instance, a proxy to enable ObjectScript and Java to talk. See my dockerfile instructions to install Java and Tesseract to IRIS instance:

```
ARG IMAGE=store/intersystems/iris-community:2020.1.0.204.0
ARG IMAGE=intersystemsdc/iris-community:2020.1.0.209.0-zpm
ARG IMAGE=intersystemsdc/iris-community:2020.2.0.204.0-zpm
ARG IMAGE=intersystemsdc/irishealth-community:2020.3.0.200.0-zpm
ARG IMAGE=intersystemsdc/iris-community:2020.3.0.200.0-zpm
ARG IMAGE=intersystemsdc/iris-community:2020.3.0.221.0-zpm
ARG IMAGE=intersystemsdc/iris-community:2020.4.0.521.0-zpm
FROM $IMAGE

USER root   
        
WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp
USER ${ISC_PACKAGE_MGRUSER}
  
COPY  Installer.cls .
COPY  src src
  
COPY iris.script /tmp/iris.script
  
RUN iris start IRIS \
    && iris session IRIS < /tmp/iris.script \
    && iris stop IRIS quietly
  
USER root   
  
# Install Java 8 using apt-get from ubuntu repository
RUN apt-get update && \
    apt-get install -y openjdk-8-jdk && \
    apt-get install -y ant && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    rm -rf /var/cache/oracle-jdk8-installer;
    
# Fix certificate issues, found as of 
# https://bugs.launchpad.net/ubuntu/+source/ca-certificates-java/+bug/983302
RUN apt-get install -y ca-certificates-java && \
    apt-get clean && \
    update-ca-certificates -f && \
    rm -rf /var/lib/apt/lists/* && \
    rm -rf /var/cache/oracle-jdk8-installer;
  
# Setup JAVA_HOME, to enable apps to know where the Java was installed
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64/
RUN export JAVA_HOME
ENV JRE_HOME /usr/lib/jvm/java-8-openjdk-amd64/
RUN export JRE_HOME
  
# Setup classpath, to enable apps to know where java classes and java jar libraries was installed
ENV classpath .:/usr/irissys/dev/java/lib/JDK18/*:/opt/irisapp/*:/usr/irissys/dev/java/lib/gson/*
:/usr/irissys/dev/java/lib/jackson/*:/jgw/*
RUN export classpath
ENV CLASSPATH .:/usr/irissys/dev/java/lib/JDK18/*:/opt/irisapp/*:/usr/irissys/dev/java/lib/gson/*
:/usr/irissys/dev/java/lib/jackson/*:/jgw/*
RUN export CLASSPATH
  
USER root
  
ARG APP_HOME=/tmp/app
  
COPY src $APP_HOME/src
  
# Tess4J and another java libraries used, are into jgw folder and jgw folder is in the classpath
COPY jgw /jgw
  
# Copy our Java OCR program, packaged into a jar, to the jgw
COPY target/ocr-pex-1.0.0.jar /jgw/ocr-pex-1.0.0.jar  
  
COPY jgw/* /usr/irissys/dev/java/lib/JDK18/
  
# Install tesseract using ubuntu apt-get
RUN apt-get update && apt-get install tesseract-ocr -y
  
USER root
  
# Copy trained models eng and por to the models folder
COPY tessdata /usr/share/tessdata
  
# Install and config default OS locale - it is required to tesseract works fine
RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/* \
 && locale-gen "en_US.UTF-8"
ENV LANG=en_US.UTF-8 \
    LANGUAGE=en_US:en \
    LC_ALL=en_US.UTF-8

```

When your ObjectScript class is an interoperability business class or operation, set a Java Gateway Business Service (JavaGateway item into my production).

```
Class dc.ocr.OcrProduction Extends Ens.Production
{

XData ProductionDefinition
{

<Production Name="dc.ocr.OcrProduction" LogGeneralTraceEvents="false">
  <Description></Description>
  <ActorPoolSize>2</ActorPoolSize>
  <Item Name="OcrService" Category="" ClassName="dc.ocr.OcrService" PoolSize="1" Enabled="true" 
Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
  </Item>
  
  <Item Name="JavaGateway" Category="" ClassName="EnsLib.JavaGateway.Service" PoolSize="1" 
Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
    <Setting Target="Host" Name="ClassPath">.:/usr/irissys/dev/java/lib/JDK18/*:/opt/irisapp/*
:/usr/irissys/dev/java/lib/gson/*
:/usr/irissys/dev/java/lib/jackson/*:/jgw/ocr-pex-1.0.0.jar
</Setting>
    <Setting Target="Host" Name="JavaHome">/usr/lib/jvm/java-8-openjdk-amd64/</Setting>
  </Item>

</Production>

}

```
  
}

Into the production we have our ObjectScript OCRService too. This ObjectScript class receive the file uploaded from a HTTP multipart request and uses JavaGateway to load the Java class, pass the file and get the text from OCR process, so returns the response. See:

```
Class dc.ocr.OcrService Extends Ens.BusinessService
{

// extends Ens.BusinessService to create a custom Business service using Object Script
  
// This class receive a file from a multipart http request and save 
// to the folder configured into folder parameter
  
// choose an adapter to get data from a source of data
  
// HTTP.InboundAdapter allows you get data from an http request
  
Parameter ADAPTER = "EnsLib.HTTP.InboundAdapter";
  
// custom parameter to allows production user set destination folder to multipart file uploaded 
  
Property Folder As %String(MAXLEN = 100);
  
// when you set parameter Folder to SETTINGS parameter, the production IRIS interface 
// create a field to the user fills
// so the user will inform host path for the uploaded file 
  
Parameter SETTINGS = "Folder,Basic";
  
// This method is mandatory to have a business service. It receives the multipart file into pInput 
  
// and returns a result to the caller using pOutput
  
Method OnProcessInput(pInput As %GlobalBinaryStream, pOutput As %RegisteredObject) As %Status
{
    //try to do the actions
    try {
        Set reader = ##class(%Net.MIMEReader).%New() //creates a MIMEReader to extract files 
//from multipart requests 
        Do reader.OpenStream(pInput) //reader open the file
  
        Set tSC = reader.ReadMIMEMessage(.message) //the reader put the file uploaded into a MIME Message
        //Get Header obtains headers from the request and the multipart file, like content-type or content 
//disposition the content disposition have 3 headers: Content-Disposition: form-data; name="file"; 
//filename="filename.ext". This split content-disposition header into 3 parts
        Set filenameHeader = $PIECE(message.GetHeader("CONTENT-DISPOSITION", .header),";",3) 
        //get filename header value
        Set filename = $EXTRACT(filenameHeader, 12, $LENGTH(filenameHeader)-1)
        //Headers are not more needed. It clean the header to remains only the file content to be saved
        Do message.ClearHeaders()
  
        //create a file object to save the multipart file
        Set file=##class(%Stream.FileBinary).%New()
        //points the file to folder informed into folder parameter, plus upload filename from header
        Set file.Filename=..Folder_filename 
        //save body message (the file content) to file object
        Do file.CopyFromAndSave(message.Body)
  
        // Connect a Gateway instance to server JavaGate on the host machine
        set GW = ##class(%Net.Remote.Gateway).%New()
        set st = GW.%Connect("127.0.0.1", "55555", "IRISAPP",,)
        //instantiate java ocr class
        set proxyOcr = ##class(%Net.Remote.Object).%New(GW,"community.intersystems.pex.ocr.OcrOperation")
        //call ocr method to get text from image
        set pResponse = proxyOcr.doOcr(file.Filename)
        //returns to the service
        Set pOutput = pResponse
        Set tSC=$$$OK
    
    //returns error message to the user
    } catch e {
        Set tSC=e.AsStatus()
        Set pOutput = tSC
    }
  
    Quit tSC
}
  
}


```
Now, IRIS can do OCR to images and PDF, but with JavaGateway function can do many amazing things. See all details into my app code: https://openexchange.intersystems.com/package/OCR-Service.