Extrayendo texto de una imagen usando GOCR

La verdad es que las pocas veces que había necesitado sacar un texto de una imagen había usado tesseract. Pero gracias a @garciademarina he conocido otra librería que me ha ido mucho mejor (esto no significa que sea mejor para todos los casos): GOCR.

Para compararlos voy a realizar los siguientes ejemplos a partir de una misma imagen:

  1. Tesseract: Ejecutar tesseract a partir de una imagen TIFF.
  2. GOCR: Ejecutar GOCR a partir de una imagen PBM.
  3. Tesseract: Ejecutar tesseract a partir de una imagen PBM.
  4. GOCR: Ejecutar GOCR con training.
Imagen de prueba utilizada para realizar la comparación

Imagen de prueba utilizada para realizar la comparación

Requisitos

Será necesario tener instalado: ImageMagick, tesseract y GOCR. Si usáis OS X y usais brew, es tan sencillo como:

$> brew install imagemagick
$> brew install tesseract
$> brew install gocr

1. Tesseract: Ejecutar tesseract a partir de una imagen TIFF

Lo primero que hay que hacer es convertir la imagen a TIFF:

$> convert hn.png hn.tiff

Podríamos intentar procesar la imagen mejor: asegurarnos de que esta en blanco y negro, ampliarla, etc. Pero como es una imagen bastante nítida y sin diferentes colores no lo he hecho.

Después ejecutamos tesseract y hacemos un cat del fichero para ver el resultado:

$> tesseract hn.tiff output > /dev/null 2>&1 && cat output.txt

Resultado

Such a great article, true Hacker News material. Does nm assume much knawledge about
the field, ye! still anaws far a much deeper understanding of mechanks involved.

No esta mal, pero como podéis ver hay algunos errores.

2. GOCR: Ejecutar GOCR a partir de una imagen PBM

En este caso primero deberemos pasar la imagen a PBM ya que GOCR solo acepta ciertos formatos: pnm, pgm, pbm, ppm, pcx…

$> convert hn.png hn.pbm

Después ejecutaremos GOCR y nos devolverá directamente el texto:

$> gocr hn.pbm

Resultado

Such a great article, true Hacker News material. D0es n0t assume much kn0wledge ab0ut
the field, yet still all0ws f0r a much deeper understanding 0f mechanics inv0lved.

En este caso solo ha tenido problemas con las “o”.

3. Tesseract: Ejecutar tesseract a partir de una imagen PBM

$> tesseract hn.pbm output > /dev/null 2>&1 && cat output.txt

Resultado

such a great amde, true Hacker News makenm Does not assume much l<now\edge about
me hem, yet shH aHows for a much deeper understandmg of mechamcs mvoh/ed

Este todavía ha resultado peor que realizarlo a partir de la imagen TIFF.

4. GOCR: Ejecutar GOCR con training

$> gocr hn.pbm -m 162

En este caso no nos ha preguntado por ninguna corrección porque se estaba dentro del umbral de certeza que tiene por defecto: 95%. Por eso en este caso vamos a especificar que queremos un umbral del 100%:

$> gocr hn.pbm -m 162 -a 100

CLI

Resultado

Such a great article, true Hacker News material. Does not assume much knowledge about
the field, yet still allows for a much deeper understanding of mechanics involved.

Como podéis observar en este caso la conversión ha sido perfecta. Si bien es cierto que ha sido porque le hemos ayudado, esta ayuda solo se realiza la primera vez y poco a poco GOCR va aprendiendo a convertir mejor la imagen a texto.

Conclusión

Si sois pacientes os recomiendo que pongáis un threshold del 100% y que vayáis enseñando poco a poco al GOCR a convertir correctamente el texto.

digg reader screenshots

Que caracteres no usar en un fichero de cron

Después de ver que no me funcionaba una cron (en /etc/cron.d) en la que la estructura estaba bien… me he dado cuenta que el problema estaba en el nombre del fichero de la cron. No se puede poner cualquier nombre… en mi caso el fallo estaba en que había usado un punto. Con lo cual… a modo recordatorio, caracteres permitidos en el nombre de un fichero de cron: caracteres en mayúsculas y minúsculas y guiones.

P.S.: Newbie fail :)

Como usar Hubot en TalkerApp

Para los que no conozcan Hubot o TalkerApp, les recomiendo visitar sus webs donde explican detalladamente para que sirven. Por defecto Hubot se creo para Campfire pero existe un adapter para Talker.

Este ejemplo es para usar en un entorno UNIX. Es importante tener en cuenta que vas a necesitar tener instalado:

Primero de todo tendrás que hacer un clone del proyecto:

$> git clone https://github.com/github/hubot.git ~/hubot

Después tendremos que instalar las dependencias y exportarlo al directorio que queramos:

$> cd ~/hubot
$> npm install
$> ./bin/hubot -c ~/hubot-talker

Una vez exportado tendremos que añadir en el fichero composer.json la dependencia de hubot-talker:

{
  ....

  "dependencies": {
    "hubot": ">=2.4.6",    
    "hubot-scripts": ">= 2.4.1",
    "hubot-talker": ">= 1.0.0",
    "optparse": "1.0.3"
  },

  ....
}

Una vez añadida la dependencia tendremos que actualizar los paquetes:

$> npm update

Para finalizar si no queréis usar Redis ya solo os faltará quitar redis-brain.coffee de hubot-scripts.json y exportar:

  • HUBOT_TALKER_ROOMS: Las salas en las que quieres que entre el bot. Si quieres usarlo en más de una sala solo tendrás que separar por comas el número de las salas.
  • HUBOT_TALKER_TOKEN: El token del usuario del bot.
$> chmod +x ~/hubot-talker/bin/hubot
$> export HUBOT_TALKER_ROOMS="<TALKER ROOM>"
$> export HUBOT_TALKER_TOKEN="<TALKER TOKEN>"
$> ~/hubot-talker/bin/hubot -a talker -n hubot

Google Analytics para GlotPress Plugin

En GlotPress también se pueden crear plugins, aunque aquí no hay panel de administración como en WordPress para activarlos y desactivarlos… con lo cual, una vez lo pones ya está activo!

En este caso he creado un plugin para añadir Google Analytics a GlotPress. Simplemente deberíais crear un fichero nuevo en la carpeta de ./plugins que está en la raíz de GlotPress con el siguiente código:

<?php

class Google_Analytics extends GP_Plugin {
    var $ga_id;

    function __construct() {
        parent::__construct();

        $this->add_action( 'gp_footer' );
        $this->ga_id= 'YOUR_GA_TRACKING_ID';
    }

    function gp_footer() {
        $footer = <<<FOOTER
<script type="text/javascript">
    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', '$this->ga_id']);
    _gaq.push(['_trackPageview']);

    (function() {
        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
    })();
</script>
FOOTER;
        echo $footer;
    }
}

GP::$plugins->google_analytics = new Google_Analytics;

Para acabar, solo tenéis que sustituir YOUR_GA_TRACKING_ID por vuestro tracking id!

Dos trackings de Google Analytics diferentes en una web

Aunque quizá no es lo más común, es posible que alguna vez queramos poner dos trackings ids de Google Analytics en una misma web. No es tan sencillo como duplicar el código de Google Analytics, no funcionaría bien. Un ejemplo básico de como con un código de seguimiento:

<script type="text/javascript">
    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', 'UA-1111111-1']);
    _gaq.push(['_trackPageview']);

    (function() {
        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/u/ga.js';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
     })();
</script>

En caso de que quisiéramos poner dos (o más) trackings sería un poco diferente. Aquí el ejemplo:

<script type="text/javascript">
    var _gaq = _gaq || [];
    _gaq.push(function() {
        var _gaq_site_1 = _gat._createTracker('UA-1111111-1');
        _gaq_site_1._trackPageview();
        var _gaq_site_2 = _gat._createTracker('UA-2222222-2');
        _gaq_site_2._trackPageview();
    });

    (function() {
        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/u/ga.js';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
     })();
</script>

Cómo crear un usuario en GlotPress

GlotPress es un herramienta web open-source de traducción desarrollada por WordPress.

Por defecto no incorpora ninguna gestión de usuarios. Solo se crea un usuario durante la instalación y es el administrador. Si tienes un blog con WordPress, no debería ser un problema ya que puedes usar los mismos usuarios del blog simplemente definiendo en el fichero gp-config.php de que tablas quieres que coja los datos:

define('CUSTOM_USER_TABLE', 'wp_users');
define('CUSTOM_USER_META_TABLE', 'wp_usermeta');

Hay que tener en cuenta que las tablas de GlotPress y WordPress tienen que estar en la misma base de datos. Si no quieres instalar WordPress para añadir nuevos usuarios, puedes usar el siguiente script:

<?php
    require_once dirname( dirname( __FILE__ ) ) . '/gp-load.php';

    class GP_Script_Add_User extends GP_CLI {

        var $short_options = 'u:e:p:';

        var $usage = "-u <username> -e <e-mail> [-p <password>]";

        function run() {
            if ( !isset( $this->options['u'] ) || !isset( $this->options['e'] ) ) {
                $this->usage();
            }

            $user_by_login = GP::$user->by_login( $this->options['u'] );
            if ( $user_by_login ) {
                $this->to_stderr( sprintf("User '%s' already exists.", $this->options['u']) );
                exit( 1 );
            }

            $user_by_email = GP::$user->by_email( $this->options['e'] );
            if ( $user_by_email ) {
                $this->to_stderr( sprintf("Email '%s' already exists.", $this->options['e']) );
                exit( 2 );
            }

            $args = array();
            $args['user_login'] = $this->options['u'] ;
            $args['user_nicename'] = $this->options['u'] ;
            $args['display_name'] = $this->options['u'] ;
            $args['user_email'] = $this->options['e'] ;
            if( isset( $this->options['p']) ) {
                $args['user_pass'] = $this->options['p'] ;
            }

            $user = GP::$user->create( $args ) ;
            if( !$user ) {
                $this->to_stderr( sprintf("User '%s' hasn't been added.", $this->options['u']) );
                exit( 3 );
            }

            $this->to_stderr( "New user has been added:" );
            $this->to_stderr( sprintf(" - user: %s", $user->user_login) );
            $this->to_stderr( sprintf(" - e-mail: %s", $user->user_email) );
            $this->to_stderr( sprintf(" - pass: %s", $user->plain_pass) );
        }
    }

    $gp_script_add_user = new GP_Script_Add_User;
    $gp_script_add_user->run();
?>

Este fichero va en la carpeta de scripts de GlotPress. En mi caso lo he llamado add-user.php:

php scripts/add-user.php -u <username> -e <e-mail> [-p <password>]

El parámetro password es opcional, en caso de no ponerlo generará uno aleatorio:

$> php scripts/add-user.php -u glotpress -e glotpress@juanramon.me
New user has been added:
- user: glotpress
- e-mail: glotpress@juanramon.me
- pass: 1Owg6XO3x@xp

Cómo usar el ShareActionProvider de Android

En la versión 4.0 de Android, ICS, se añadió una nueva forma de compartir contenido a través del ActionBar. Gracias a la versión 4.0.1 de ActionBarSherlock se puede usar en todas las versiones de Android. La documentación explica bien como implementar esta funcionalidad, pero me gustaría explicar un par de detalles en los que no he caído y que me han estado causando problemas:

  • Es muy importante definir correctamente el atributo android:actionProviderClass. En el ejemplo usa la clase nativa de Android, pero se ha de tener en cuenta que al usar ActionBarSherlock el provider es diferente, con lo cual quedaría algo así:
    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:id="@+id/menu_share"
              android:title="@string/share"
              android:showAsAction="ifRoom"
              android:actionProviderClass="com.actionbarsherlock.widget.ShareActionProvider" />
        ...
    </menu>
  • Se ha de hacer set al share de nuevo si el contenido cambia dinámicamente. En mi caso, como estaba usando un ViewPager, he creado un listener para hacer un set al share intent cada vez que cambiaba la página. Ha quedado de la siguiente forma:
    private class ViewPageChangeListener extends ViewPager.SimpleOnPageChangeListener {
        @Override
        public void onPageSelected(int pos) {
            if (mShareActionProvider != null) {
                mShareActionProvider.setShareIntent(createShareIntent());
            }
        }
    }

Para mi son los dos puntos más importantes a tener en cuenta a la hora de implementar el nuevo share action.

Como borrar un tag remoto en git

No es muy común borrar un tag en git, y menos en remoto. Pero por si lo necesitáis, a continuación explico como:

git tag -d v1.0
git push origin :refs/tags/v1.0

En este caso el nombre del tag a borrar sería v1.0