¿El TIFF contiene una imagen o por el contrario contiene un MDT/MDS?

TIFFDump mostrando la cabecera de un archivo MDS obtenido mediante un radar.

TIFFDump mostrando la cabecera de un archivo MDS obtenido mediante un radar.

Acabo de añadir en DigiNG la posibilidad de cargar archivos MDS/MDT contenidos en un archivo .tif.

El usuario carga el archivo .tif con la orden CARGA_F, y es DigiNG quien decide si carga el archivo como una imagen o como un MDT. En este post te voy a explicar el criterio que utiliza DigiNG para decidir cómo carga el archivo.

Para ello vamos a utilizar una herramienta denominada TIFFDUMP que viene junto con el código fuente de la librería libtiff (lo encontrarás en la carpeta bin de este enlace).

Esta herramienta (que es una aplicación de consola) nos muestra el contenido de la cabecera de un archivo .tif.

El formato TIFF es un formato muy bien pensado (exceptuando que no permite crear archivos de más de 4GB de tamaño, aunque ese pequeño problema ya está solucionado con el formato BigTiff), y tiene una cabecera muy completa que permite indicar todas las características de la imagen contenida dentro del archivo.

Esas características se almacenan en variables. Existen muchísimas según el estándar, y si no encuentras la que necesitas para almacenar metadatos de la imagen, eres libre de añadir la que quieras. Si además tienes la suerte de que se acepte por la comunidad podrías conseguir que en una próxima revisión del estándar se convirtiera en una variable estandarizada.

Las variables no tienen por qué aparecer en el archivo .tif. Si no aparecen, se asume que su valor es el que tienen por defecto.

El programa tiffdump.exe es muy sencillo de ejecutar. Tan solo tenemos que abrir una consola, y ejecutarlo pásandole como parámetro el nombre del archivo .tiff a analizar.

En la siguiente tabla tienes varias variables junto con su significado:

Nombre de variable Significado
ImageWidth Ancho de la imagen en píxeles
ImageLength Alto de la imagen en píxeles.
Photometric Valor que indica el tipo de imagen (tonos de gris, color RGB, paleta de color, imagen que es únicamente un canal alpha, CMYK, YCrCb,…)
BitsPerSample Indica el número de bits que tendrá cada canal de la imagen.
SamplesPerPixel Indica el número canales que tendrá cada pixel. Si la imagen es RGB lo normal es encontrarse aquí un 3, si es en tonos de gris, lo normal es encontrar aquí un 1. Si es una imagen RGB con canal alpha, aquí tendremos un 4, si es una imagen en tonos de gris con canal alpha, nos encontraremos con un 2, etc.
SampleFormat Indica el tipo de valor almacenado en cada pixel. Este parámetro casi nunca se encuentra en una imagen Tiff. Si creas una imagen Tiff con Photoshop por ejemplo no verás este valor almacenado en el tiff, y se asumirá que el valor por defecto es 1, que significa entero sin signo.

Ahora bien, ¿cómo decidimos si una imagen es un MDT/MDS o no?

Nunca conviene reinventar la rueda, y si hay alguna aplicación estándar que realice la operación que quieres implementar, lo ideal es analizar el comportamiento de esa aplicación y realizar una implementación parecida. De ese modo estarás reforzando ese estándar de facto.

La aplicación a seguir en este caso, y sin ningún tipo de dudas es Global Mapper.

Lo único que necesitaba son una serie de imágenes, y como se por donde van los tiros, solicité a un colega que me crease archivos TIFF con MDTs en los siguientes formatos:

Tipo de imagen Canales bits por canal tipo de valor
Tono de gris 1 8 entero sin signo
Tono de gris 1 8 entero con signo
Tono de gris 1 16 entero sin signo
Tono de gris 1 32 valor en coma flotante
Tono de gris 1 64 valor en coma flotante

Y como era de esperar, Global Mapper únicamente trata como MDT/MDS los archivos con valor en coma flotante.

De modo que las pruebas que se realizan para comprobar si un TIFF es un MDT/MDS o no son las siguientes:

  1. Se comprueba que la imagen tenga un único canal
  2. Se comprueba que el número de bits del canal sea 32 o 64
  3. Se comprueba que la imagen informe del tipo de valor almacenado en cada pixel (te recuerdo que este campo habitualmente no aparece en la mayoria de archivos .tif)
  4. Se comprueba que el tipo de valor almacenado en cada pixel es valor en coma flotante (o lo que es lo mismo, un 3, que significa IEEE floating point data

Si se superan todas estas pruebas, DigiNG considera el archivo como un MDT/MDS. Si no, se considerará como una imagen.

Convirtiendo los métodos de extensión sobre secuencias de entidades en métodos genéricos

Vamos a realizar un cambio a la biblioteca de clases UtilidadesDigi que hemos ido construyendo en nuestro curso de programación de Digi3D:

Si te fijas en el código de los métodos de extensión UtilidadesSecuenciaEntity.QueTenganElCódigo, UtilidadesSecuenciaEntity.QueTenganElCódigoConComodín, UtilidadesSecuenciaEntity.QueTenganAlgúnCódigo y UtilidadesSecuenciaEntity.QueTenganAlgúnCódigoConComodín:

    public static IEnumerable<Entity> QueTenganElCódigo(this IEnumerable<Entity> secuenciaOriginal, string código)
    {
        return from entidad in secuenciaOriginal
               where entidad.TieneElCódigo(código)
               select entidad;
    }
    public static IEnumerable<Entity> QueTenganElCódigoConComodín(this IEnumerable<Entity> secuenciaOriginal, string código)
    {
        return from entidad in secuenciaOriginal
               where entidad.TieneElCódigoConComodín(código)
               select entidad;
    }
    public static IEnumerable<Entity> QueTenganAlgúnCódigo(this IEnumerable<Entity> secuenciaOriginal, IEnumerable<string> códigos)
    {
        return from entidad in secuenciaOriginal
               where entidad.TieneAlgúnCódigo(códigos)
               select entidad;
    }
    public static IEnumerable<Entity> QueTenganAlgúnCódigoConComodín(this IEnumerable<Entity> secuenciaOriginal, IEnumerable<string> códigos)
    {
        return from entidad in secuenciaOriginal
               where entidad.TieneAlgúnCódigoConComodín(códigos)
               select entidad;
    }

Siempre devuelven una secuencia de IEntity, aunque esa secuencia esté particularizada para un determinado tipo de entidad, y eso deshabilita todos los filtros que se hubieran aplicado antes, como en el siguiente ejemplo:

            var líneasDeMarcoHoja = DigiNG.DrawingFile.OfType<ReadOnlyLine>().QueTenganElCódigo(códigoDelMarco);

Vamos a estudiar que pasa con la línea de código anterior:

  • DigiNG.DrawingFile devuelve una secuencia de IEnumerable<IEntity>.
  • Generamos una secuencia de ReadOnlyLine mediante el método de extensión de Linq: OfType<T>. A partir de este momento tenemos una secuencia: IEnumerable<ReadOnlyLine>.
  • Luego llamamos al método de extensión UtilidadesDigiNG.QueTenganElCódigo que extuende IEnumerable<IEntity> y que devuelve una secuencia: IEnumerable<IEntity>.

Lo que resulta en que al final lo que tenemos es un IEnumerable<IEntity>, y está claro por el contexto que lo que queremos es un IEnumerable<ReadOnlyLine>.

En este caso particular, podríamos haberlo solucionado de la siguiente forma:

            var líneasDeMarcoHoja = DigiNG.DrawingFile.QueTenganElCódigo(códigoDelMarco).OfType<ReadOnlyLine>();

Pero esa no es la solución. Tenemos que modificar nuestros métodos de extensión para que sean genéricos, de forma que si están subclasificando una secuencia de X, que lo que devuelvan sea una secuencia de X y no una secuencia de otra cosa.

Éstos métodos van a extender secuencias de tipos cuya base sea IEntity, así que cuando las convirtamos en métodos genéricos tendrán que tener como restricción que el tipo genérico herede de IEntity.

Veamos cómo quedan nuestros métodos de extensión una vez convertidos en métodos genéricos:

    public static IEnumerable<T> QueTenganElCódigo<T>(this IEnumerable<T> secuenciaOriginal, string código)
        where T : Entity
    {
        return from entidad in secuenciaOriginal
               where entidad.TieneElCódigo(código)
               select entidad;
    }

    public static IEnumerable<T> QueTenganElCódigoConComodín<T>(this IEnumerable<T> secuenciaOriginal, string código)
        where T : Entity
    {
        return from entidad in secuenciaOriginal
               where entidad.TieneElCódigoConComodín(código)
               select entidad;
    }

    public static IEnumerable<T> QueTenganAlgúnCódigo<T>(this IEnumerable<T> secuenciaOriginal, IEnumerable<string> códigos)
        where T : Entity
    {
        return from entidad in secuenciaOriginal
               where entidad.TieneAlgúnCódigo(códigos)
               select entidad;
    }

    public static IEnumerable<T> QueTenganAlgúnCódigoConComodín<T>(this IEnumerable<T> secuenciaOriginal, IEnumerable<string> códigos)
        where T : Entity
    {
        return from entidad in secuenciaOriginal
               where entidad.TieneAlgúnCódigoConComodín(códigos)
               select entidad;
    }

De esta manera estos métodos de extensión devuelven subconjuntos de secuencias sin cambiar su tipo.

Comportamiento de los importadores de archivos de dibujo sin tabla de códigos

El día 01/02/2013 hemos añadido a Digi3D.NET la posibilidad de no indicar en el cuadro de diálogo Nuevo Proyecto, en la pestaña Archivo de dibujo ninguna tabla de códigos en el campo Tabla de códigos.

En este post te voy a explicar como funciona esta característica dentro de Digi3D.NET.

Modificaciones realizadas al programa principal

Digi3D.NET necesita una tabla de códigos para poder trabajar. Es absolutamente fundamental. Todo el programa está desarrollando teniendo en cuenta de que en memoria hay una tabla de códigos. Ahí es donde aparece la representación (color, grosor, patrones,…) de cada código, parámetros de transformación, …

Se han realizado dos modificaciones a Digi3D.NET para permitir trabajar sin tabla de códigos:

  • Permitir al usuario dejar en blanco el campo correspondiente en el cuadro de diálogo de nuevo proyecto.
  • Analizar si el usuario deje ha dejado en blanco ese campo. En caso afirmativo, crear en memoria una tabla de códigos vacía, sin nombre asignado, sin ruta, sin nada (bueno, en realidad si hay algo, un código que es el código al que van a parar las entidades con códigos desconocidos). Esta tabla de códigos es la que se le pasa al importador/exportador encargado de cargar el archivo de dibujo.

Ahora bien, no solo ha habido que modificar el programa principal. También ha habido que modificar los distintos importadores/exportadores para que añadan códigos a esta tabla de códigos, y a la hora de desarrollar esa modificación surgió una duda: ¿En qué casos los importadores/exportadores van a añadir códigos?.

Decidiendo cuando se va a añadir un código nuevo en la tabla de códigos

Digi3D.NET nunca ha añadido códigos a la tabla de códigos activa si se encuentra con un código desconocido, por lo tanto no queremos que si alguien actualiza el programa, este siga comportándose así, es decir, si se encuentra un código desconocido, este no se añade a la tabla de códigos y el usuario verá ese código de color gris, lo que le hará saber que es un código desconocido.

Cuadro de diálogo de configuración

Cuadro de diálogo de configuración mostrando la opción de añadir códigos desconocidos para el importador de binarios de doble precisión.

Lo primero que se ha añadido a cada uno de los importadores/exportadores es una nueva opción en el cuadro de diálogo de Configuración que le va a permitir indicar al usuario si quiere que el importador/exportador añada automáticamente códigos a la tabla de códigos activa si no localiza en esa tabla el código con el cual traducir una determinada entidad localizada en el archivo de dibujo.

Si te fijas en la captura de pantalla, la opción por defecto es No. Es así por lo indicado anteriormente: queremos mantener la compatibilidad hacia atrás y no queremos que de repente se añadan códigos automáticamente a un usuario que indica una tabla de códigos activa. Si el usuario activa esta opción, se añadirán códigos, pero tiene que ser el usuario quien implícitamente indique que quiere que el programa se comporte de ese modo.

Sin embargo, no queremos que el usuario que se quiera aprovechar de esta funcionalidad tenga que cambiar esta opción obligatoriamente, y ahí es donde entra la posibilidad de dejar vacío el campo en el que se indica la tabla de códigos en el cuadro de diálogo de Nuevo Proyecto.

Si el usuario deja vacío ese campo, aunque tenga deshabilitada la opción de añadir códigos en el cuadro de diálogo de Configuración, los importadores van a crear códigos nuevos.

Comportamiento en función del importador/exportador

No todos los importadores/exportadores se comportan igual, por eso te pongo aquí una tabla para que veas las diferencias de comportamiento en función del formato del archivo de dibujo y en función de la configuración indicada.

Escenario 1: El usuario no ha rellenado el campo Tabla de códigos en el cuadro de diálogo Nuevo Proyecto, al cargar el archivo de dibujo o ha seleccionado una tabla de códigos pero ninguno de sus códigos se corresponden con los de las entidades del archivo de dibujo y además ha seleccionado NO AÑADIR CÓDIGOS DESCONOCIDOS en la configuración del programa para el importador en cuestión. .

Importador Comportamiento
BinD Se crearán tantos códigos como códigos se localicen en las entidades del archivo de dibujo.
Bin Se crearán tantos códigos como códigos se localicen en las entidades del archivo de dibujo.
Dwg Se crearán tantos códigos como capas se localicen en las entidades del archivo de dibujo.
Dgn Se crearán tantos códigos como códigos se localicen en las entidades del archivo de dibujo. El caso de Dgn es especial, pues la codificación varía en función del parámetro riterio para códigos seleccionado por el usuario a la hora de cargar el archivo de dibujo.
Shp Se crearán tantos códigos como tablas formen el shapefile.
Mdb (Geomedia Datawarehouse) Se crearán tantos códigos como tablas con geometrías se localicen en el .mdb.

Escenario 2: El usuario ha cargado una tabla de códigos pero ninguno de sus códigos se corresponden con los de las entidades del archivo de dibujo. El usuario ha seleccionado NO AÑADIR CÓDIGOS DESCONOCIDOS en la configuración del programa para el importador en cuestión.

Importador Comportamiento
BinD Se cargarán las entidades con código desconocido.
Bin Se cargarán las entidades con código desconocido.
Dwg Se cargarán las entidades con código desconocido.
Dgn Se cargarán las entidades con código desconocido,
Shp No se cargarán las entidades de las capas para las que no se localiza correspondencia en la tabla de códigos.
Mdb (Geomedia Datawarehouse) No se cargarán las entidades de las capas para las que no se localiza correspondencia en la tabla de códigos.

Es decir, que los importadores basados en bases de datos, como Shp y Geomedia no pueden cargar entidades con código desconocido.

Conectando con bases de datos SQL Server Compact

Digi3D.NET permite que almacenemos información alfanumérica en bases de datos basadas en archivo (como Access, o SQL Server Compact) o basadas en servidor, como Microsoft SQL Server, Oracle, MySQL, PostgreSQL, …

En ocasiones nos interesará crear una base de datos por cada archivo de dibujo. En este escenario, es posible que nos interese utilizar una base de datos de archivo que se asociará con cada archivo de dibujo.

Tenemos varias posibilidades, una de ellas (la opción clásica) es asociar un archivo Access a cada archivo de dibujo. Esta solución tiene dos inconvenientes:

  • Access no es gratuito, por lo tanto tendremos que adquirir una licencia de esta aplicación para poder crear la base de datos, realizar consultas, …
  • No es compatible con aplicaciones de 64 bits, por lo tanto queda descartado para Digi3D.NET si tenemos instalada la versión de 64 bits

Pero existen otras soluciones. Hoy vamos ha hablar de Microsoft SQL Server Compact.

Un archivo de base de datos Microsoft SQL Server Compact tiene la extensión .sdf.

Este tipo de bases de datos tienen muchas ventajas si las comparamos con Access, entre las que podríamos destacar (aunque hay muchas más):

  • Es compatible tanto con aplicaciones de 64 bits como de 32 bits.
  • Es gratuita.
  • Es espacial (permite almacenar geometrías y consultas espaciales).
  • Permite crear tablas de hasta 4 GB de información (frente a 4 de Access).
  • Permite almacenar cadenas de caracteres de hasta 4000 caracteres (frente a 255 de Access).

Si queremos utilizar una base de datos Microsoft SQL Server Compact tan solo tenemos que instalar un pequeño motor de base de datos en nuestro ordenador. A día de hoy existen dos versiones de este motor de bases de datos:

Si tienes instalado Digi3D.NET en tu equipo, ya tienes instalado Microsoft SQL Server Compact 3.5 SP2, pues es uno de los requisitos necesarios para instalar la aplicación.

Para crear estas bases de datos y realizar consultas SQL sobre ellas disponemos de varias herramientas en función de la versión:

Microsoft SQL Server Compact 3.5 SP2 Microsoft SQL Server Compact 4.0 SP1
SQL Server 2008 R2 Management Studio (Express) (3.5 SP2) Visual Studio 2010 (3.5 SP2)
Visual Studio 2010 (3.5 SP2)
Visual Studio 2010 SP1 + 4.0 Tools (3.5 SP2 + 4.0)
Visual Studio Express 2012 for Web
WebMatrix 2

Si te fijas hay muchas más opciones para la versión 4, pero a mi personalmente me encanta Microsoft SQL Server Management Studio para trabajar con bases de datos. Ni te molestes en intentar conectar con una base de datos Microsoft SQL Server Compact 4.0 con ninguna de las versiones Microsoft SQL Server Management Studio, ni siquiera con la versión 2012. Microsoft decidió que nunca jamás se podrán administrar estas bases de datos con este programa, y en parte tienen razón. Este programa está pensado para servidores de bases de datos completos, con Transact SQL completo, con procedimientos almacenados, con replicación, informes, y un sinfín de cosas que no admiten las bases de datos compact que son algo muy sencillo pensado inicialmente para aplicaciones de telefonía móvil.

NuevoProyectoMostrandoBotonSeleccionDataLinkProperties

Una vez que hemos creado una base de datos SQL Server Compact con cualquiera de estas herramientas (en el formato adecuado, lee el capítulo de Bases de datos del libro Digi3D en profundidad para aprender el esquema de una base de datos compatible con Digi3D.NET), lo único que nos queda es hacer que Digi3D.NET se conecte con nuestra base de datos.

La forma de conectar Digi3D.NET con una base de datos consiste en indicar en la pestaña Archivo de dibujo del cuadro de diálogo Nuevo Proyecto el tipo de Modelo de Datos (a día de hoy podemos seleccionar o CATDBS o Geographics) y en el siguiente campo, especificar la cadena de conexión a la base de datos.

La cadena de conexión es una línea de texto en un formato especial que indica por un lado el proveedor de base de datos (el motor de base de datos, runtime, o como lo queramos llamar), la ruta a la base de datos y opcionalmente una serie de parámetros.

Un ejemplo de cadena de conexión podría ser el siguiente:

Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=PruebaCatDBS;Data Source=.sqlexpress

Si te fijas no es trivial crear estas cadenas de conexión, por eso, existe un cuadro de diálogo común de Windows que nos va a facilitar la tarea de crear cadenas de conexiones. En Digi3D.NET podemos hacer que aparezca este cuadro de diálogo pulsando el botón de los tres puntos que aparece en la ventana en la que se nos solicita la cadena de conexión.

CuadroDialogoDataLinkProperties

Este cuadro de diálogo nos va a permitir seleccionar entre una lista de proveedores de bases de datos instalados en nuestro equipo. Esos proveedores pueden variar en función de la versión de Digi3D.NET, ya que en el caso de ejecutar la versión de 64 bits, no aparecerá el proveedor Microsoft Jet 4.0 OLE DB Provider, que es el utilizado por las bases de datos de Access, lo que significa que con las versiones de 64 bits no podríamos conectarnos con bases de datos de Access.

El único inconveniente es que no vamos a localizar aquí ningún proveedor para Microsoft SQL Server Compact 3.5 SP2 o Microsoft SQL Server Compact 4.0 SP1, de modo que no nos va a quedar más remedio que introducir manualmente la cadena de conexión, y ese es precisamente el objetivo de esta entrada en el blog.

El formato de las cadenas de conexión para estos dos proveedores es la siguiente:

Tipo de conexión Cadena de conexión
SQL Server Compact 3.5 SP2 sin contraseña Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=rutaAlArchivonombreDelArchivo.sdf;
SQL Server Compact 3.5 SP2 con contraseña Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=rutaAlArchivonombreDelArchivo.sdf;SSCE:Database Password=’contraseña’;
SQL Server Compact 4.0 SP1 sin contraseña Provider=Microsoft.SQLSERVER.CE.OLEDB.4.0;Data Source=rutaAlArchivonombreDelArchivo.sdf;
SQL Server Compact 4.0 SP1 con contraseña Provider=Microsoft.SQLSERVER.CE.OLEDB.4.0;Data Source=rutaAlArchivonombreDelArchivo.sdf;SSCE:Database Password=’contraseña’;

De modo que si por ejemplo nuestro archivo de dibujo está almacenado en la ruta C:Trabajos20131h1.bind podríamos tener una base de datos asociada en la ruta C:Trabajos20131h1.sdf mediante la cadena de conexión (suponiendo que la versión de la base de datos es la 3.5): Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=C:Trabajos20131h1.sdf;

Esto tiene el inconveniente de que cada vez que cambiemos de archivo de dibujo, tendremos que cambiar la cadena de conexión para que la conexión sea con la base de datos correspondiente.

Si queremos evitar tener esto, podemos aprovecharnos de los sustituidores de Digi3D.NET para hacer que el programa seleccione automáticamente el nombre de la base de datos en función del nombre del archivo de dibujo mediante la siguiente cadena de conexión: Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=$(RutaArchivoDibujo)$(NombreArchivoDibujoSinExtension).sdf;

Fragments Shaders en Digi3D.NET

Digi3D.NET permite transformar los parámetros radiomátricos (brillo/color/contraste/gama/niveles/tonos de gris/positivo o negativo…) de visualización de las imágenes que muestra en la ventana fotogramétrica mediante los controles del panel Propiedades de la imagen de la ventana fotogramétrica.

Podemos configurar el programa para que esta transformación se realice o por software o por hardware, en la opción del menú Herramientas/Configuración/Estereoscopía/Método para Brillo y Contraste.

Disponemos de dos opciones:

  • Software
  • Fragment Shaders

Si instalamos Digi3D.NET por primera vez en un equipo la opción predeterminada será Fragment Shaders que es la implementación por hardware.

La opción ideal es hacer la transformación por hardware, pero no todas las tarjetas gráficas están preparadas para realizar operaciones por hardware. Las tarjetas gráficas que admiten esta operación son tarjetas que admiten el lenguaje de programación de Shaders.

Si nuestra tarjeta gráfica no admite shaders, si tenemos seleccionada la opción Fragment Shaders comprobaremos que al desplazarnos por la imagen, esta dará tirones, en cuyo caso tendremos que seleccionar la opción Software.

Mi recomendación es que si tu tarjeta gráfica lo admite, selecciones la opción Fragment Shaders, por varios motivos:

  • Velocidad: ya que si la operación se realiza por software, cada vez que Digi3D.NET cargue una tesela de la imagen, tendrá que modificar pixel por pixel su valor en función de la configuración de brillo/contraste/color… seleccionada por el usuario.

    Además, me atrevería a asegurar que el 99% de los equipos cuya tarjeta gráfica no admite shaders son antiguos y lentos, lo que provocará que la carga de la tesela se relentice, provocando que el usuario visualice por uno o varios segundos esta tesela de color negro.

  • Carga del procesador: Si el procesador está ocupado cambiando el color de la imagen, el scroll de la imagen se puede ralentizar.

    Y tal y como expongo en el punto anterior, como el procesador seguro que tiene pocos núcleos (dos, no más) esto afectará al rendimiento de Digi3D.NET.

  • Modificación real de la imagen en memoria: Si nos disponemos a realizar alguna operación con la imagen cargada en memoria, como por ejemplo correlar un modelo, y esta ha sido alterada por software, los resultados variarán en función del los parámetros radiométricos de la imagen.

Digi3D.NET programa los shaders de radiometría mediante el lenguaje de programación GLSL que son las siglas de OpenGL Shading Language, y este lenguaje se programación se caracteriza porque los programas no se pueden almacenar compilados, sino que los compila la tarjeta gráfica (el Driver de OpenGL más bien) en el mismo instante en el que cargamos el programa en la tarjeta gráfica.

Puedes localizar el código fuente del Fragment Shader que gestiona los parámetros radiométricos de visualización de Digi3D.NET en la ruta:

Sistema operativo Versión de Digi3D.NET Ruta
32 bits 32 bits %ProgramFiles%Digi21.netDigi3D2011FragmentShader.glsl
64 bits 64 bits %ProgramFiles%Digi21.netDigi3D2011FragmentShader.glsl
64 bits 32 bits %ProgramFiles (x86)%Digi21.netDigi3D2011FragmentShader.glsl

Este archivo es un archivo de texto que puedes editar con cualquier editor como el bloc de notas. Está programado en el lenguaje CLSL que es muy parecido al lenguaje de programación C.

Lo que más te va a sorprender si no sabes de programación de shaders es que el programa se ejecuta una vez por cada pixel de pantalla, si has leido bien, si nuestro monitor tiene 1920×1080 píxeles, cada vez que movamos la ventana de Digi3D.NET un único píxel, se ejecutará este programa 2073600 veces!!!!! y en tiempo real, seguro que si nuestra tarjeta no es muy lenta podremos llegar a los 140 frames por segundo sin ningún problema.

No es mi intención enseñarte a programar shaders, lo que si que puedo es recomendarte un libro, como es OpenGL Shading Language, pero si te puedo explicar un poquito el contenido del código y cómo modificarlo para hacer una broma o para investigar.

El código del shader es el siguiente:

// Fragment Shader para las transformaciones de color en Digi3D 2011-2012
// Desarrollado por José Ángel Martínez Torres - Dreaming With Objects S.L.
// www.digi21.net/digi3d
//

// Indica realizar una transformación por tabla de color. Si es verdadero no se ajustarán ni brillo, 
// ni contraste ni gamma, sino que se transformará el color de entrada
// en un color de salida.
// Lo activa por ejemplo la casilla de Niveles Automáticos. Si el usuario activa esta casilla, se 
// calcula una tabla de transformación de colores mediante el algoritmo
// indicado en http://en.wikipedia.org/wiki/Histogram_equalization
uniform bool aplicarTablaColor;
uniform vec3 tablaColor[256];

// En la variable brillo se almacena el desplazamiento a realizar en el brillo. Se admiten valores entre [-1,+1]
uniform float brillo;

// En la variable brillo se almacena el desplazamiento de contraste a aplicar. Se admiten valores entre [-1,+1]
uniform float contraste;

// La variable transformarTonoGris controla si se debe transformar la imagen a tonos de gris.
uniform bool transformarTonosGris;

// La variable negativo controla si se debe mostrar la imagen en negativo.
uniform bool negativo;

// El valor escalaColor indica el factor (que será potencia de 2) por el cual se debe escalar el color.
uniform float escalaColor;

// Indica si el usuario ha desplazado el control de Gamma.
// Documentación de Gamma en http://www.ivl.disco.unimib.it/Teaching/AIC-2010-specialistica/proof_paper_JEI.pdf
uniform float gamma;


// Fórmula del brillo contraste de The Gimp tal y como aparece en el artículo de la wikipedia
// http://en.wikipedia.org/wiki/Image_editing#Contrast_change_and_brightening
// Modificada para no calcular la tangente por cada componente
float CalculaValorBrilloContrasteRapido(float value, float brightness, float tangentePiCeroCinco)
{
    if (brightness < 0.0)
        value = value * ( 1.0 + brightness);
    else
        value = value + ((1.0 - value) * brightness);

    value = (value - 0.5) * tangentePiCeroCinco + 0.5;
    return value;
}

void main(void)
{
    vec4 colorTransformado = gl_Color;

    if( aplicarTablaColor ) {
        colorTransformado.r = tablaColor[int(gl_Color.r*255.0)].r;
        colorTransformado.g = tablaColor[int(gl_Color.g*255.0)].g;
        colorTransformado.b = tablaColor[int(gl_Color.b*255.0)].b;
    } else {
        if( brillo != 0.0 || contraste != 0.0) {
            float tangentePiCeroCinco = tan((contraste + 1.0) * 3.14159265358979323/4.0);
            colorTransformado.r = CalculaValorBrilloContrasteRapido(gl_Color.r, brillo, tangentePiCeroCinco);
            colorTransformado.g = CalculaValorBrilloContrasteRapido(gl_Color.g, brillo, tangentePiCeroCinco);
            colorTransformado.b = CalculaValorBrilloContrasteRapido(gl_Color.b, brillo, tangentePiCeroCinco);
        }

        if( 1.0 != escalaColor )
            colorTransformado *= vec4(escalaColor, escalaColor, escalaColor, 1.0);

        if( gamma != 1.0 )
            colorTransformado = pow(colorTransformado, vec4(1.0/gamma, 1.0/gamma, 1.0/gamma, 1.0));
    }

    if( negativo )
        colorTransformado = vec4(1.0, 1.0, 1.0, 0.0) - colorTransformado;

    if( transformarTonosGris ) {
        float iluminacion = colorTransformado.r * 0.299 + colorTransformado.g * 0.587 + colorTransformado.b * 0.114;
        colorTransformado = vec4(iluminacion, iluminacion, iluminacion, 1.0);
    }

    gl_FragColor = colorTransformado;
}

Como puedes comprobar, al principio se declaran una serie de variables (aplicarTablaColor, tablaColor, brillo, …) que son las que se modifican cuando el usuario interactua con los controles, y dentro de la función main tienes el código que realiza las correspondientes transformaciones.

Te recuerdo que este programa se ejecuta una vez por cada pixel a mostrar, y el objetivo de cualquier Fragment Shader es el de asignar la variable global gl_FragColor que es un tipo con cuatro componentes (rojo, verde, azul, alfa). El color original lo tenemos almacenado en la viable global gl_Color.

Vamos a modificar el Fragment Shader para que no se muestren colores ni verde ni azul, únicamente rojo, de esta manera conseguiremos ver el modelo de Bronchales (que es el que utilizo para la captura de pantalla del comienzo de la entrada) de color rojo. Para ello, voy a obtener el valor de iluminación (que como puedes ver en el código fuente original se obtiene multiplicando la componente roja del pixel por 0,299, la componente verde por 0,587 y la componente azul por 0,114, y vamos a indicar que este valor será el color a utilizar en la componente roja del color de salida.

A continuación el código fuente de nuestro Fragment Shader modificado…

void main(void)
{
    float iluminacion = gl_Color.r * 0.299 + gl_Color.g * 0.587 + gl_Color.b * 0.114;
    gl_FragColor = vec4(iluminacion, 0.0, 0.0, 1.0);
}

Digi3D 2011 ahora es Digi3D.NET

Debido al enfoque orientado al alquiler que estamos dando a nuestros productos, no tiene sentido el poner un número de versión al nombre de la aplicación, ya que los usuarios que alquilen un producto, tendrán siempre acceso a la versión con las últimas novedades. Por ello, y en el caso de Digi3D debido a la gran vinculación que tiene con .NET al ser completamente programable, hemos decidido cambiar el nombre de la aplicación que a partir de ahora pasa a llamarse Digi3D.NET. El módulo de CAD de la aplicación pasa a llamarse DigiNG.NET

Imágenes TIFF comprimidas con JPEG de 12 bits

La semana pasada estuvimos en una empresa en Portugal que trabajan con imágenes TIFF comprimidas con compresión JPEG y con una profundidad de color de 12 bits (el estándar JPEG permite compresiones de 8 y de 12 bits).

Digi3D 2011 utiliza para la lectura de archivos JPEG la librería LibJPEG, y para la lectura de archivos TIFF la librería LibTIFF. La librería LibTIFF se basa en la LibJPEG si se encuentra con un archivo TIFF comprimido con compresión JPEG.

La librería LibJPEG se puede compilar para trabajar con profundidades de color de 8 y de 12 bits, para seguir el estándar de JPEG, pero tiene un pequeño problema: no se puede compilar para 8 y 12 bits simultáneamente. Esto quiere decir que o tenemos una librería LibJPEG (y por lo tanto una LibTIFF) que puede descomprimir archivos JPEG de 8 bits o de 12, pero nunca simultáneamente. Esto hace que sea muy complicado desarrollar una aplicación que soporte simultáneamente JPEG de 8 y de 12 bits.

Por esta razón, Digi3D no puede cargar imágenes TIFF con JPEG de 12 bits. Si intentamos cargar una imagen TIFF comprimida en JPEG de 12 bits, Digi3D mostrará un cuadro de diálogo indicando que no se puede cargar la imagen porque no se soportan imágenes TIFF con compresión JPEG de 12 bits.