Integración de OCR en Alfresco

Hace un par de semanas, Cesar Capillas y yo intercambiamos unos correos, ente los temas que tratamos hablamos sobre qué estábamos pensando escribir en los blogs respectivos y ambos coincidíamos con algo relacionado con Alfresco y OCR, algo sencillo pero que ofreciera una nueva funcionalidad al usuario final, así que quedamos unos días más tarde para profundizar en el tema y ponernos manos a la obra. Aquí está el resultado.

  • Introducción

En el siguiente artículo vamos a ver cómo configurar Alfresco para poder indexar automáticamente el contenido de un fichero TIF escaneado, subirlo al repositorio y poder localizarlo posteriormente buscando por el contenido del mismo. Todo ello pasándole a cada fichero TIF un OCR de forma transparente para el usuario.

En materia de escaneo y almacenamiento de información en Alfresco se ha escrito mucho y hay muchas soluciones disponibles de diferente índole que ya vimos aquí hace tiempo.

Cuando se va ha digitalizar una gran cantidad de contenidos en papel y se quiere extraer su contenido vía OCR para poderlo indexar con Alfresco, disponemos de varias herramientas empresariales que permiten revisión, OCR zonal, mayor escalabilidad, mapeo de metadatos, capacidades de integración, inyección masiva y otras funcionalidades propias de entornos donde se va a hacer un uso intensivo de estas características. Para eso se encuentra aplicaciones como las siguientes:

Kofax Ascent Capture: conector soportado con Alfresco Enterprise.

Introspeqt InstaCapture: guía.

Ephesoft: CMIS

Cobra Technologies: CMIS

Sira Systems.

  • Caso de uso

Como hemos comentado anteriormente, con las indicaciones que se explican a continuación conseguimos extraer la información OCR de un fichero TIF de forma transparente, el texto extraído del documento escaneado es indexado automáticamente.

En principio las pruebas las hemos hecho en Linux (Ubuntu y CentOS) usando Tesseract versión 2.04 y Alfresco 3.3/3.4 (tanto Community como Enterprise)

  • Usando Tesseract

Hemos elegido Tesseract por ser uno de los más comunes OCR en el mundo Open Source, pero se puede usar cualquier otro, e incluso añadiendo más características. Usando Tesseract 2.X hay que tener en cuenta que los TIF que se suben deben estar sin comprimir ya que es el único formato que soporta esta versión. Para soportar TIF comprimido hay que usar la versión 3 de Tesseract.

Hay otros OCR tanto libres como privativos, cada uno soporta unos ficheros de entrada u otros aunque en casi todos los casos el común denominador es el formato TIF. Algunos ejemplos de software alternativo a Tesseract son gocr, ocrad, ocropus o cuneiform. Para Windows hay otros como TigerOCR o incluso los mismos Tesseract y cuneiform tienen versiones para Windows.

Una vez instalado Tesseract bien sea compilando o mediante apt/yum. Podemos probar su correcto funcionamiento con el siguiente comando (especificamos el idioma aunque por defecto está en inglés, así que debes instalar el idioma correspondiente que viene con el paquete):

# tesseract documento.tif documento -l spa

Recuerda que el documento.tif debería ser un fichero de un documento escaneado en español y con formato TIF sin comprimir, documento es la salida del comando, es decir, un fichero txt con el contenido detectado mediante el ocr. La opción –l permite especificar el idioma del documento escaneado para afinar el reconocimiento.

Si el comando anterior ha funcionado correctamente podemos seguir.

  • Creación del script

Ejecutando el comando anterior obtendremos en un archivo de texto el conjunto de palabras extraídas de la imagen en cuestión. Esto en principio no es una tarea sencilla para el motor de OCR y no siempre extraerá con éxito las palabras, a no ser que este bien entrenado.

Desde el punto de vista de Alfresco prepararemos un transformador, que invoca un script de shell que transforma un archivo tif a texto plano (extrae el texto identificado). Automaticamente el texto  obtenido se indexará en el motor de Lucene de Alfresco. Esta misma idea se ha utilizado en otros transformadores como la siguiente, donde extraiamos el texto de un texto XML via xmlstarlet:

El script va hacer algunas cosas más y está hecho en python (ocr-simple.py) y para su ejecución se necesita un interprete de python. El script hace algunas cosillas más que la ejecución del comando en cuestión. Por ejemplo, utiliza un diccionario de palabras donde cotejar las coincidencias. Por defecto utiliza una ruta que apunta al diccionario de palabras de ispell en Linux (paquete ispell o aspell dependiendo de la distribución), pero se puede utilizar otro archivo. Por otro lado, el script limpia y normaliza las palabras obtenidas por el ocr y recoge aquellas que tienen más de tres caracteres. Buena parte de estas acciones de limpieza y normalización seguro que las hace internamente Lucene pero nunca están de más. Aquí podemos ver el contenido del fichero ocr-simple.py (modifica las rutas según tu sistema y hazlo ejecutable):

#!/usr/bin/python
from os import popen
from string import split,join
from pprint import *
import re
import sys

def uniq(seq):
    # Not order preserving
    keys = {}
    for e in seq:
        keys[e] = 1
    return keys.keys()

# Tamano maximo
maxstr  = 3

# Comando ocr en la salida estandar
#command = popen('/usr/bin/pngtopnm '+sys.argv[1]+' | /usr/bin/ocrad')
#command = popen('/usr/bin/pngtopnm '+sys.argv[1]+' | /usr/bin/gocr -')
command = popen('/usr/bin/tesseract '+sys.argv[1]+' /tmp/tesser-$$ 2> /dev/null; cat /tmp/tesser-$$.txt')

# Diccionario
dictwrd = open("/usr/share/dict/words","r")
s       = dictwrd.read()
# Lineas ocr
lines   = command.readlines()
#pprint(lines)
text = []
for i in lines:
  # Quitar los saltos de linea
  i = i.strip("\n")
  # En minusculas
  i = i.lower()
  for j in i.split(" "):
    # Quitar los puntos y comas....
    myregex = re.compile(r"[^\w\s]")
    j = myregex.sub('', j)
    # Tamano maximo de palabra
    if len(j) >= maxstr:
      if re.search(j, s):
        text.append(j)
      #  print j, "Match!"      # char literal
      #else:
      #  print j, "upppss!"

zz = open("/tmp/ocr.log","w")
zz.write(sys.argv[1]+"\n")
zz.write(join(uniq(text), " "))

# Palabras unicas
outputf = open(sys.argv[2],"w")
outputf.write(join(uniq(text), " "))

Un sitio para alojarlo puede ser: $ALF_HOME/bin

HINT: Se pueden usar otros OCR, por ejemplo, sustituyendo el comando de tesseract e instalando el OCR correspondiente (ocrad, gocr)


#command = popen('/usr/bin/pngtopnm '+sys.argv[1]+' | /usr/bin/ocrad')

o


#command = popen('/usr/bin/pngtopnm '+sys.argv[1]+' | /usr/bin/gocr -')

  • Creación del transformador

El transformador se configura en el directorio de extensiones de Alfresco es decir:

$ALF_HOME/tomcat/shared/classes/alfresco/extension

Se define el worker, la invocación al script ocr-simple.py (que tiene que tener permisos de ejecución) y el mimetype de los archivos sobre los que se aplica la transformación, en este caso, de tif a texto plano (revisa las rutas de este xml para que estén acorde a tu instalación):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

  <bean id="transformer.OCRToText" class="org.alfresco.repo.content.transform.ProxyContentTransformer" parent="baseContentTransformer">
      <property name="worker">
         <ref bean="transformer.worker.OCRToText" />
      </property>
  </bean>

  <bean id="transformer.worker.OCRToText" class="org.alfresco.repo.content.transform.RuntimeExecutableContentTransformerWorker">
      <property name="mimetypeService">
         <ref bean="MimetypeService"/>
      </property>
      <property name="transformCommand">
         <bean class="org.alfresco.util.exec.RuntimeExec">
            <property name="commandsAndArguments">
                <map>
                    <entry key="Linux">
                        <list>
                            <value>/usr/bin/python</value>
                            <value>/opt/alfresco33/bin/ocr-simple.py</value>
                            <value>${source}</value>
                            <value>${target}</value>
                        </list>
                    </entry>
                </map>
            </property>
         </bean>
      </property>

    <property name="explicitTransformations">
      <list>
        <bean class="org.alfresco.repo.content.transform.ExplictTransformationDetails">
          <property name="sourceMimetype"><value>image/tiff</value></property>
          <property name="targetMimetype"><value>text/plain</value></property>
        </bean>
      </list>
    </property>

  </bean>
</beans>

Por descontado el transformador se puede extender a otros mimetypes de tipo imagen aunque esto no lo detallaremos aquí. También se podría implementar fácilmente en Windows.

  • Ejemplo de uso

Una vez dispuesto el fichero ocr-context.xml en el directorio de extensiones y el script de python ocr-simple.py en el directorio bin y reiniciado el servidor, tan sólo tenemos que subir una imagen en formato tif (con extension .tif). Una vez subida podemos buscar alguno de los términos en el buscador de la parte superior derecha de Alfresco Explorer o Share, por ejemplo, las palabras lazy o fox. De este modo subimos el archivo simple.tif a Alfresco:

Y buscamos la palabra fox de la imagen:

El resultado es el siguiente, aparecen un conjunto de archivos indexados que contienen la palabra fox, entre ellos la imagen que acabamos de subir.

Para trazar lo que esta haciendo el transformador se puede activar el log4j ($ALF_HOME/tomcat/webapps/alfresco/WEB-INF/classes/log4j.properties)

log4j.logger.org.alfresco.util.exec.RuntimeExec=DEBUG
log4j.logger.org.alfresco.repo.content.transform.ContentTransformerRegistry=DEBUG
  • Referencias:

Entrada en el foro de Alfresco en Español:

http://forums.alfresco.com/es/viewtopic.php?f=11&t=2979&start=0&hilit=tesseract

Tesseract OCR:

http://code.google.com/p/tesseract-ocr/wiki/ReadMe

Ficheros usados en el artículo: ocr-files-web.

Nota: En Alfresco Share el comportamiento es el mismo, esta integración es independiente del cliente web.

40 thoughts to “Integración de OCR en Alfresco”

  1. Pingback: Cesar Capillas
  2. Hola Fonts,

    Mira, poca cosa aporto a tu mágnifico blog, pero algo de inglé si que lo sé.
    Pue, corrección… es … “The quick brown fox jumped over the lazy dog”. Desde cuando tienes un zurro lazy, tio ! broma :) buen artículo como siempre !

    Sl2
    Guillermo Riachuelo

  3. Hola Toni.

    Me surgen unas dudas al ver tu implementación respecto a unas realizadas por mi (con cuneiform en lugar de Tesseract):

    1) ¿No es necesario definir una regla que lance la transformación al llegar el contenido al espacio o lanzar la transformación manualmente sobre el contenido?

    2) El fichero que encuentra Alfresco al realizar la búsqueda es el “simple.tif”, ¿de alguna forma Alfresco asocia el texto OCR de simple.txt (creado en la transformación) con simple.tif? Esto me ha dejado de piedra porque no lo había visto antes.

    Ya que escribo aprovecho y te felicito por tu blog. Un saludo.

  4. Hola Victor,

    Respondo a tus preguntas:

    1) No, se hace automáticamente gracias al transformador (xml).

    2) Si, así funciona, es algo bastante potente y extensible.

    Saludos.

  5. Hola Victor:

    Te respondo brevemente:

    1) En un transformador de alfresco que crea el simple.txt en el repositorio esto sería como dices. Primero habría que declarar el transformador en el web-client-config-custom.xml y luego crear la regla que lance un script que invoque tu transformador via API Freemaker.

    2) No es necesario porque la transformación a texto plano es procesada por el propio indexador de texto de lucene. Ten en cuenta que no se crea un archivo de texto en el repositorio. En ese sentido pasaria lo mismo si lo transformas a otro formato indexable por alfresco como html….

    Un saludo.

    –C.

  6. Muchas gracias. Voy a probar ahora mismo si es cosa de targetMimetype = application/pdf cuando defines el transformador lo que evita que sea automático todo.

  7. Pingback: Cesar Capillas
  8. Buenas,

    Unas dudas relacionadas con las presentadas por Víctor Suárez.

    1- Sobre la transformación automática, con el contenido del transformador xml no creo que sea suficiente. Supongo que habeis creado una regla automática que al detectar la inserción de un nuevo tiff en el repositorio se ejecute automáticamente el transformador. ¿Cierto?

    2- No entiendo lo que indica César con lo que es el propio indexador de texto de lucene el que se encarga de asociar el texto a la imagen. Según el script ocr-simple.py en las últimas líneas añades el texto procesado al fichero de destino (texto plano, .txt?). ¿Este fichero de destino no se guarda en el repositorio? ¿Cómo y cuando asocia el indexador el texto al tiff? ¿Se puede consultar el texto desde algún metadato del tiff?

    Un saludo

  9. Hola Jose Antonio,

    1. No necesitas regla alguna, una vez definido el transformador a texto plano.
    2. El fichero de destino no se guarda en el repositorio pero se indexa automáticamente por Lucene como cualquier archivo de texto que subes a alfresco, sin tener que definir nada. No se puede consultar el texto indexado desde un metadato sin mas.

    Pero podrías llegar a definir una regla que ejecute un script de alfresco para que recoja el texto de un archivo de texto ya generado en el repositorio, y que su contenido lo guarde en el campo descripción del documento o otro campo de texto definido en un aspecto por ejemplo (via javascript API).

    Un saludo.

    –C.

  10. Buenas César,

    Perdona si soy muy pesado, pero todavía no me cuadra la cosa.

    1- Que yo sepa las transformaciones se ejecutan a través de una acción o una regla. En este caso, ¿cuál sería el disparador de la transformación? ¿O es que simplemente definiendo un transformador cuyo destino es texto plano ya se ejecuta automáticamente? Si es esto último, ¿tienes alguna referencia de documentación de alfresco donde se den mas detalles sobre dicha automatización?

    2- Si, entiendo lo de la indexación por contenido en Lucene. ¿Pero cómo es que dicha indexación se asocia sobre el tiff? En el artículo si buscas por el contenido te sale el tiff pero el contenido del tiff sigue siendo binario y no indexable. En conclusión, no veo en el código del artículo el momento de dicha asociación al tiff.

    3- Otra cosa, quizás esto me aclara el tema, ¿cuál es el destino de la transformación si como comentas no es un fichero de texto plano en el repositorio?

    Un saludo y muchas gracias por tu rápida respuesta.

  11. Hola chicos!, en primer lugar .. muy buen artículo! .. en segundo .. gracias por el aporte! .. y para finalizar .. ¿A que se debe el hecho de que utilicéis la versión 2.04 de tesseract en lugar de la 3.0?
    Saludos!

  12. Gracias Javier, la verdad es que da igual una que otra para esta prueba de concepto, una de las diferencias más importantes entre ambas es el soporte de TIFF comprimidos y alguna cosilla más. En realidad puedes usar el OCR que quieras, no tiene por que ser Tesseract.

    Saludos.

  13. Buenas Jose Antonio:

    1. Es cierto que las transformaciones se ejecutan a través de una acción y de hecho lo puedes hacer directamente en el cliente web. Aplica una acción sobre el tif –> “Copiar imagen en un formato dado a una ubicación especifica”, el formato resultante es texto. También invocando el api javascript sobre el documento en una acción donde se recoja el nodo y se aplique:

    node.transformDocument(“text/plain”, companyhome);

    o traccionado a través de una regla de contenido. El transformador esta disponible en el repositorio pero no se ha ejecutado o materializado en el proceso de subida en un archivo .txt, pero los servicios asociados a la indexación del tif por parte de Lucene en el proceso de subida se han disparado. Por otro lado, ten en cuenta que hay multitud de formatos en Alfresco que internamente se indexan, pdf, doc, odt…., sin necesitar de realizar transformaciones a texto plano, e internamente se ejecutan procesos de indexación similares.

    2. Ahí tendrías que llegar a bajo nivel. Lucene solo indexa texto plano, e internamente vincula determinados campos en sus índices relacionados con la url/nombre con el texto indexado. Alfresco nos abstrae de todo eso en cualquier caso.

    3. Hasta que no la ejecutas explícitamente (que no el proceso de transformación + indexación) no defines donde se guarda.

    Espero que te sirva.

    Un saludo.

    –C.

  14. Perfecto César,

    Ahora te he entendido.

    Muy interesante esta funcionalidad, me la apunto!!.

    Muchas gracias

  15. Hola Toni. No se si te acuerdas de mi?

    Disculpa que te haga la pregunta por aquí, pero puede serle útil a otros.

    En relación al transformer, existe alguna posibilidad de conocer el nombre real de ${source} que se obtiene dentro del bean?

    Te cuento, lo necesito porque en base al nombre del archivo tif que yo suba, selecciono en qué idioma realizo el OCR pasándole el parámetro “-l” a tesseract. Es posible, incluso efectuar un regexp para saber si en el nombre del archivo tif incluye un determinada cadena?

    Muchas gracias y felicitaciones por tan buen blog lleno de datos!

  16. Hola Toni

    Pero en el script de python recibo el source con el nombre cambiado, lo recibo con el nombre content-tranformer-xxxxxxxxxxxxxxx.tiff o algo parecido y no tiene nada que ver con el nombre original, por ello quería saber si existe alguna manera de averiguarlo y pasarlo luego como un parámetro mas al script y alli hacer el resto.

    Gracias
    Normando

  17. Qué suerte haber encontrado este blog.
    Muchas gracias por el trabajo realizado y por compartirlo con todos.

    Estoy pensando en instalar en la oficina un Alfresco Community y me viene de perlas esta información.

    Tan sólo quería saber si esto mismo se puede hacer con documentos escaneados en pdf, es decir, pdfs que en realidad son imagenes.

    Supongo que sería lo mismo pero con el paso previo de PDF a Tiff ¿no?

  18. Gracias Tomas. Se podría hacer con un PDF que contuviese una imagen si el OCR de turno lo soportase, tendrías que ver las características de tesseract y otros que menciono ya que depende de ellos el formato que aceptan. Generalmente es TIFF.

    Saludos.

  19. Esto me hace plantearme… Realmente, la gente que escanea grandes cantidades de papel ¿lo almacena en TIFF? ¿o lo almacena en PDF?

  20. Hola Tomás, por lo que he visto, la mayoría de las organizaciones almacenan los “papeles” escaneados en PDF tras pasarle el OCR de turno al TIFF, pero como decía depende del software de OCR que se utilice.

  21. Hola Toni.

    Ayer estuve siguiendo este tutorial pero hay algo que me falla.

    He conseguido hacer que el transforme me trasnforme el .tif a .txt pero no consigo que lo idexe.

    Quiero decir, subo un .tif a alfresco, que tiene el worker en modo debug y veo que le pasa el ocr y genera el .txt. De hecho el tomcat/temp/alfresco/ teengo arxivo.UUID.tif y arcivo.OTRO UUID.txt pero si realizo alguna búsqueda por el texto no me encuentra nada.

    Estoy usando la versión alfresco 4 c.e.

    Seguro que no hay que decirle que debe indexar el txt en ningún sitio?

    Saludos y gracias

  22. Hola Juanjo,
    No lo he probado con la versión 4, igual hay que activar la indexación en el contenido a la hora de hacer la transformación ya que en la versión 4 es una opción nueva (la posibilidad de indexar contenidos bajo demanda).

    Saludos.

  23. Hola,

    Buscando solución a un problema que se me ha planteado he encontrado esta entrada, que es casi casi lo que necesito. La cuestión es que yo dispongo del OCR “de antemano”, es decir, una aplicación se encarga de hacer el escanéo de documentos y devuelve un PDF con el documento escaneado y su OCR, y luego el documento se sube a Alfresco vía Web Service. Por tanto, no necesito que Alfresco pase ejecute el OCR (al menos de momento…).

    Lo que necesito averiguar, y creo que está encaminado con lo que se expone en esta entrada, es de qué forma se puede asociar un texto plano a un documento en Alfresco, e incluso poder modificarlo a posteriori como si se tratara de un metadato más del documento (justamente lo que quiero evitar es tratarlo como un metadato…).

    Muchas gracias.

    Un saludo,
    Christian

  24. Hola Christian, me alegro que te sea “casi” de ayuda ;)

    No se si entendí bien pero si subes un documento PDF con texto adjunto (de su OCR) a Alfresco automáticamente lo extrae y lo indexa, por lo podrás buscar por el texto (Full Text Search) a posteriori.

    Saludos.

  25. Hola,

    Gracias por tu respuesta (¡y por la rapidez!).

    ¿Entiendo que la solución sería “meter” de alguna forma el OCR dentro del fichero PDF, y entonces subir el PDF? Ahora mismo lo que tengo es un PDF con las imágenes escaneadas, sin texto, sin OCR, por un lado, y por otro lado una cadena con el OCR; la idea que estaba buscando es, de algún modo, poder decirle a Alfresco que asocie a un PDF que voy a subir y que no tiene texto el texto que yo le indique.

    Un saludo,
    Christian

  26. Hola Christian, perdona que no te entendí correctamente. Entonces puedes hacerlo como se muestra en el artículo usando un transformador.
    Saludos.

  27. Hola,

    Gracias por tu respuesta, me centraré en el uso de un transformador.

    Sin embargo, hay algo que no termino de entender. Por lo que deduzco del artículo y de los comentarios posteriores, el script en Python sólo extrae el OCR, no lo asocia al documento TIFF, ¿me equivoco?; por tanto, de algún modo, a través del transformador el OCR es asociado por Lucene al documento TIFF, posibilitando la posterior búsqueda por contenido. ¿En mi caso qué debería hacer? ¿Almacenar un primer documento TXT con el OCR y luego subir el PDF, de modo que el transformador acceda al primer TXT para leer su contenido y así Lucene lo indexe asociándolo al PDF?

    Un saludo,
    Christian

  28. Hola Christian, en la DevCon estuve hablando de esto con mi compañero Nathan creador de Alfresco PDF Toolkit y van a añadir esta característica al módulo basándose en la idea de este artículo, échale un vistazo que te será de ayuda seguro, además las nuevas funcionalidades son una pasada: http://code.google.com/p/alfresco-pdf-toolkit/ recuerda descargarte los fuentes del svn que están más actualizados.

  29. Toni una pregunta, nosotros requerimos hacer escaneo zonal de algunos documentos (recibos de luz, agua) para extraer el nombre, dirección, teléfono de los clientes. ¿Hay forma de hacer escaneo zonal con Tesseract? ¿O ?

  30. ¿Hay forma de hacer escaneo zonal con Tesseract? ¿O que software me recomiendas? Perdón pero la pregunta anterior se me fue antes de terminar de escribirla.

  31. Hola Buen día Toni muchas gracias por este blog, ha sido de gran ayuda para muchos, seguí el tutorial tal cuál pero me encuentro en la misma situación que la que describe JUANJO ORTILLES en los comentarios de este mismo blog, es decir, se genera el archivo del script de python en la ruta tomcat/temp/alfresco/ pero no me regresa alfresco ningún resultado, entiendo que comentaste acerca de configurar alfresco para reindexar contenido por demanda, mi pregunta es ¿tienes algún ejemplo para hacer eso? mi versión de alfresco es la 5.0.d por cierto. Muchas gracias y saludos…

  32. Hola Uriel, este artículo lo escribí en 2010 y es una prueba de concepto, Alfresco ha cambiado desde la versión 3.X entonces así que es lógico que no funcione tal como se describe en el artículo, hay que considerar que en versiones 5.x el motor de indexación es Solr en lugar de Lucene. Al menos lo que cuento aquí puede servir de referencia o idea pero no como un manual que pueda servir para 5.X. Puedes mirar en addons.alfresco.com que seguro hay ya algún plugin que haga lo que necesitas.
    Saludos.

Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.