20.01.2015 Views

PROYECTO FIN DE CARRERA Esquemas multirresolución para ...

PROYECTO FIN DE CARRERA Esquemas multirresolución para ...

PROYECTO FIN DE CARRERA Esquemas multirresolución para ...

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

UNIVERSIDAD <strong>DE</strong> VALLADOLID<br />

ETSI TELECOMUNICACIÓN<br />

<strong>PROYECTO</strong> <strong>FIN</strong> <strong>DE</strong> <strong>CARRERA</strong><br />

<strong>Esquemas</strong> multirresolución <strong>para</strong> compresión<br />

de datos volumétricos<br />

AUTOR:<br />

TUTOR:<br />

Miguel Ángel Martín Fernández<br />

Carlos Alberola López<br />

Marzo 1999


RESUMEN<br />

En este proyecto se implementan y com<strong>para</strong>n dos esquemas de compresión multirresolución<br />

de datos volumétricos, compresión mediante transformada wavelet y<br />

compresión mediante diezmado de mallas triangulares. En la compresión wavelet se<br />

puede obtener una zona de los datos volumétricos con resolución total.<br />

Además se realiza una interfaz gráfica de usuario, <strong>para</strong> realizar la compresión, evaluar<br />

su error y visualizar las renderizaciones resultantes, ofreciendo al usuario la<br />

posibilidad de interactuar con la escena renderizada.<br />

ABSTRACT<br />

In this project, two schemes of volume data multiresolution compression, are developed<br />

and compared, wavelet transform compression, and triangular meshes decimation.<br />

In wavelet compression, it is possible to obtain a zone of volume data with full<br />

resolution.<br />

It is also developed a graphic user intergace to compress data, evaluate error, and<br />

visualizate resulting renderings, offering the user, the possibility of interacting with<br />

the rendered scene.<br />

PALABRAS CLAVE<br />

Compresión multirresolución, datos volumétricos, transformada wavelet, diezmado<br />

de mallas triangulares, error cuadrático medio en volumen, error cuadrático medio<br />

en imagen renderizada, número medio de errores por rodaja, visualización, renderización,<br />

tomografía computerizada, resonancia magnética, ultrasonidos, marching<br />

cubes, isosuperficie, flood filling, VTK, Tcl/Tk, interfaz gráfica de usuario (GUI).<br />

i


Índice general<br />

1. Introducción 1<br />

2. Captación de Imágenes Médicas 5<br />

2.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

2.2. Tomografía Computerizada . . . . . . . . . . . . . . . . . . . . . . . . 6<br />

2.2.1. Instrumentación . . . . . . . . . . . . . . . . . . . . . . . . . . 6<br />

2.2.2. Principios de Reconstrucción: Proyección de los Datos a la<br />

Imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13<br />

2.3. Resonancia Magnética . . . . . . . . . . . . . . . . . . . . . . . . . . 15<br />

2.3.1. Adquisición y Procesado . . . . . . . . . . . . . . . . . . . . . 15<br />

2.3.2. Hardware e Instrumentación . . . . . . . . . . . . . . . . . . . 20<br />

2.4. Ultrasonido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />

2.4.1. Transductores . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />

2.4.2. Obtención de Imágenes por Ultrasonidos . . . . . . . . . . . . 29<br />

3. Visualización y Renderización 35<br />

3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35<br />

3.2. Procesado de Imagen, Gráficos, y Visualización . . . . . . . . . . . . 37<br />

3.3. Renderización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38<br />

3.3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38<br />

3.3.2. Fundamentos . . . . . . . . . . . . . . . . . . . . . . . . . . . 39<br />

3.3.3. Color . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42<br />

3.3.4. Luces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43<br />

3.3.5. Propiedades de la Superficie . . . . . . . . . . . . . . . . . . . 44<br />

3.3.6. Cámaras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46<br />

iii


3.3.7. Sistemas de Coordenadas . . . . . . . . . . . . . . . . . . . . . 48<br />

3.3.8. Transformación de Coordenadas . . . . . . . . . . . . . . . . . 49<br />

3.3.9. Geometría de los Actores . . . . . . . . . . . . . . . . . . . . . 51<br />

3.3.10. Hardware Gráfico . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />

3.4. Obtención de Isosuperficies: Marching Cubes . . . . . . . . . . . . . . 57<br />

3.4.1. Descripción del Algoritmo . . . . . . . . . . . . . . . . . . . . 58<br />

3.4.2. Problema del Algoritmo: Ambigüedad . . . . . . . . . . . . . . 59<br />

4. Compresión de Datos 63<br />

4.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63<br />

4.1.1. Terminología General . . . . . . . . . . . . . . . . . . . . . . . 63<br />

4.2. Compresión de Audio y Voz . . . . . . . . . . . . . . . . . . . . . . . 64<br />

4.2.1. Codificadores de Forma de Onda . . . . . . . . . . . . . . . . 65<br />

4.2.2. Algunos Codificadores Específicos de Voz . . . . . . . . . . . . 68<br />

4.3. Compresión de Imágenes y Datos Volumétricos . . . . . . . . . . . . . 71<br />

4.3.1. Criterios de Diséno . . . . . . . . . . . . . . . . . . . . . . . . 71<br />

4.3.2. Métodos de Compresión . . . . . . . . . . . . . . . . . . . . . 74<br />

4.3.3. Método 1: Transformación . . . . . . . . . . . . . . . . . . . . 74<br />

4.3.4. Método 2: Reducción de la Precisión . . . . . . . . . . . . . . 83<br />

4.3.5. Método 3: Minimización del Número de Bits . . . . . . . . . . 84<br />

4.3.6. Combinación de Métodos de Compresión . . . . . . . . . . . . 88<br />

4.3.7. Diezmado de Superficies . . . . . . . . . . . . . . . . . . . . . 89<br />

5. Transformada Wavelet 97<br />

5.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97<br />

5.2. Expresión Multirresolución y Wavelets Ortogonales . . . . . . . . . . 99<br />

5.3. Transformada Wavelet 3D . . . . . . . . . . . . . . . . . . . . . . . . 106<br />

5.4. Transformada Wavelet <strong>para</strong> Compresión 3D . . . . . . . . . . . . . . 109<br />

6. Descripción de la Aplicación 111<br />

6.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111<br />

6.2. Tareas a Realizar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112<br />

6.3. Interfaz Gráfica de Usuario (GUI) . . . . . . . . . . . . . . . . . . . . 113<br />

6.3.1. Sistema de Menús . . . . . . . . . . . . . . . . . . . . . . . . . 113<br />

iv


6.3.2. Ventana de Renderización . . . . . . . . . . . . . . . . . . . . 117<br />

6.3.3. Barra de Estado . . . . . . . . . . . . . . . . . . . . . . . . . . 118<br />

6.3.4. Ventanas de Error y de Información . . . . . . . . . . . . . . . 119<br />

6.4. Unas notas sobre la implementación . . . . . . . . . . . . . . . . . . . 121<br />

6.5. Ejemplos de Utilización del Programa . . . . . . . . . . . . . . . . . . 122<br />

6.5.1. Diezmado de un Cráneo . . . . . . . . . . . . . . . . . . . . . 122<br />

6.5.2. Compresión Wavelet de una Cabeza y Obtención de Detalle . 129<br />

7. Resultados 143<br />

7.1. Medidas de Error Empleadas . . . . . . . . . . . . . . . . . . . . . . . 143<br />

7.2. Cálculos Realizados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145<br />

7.3. Resultados obtenidos . . . . . . . . . . . . . . . . . . . . . . . . . . . 146<br />

7.4. Interpretación de los resultados obtenidos . . . . . . . . . . . . . . . . 159<br />

7.4.1. Compresión mediante la Transformada Wavelet . . . . . . . . 159<br />

7.4.2. Compresión mediante Diezmado . . . . . . . . . . . . . . . . . 162<br />

7.4.3. Com<strong>para</strong>ción entre Ambos Métodos de Compresión . . . . . . 163<br />

7.5. Algunas Imágenes Renderizadas de Ejemplo . . . . . . . . . . . . . . 163<br />

8. Conclusiones y Líneas Futuras 171<br />

8.1. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171<br />

8.2. Líneas Futuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173<br />

9. Pliego de Condiciones 177<br />

A. Manual de Referencia 181<br />

A.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181<br />

A.2. Estructura general del Programa . . . . . . . . . . . . . . . . . . . . 181<br />

A.3. vtkWaveletFilter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190<br />

A.3.1. Función vtkWaveletFilter::Execute() . . . . . . . . . . . . 194<br />

A.3.2. Función vtkWaveletFilter::CalcularDetalle(...) . . . . 197<br />

A.3.3. Función vtkWaveletFilter::Significancia(...) . . . . . . 199<br />

A.4. vtkFloodFillFilter . . . . . . . . . . . . . . . . . . . . . . . . . . . 201<br />

A.5. ErrorVol.cxx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208<br />

A.6. ErrorRender.cxx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209<br />

v


B. VTK y TCL 211<br />

B.1. VTK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211<br />

B.1.1. Renderizacion . . . . . . . . . . . . . . . . . . . . . . . . . . . 212<br />

B.1.2. Detalles de la implementación de VTK . . . . . . . . . . . . . 216<br />

B.1.3. Representación de los Datos . . . . . . . . . . . . . . . . . . . 218<br />

B.2. Intérpretes y Tcl/Tk . . . . . . . . . . . . . . . . . . . . . . . . . . . 222<br />

B.2.1. Lenguajes Interpretados vs Compilados . . . . . . . . . . . . . 222<br />

B.2.2. Introducción a Tcl . . . . . . . . . . . . . . . . . . . . . . . . 223<br />

B.2.3. Integración de VTK con Tcl . . . . . . . . . . . . . . . . . . . 225<br />

B.2.4. Ejemplo de C++ y Tcl . . . . . . . . . . . . . . . . . . . . . . 227<br />

B.2.5. Interfaces de Usuario con Tk . . . . . . . . . . . . . . . . . . . 228<br />

C. Planos 231<br />

vi


Índice de figuras<br />

2.1. Dibujo esquemático de una instalación de TC. Consta de (1) consola<br />

de control, (2) soporte de la grúa, (3) mesa del paciente (4) soporte<br />

<strong>para</strong> la cabeza (5) impresor de imagen láser. . . . . . . . . . . . . . . 6<br />

2.2. Imágenes típicas de TC de (a) cerebro, (b) cabeza con las órbitas, (c)<br />

pecho con los pulmones y (d) abdomen. . . . . . . . . . . . . . . . . . 7<br />

2.3. Cuatro generaciones de escaners <strong>para</strong> TC, mostrando las geometrías<br />

de haz <strong>para</strong>lelo y en abanico. . . . . . . . . . . . . . . . . . . . . . . . 8<br />

2.4. Esquema de un escáner ultrarrápido de quinta generación. La imagen<br />

se adquiere en 50 ms por el barrido electrónico del ánodo. . . . . . . . 10<br />

2.5. El escaneado en espiral provoca que el punto focal siga una trayectoria<br />

espiral alrededor del paciente. . . . . . . . . . . . . . . . . . . . . . . 11<br />

2.6. El sistema de adquisición de datos convierte la señal eléctrica producida<br />

en cada detector a un valor digital en el ordenador. . . . . . . 13<br />

2.7. El sistema informático controla los movimientos de la grúa, adquiere<br />

la medida de las transmisiones de rayos-x, y reconstruye la imagen<br />

final. El sistema mostrado aquí usa 12 CPUs de la familia 68000<br />

(Cortesía de Picker International, Inc.). . . . . . . . . . . . . . . . . . 14<br />

2.8. Los dos mecanismos principales de contraste en resonancias magnéticas,<br />

T 1 y T 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19<br />

2.9. Ejemplos de imágenes de un voluntario normal mostrando el contraste<br />

T 1 a la izquierda y el T 2 a la derecha. . . . . . . . . . . . . . . . . . . 20<br />

2.10. Dominios digital y analógico en RM. Las RM requieren el intercambio<br />

de datos y comandos entre estos dos dominios. . . . . . . . . . . . . . 21<br />

2.11. Enfoque y direccionamiento de un haz acústico usando un array de<br />

fase. Se muestra un array de 6 elementos (a) en el modo de transmisión<br />

y (b) en el modo de recepción. . . . . . . . . . . . . . . . . . . . . . . 27<br />

2.12. Configuraciones de los elementos del array y regiones escaneadas por<br />

el haz acústico. (a)Array lineal secuencial; (b) array curvilíneo; (c)<br />

array lineal de fase; (d) array 1.5D; (e) Array de fase 2D. . . . . . . . 30<br />

vii


2.13. Ejemplo de imagen de modo A convertido a modo M de un corazón<br />

en dos puntos del ciclo cardiaco. (a) Diástole. (b) Sístole. En la sístole<br />

las paredes son más anchas y la sección ventricular es menor. . . . . . 31<br />

2.14. Representación esquemática de un corazón y como se obtiene la imagen<br />

2D a partir de la exploración del transductor. . . . . . . . . . . 32<br />

3.1. El proceso de visualización. Los datos de varias fuentes se transforman<br />

repetidamente <strong>para</strong> obtener, derivar y resaltar la información. Los<br />

datos resultantes se mapean al sistema gráfico. . . . . . . . . . . . . . 39<br />

3.2. Representación circular del matiz. . . . . . . . . . . . . . . . . . . . . 43<br />

3.3. Iluminación difusa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45<br />

3.4. Iluminación especular. . . . . . . . . . . . . . . . . . . . . . . . . . . 46<br />

3.5. Bola iluminada con luz difusa, con reflexión especular en aumento. . . 46<br />

3.6. Atributos de la cámara. . . . . . . . . . . . . . . . . . . . . . . . . . 47<br />

3.7. Jerarquía típica del interfaz de gráficos. . . . . . . . . . . . . . . . . . 54<br />

3.8. Normales de vértices y polígonos. . . . . . . . . . . . . . . . . . . . . 55<br />

3.9. Problema con el algoritmo del pintor. . . . . . . . . . . . . . . . . . . 57<br />

3.10. Los 15 cubos triangulados del algoritmo Marching Cubes. . . . . . . . 60<br />

3.11. Casos complementarios <strong>para</strong> el algoritmo Marching Cubes. . . . . . . 61<br />

4.1. Esquema del sistema DPCM (PCM Diferencial). . . . . . . . . . . . . 66<br />

4.2. <strong>Esquemas</strong> generales <strong>para</strong> los tres métodos principales de compresión. 75<br />

4.3. Ejemplo de codificación run-length. . . . . . . . . . . . . . . . . . . . 76<br />

4.4. Ejemplo de asignación de códigos únicos a secuencias de datos repetidas.<br />

Codificación LZW. . . . . . . . . . . . . . . . . . . . . . . . . . . 77<br />

4.5. Ejemplo de codificación Huffman. . . . . . . . . . . . . . . . . . . . . 86<br />

4.6. Combinaciones típicas de métodos de compresión. . . . . . . . . . . . 88<br />

4.7. Los tres pasos del algoritmo de diezmado. . . . . . . . . . . . . . . . 90<br />

4.8. Operadores <strong>para</strong> crear mallas progresivas: fusión/división de bordes<br />

y división/fusión de vértices. . . . . . . . . . . . . . . . . . . . . . . . 94<br />

5.1. (a) Ejemplo de función de escalado φ(x), (b) Módulo de la transformada<br />

de Fourier ˆφ(x). Las funciones de escalado son filtros paso<br />

bajo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103<br />

5.2. (a) Ejemplo de función wavelet ψ(x), (b) Módulo de la transformada<br />

de Fourier ˆψ(x). Las funciones wavelet son filtros paso banda. . . . . 104<br />

viii


5.3. La aproximación discreta A d 2 j+1 f se descompone en A d 2 j f y D d 2 j f. . . . 105<br />

5.4. Reconstrucción de la aproximación discreta A d 2 j+1 f a partir de A d 2 j f<br />

y D d 2 j f. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />

5.5. Ejemplo de una transformada wavelet y su reconstrucción. . . . . . . 106<br />

5.6. Una aproximación discreta, A d 2 j+1 f, se descompone en la aproximación<br />

discreta a menor resolución, A d 2 j f, y siete detalles, desde D 1 2 j f hasta<br />

D 7 2 j f. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108<br />

5.7. Expresión multirresolución de unos datos volumétricos de tamaño<br />

128 × 128 × 128. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108<br />

6.1. GUI general del programa en el que se puede ver una renderización. . 114<br />

6.2. Ejemplo de panel de control. Panel de control con las opciones de<br />

renderización. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116<br />

6.3. Panel de control <strong>para</strong> abrir los archivos con los datos volumétricos. . 117<br />

6.4. Ejemplo de ventana de error, con información del error. . . . . . . . . 120<br />

6.5. Ejemplo de ventana de información. . . . . . . . . . . . . . . . . . . . 120<br />

6.6. Renderización de la isosuperficie correspondiente al hueso, sin comprimir<br />

(archivo hueso.ppm). . . . . . . . . . . . . . . . . . . . . . . . 130<br />

6.7. Renderización de la isosuperficie diezmada un 75 % (archivo hueso75.ppm).131<br />

6.8. Diferencia entre la renderización de la isosuperficie sin diezmado y<br />

diezmada un 75 % (archivo dif75.ppm). . . . . . . . . . . . . . . . . 132<br />

6.9. Renderización de la isosuperficie correspondiente a la piel, sin comprimir<br />

(archivo piel.ppm). . . . . . . . . . . . . . . . . . . . . . . . . 135<br />

6.10. Renderización de la isosuperficie comprimida mediante la transformada<br />

wavelet 100:1 (archivo piel100.ppm). . . . . . . . . . . . . . . . . 136<br />

6.11. Diferencia entre la renderización de la isosuperficie sin compresión<br />

wavelet y con una compresión 100:1 (archivo dif100.ppm). . . . . . . 137<br />

6.12. Obtención del detalle. Mediante el prisma blanco se selecciona la zona<br />

que se desea ver con detalle. . . . . . . . . . . . . . . . . . . . . . . . 139<br />

6.13. Detalle de la oreja izquierda (derecha desde nuestra posición). . . . . 141<br />

7.1. Compresión mediante transformada wavelet. Variación en el error<br />

cuadrático medio en el volumen reconstruido, en función del número<br />

de coeficientes de la transformada wavelet. Escala lineal <strong>para</strong> ambos<br />

ejes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147<br />

ix


7.2. Compresión mediante transformada wavelet. Variación en el error<br />

cuadrático medio en el volumen reconstruido, en función del número<br />

de coeficientes de la transformada wavelet. Escala logarítmica <strong>para</strong> el<br />

eje de ordenadas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153<br />

7.3. Compresión mediante transformada wavelet. Variación en el error<br />

cuadrático medio de la imagen renderizada, en función del número<br />

de coeficientes de la transformada wavelet. Escala logarítmica <strong>para</strong> el<br />

eje de ordenadas. La curva roja corresponde a la isosuperficie de la<br />

piel (1200) y la negra a la del hueso (600). . . . . . . . . . . . . . . . 154<br />

7.4. Compresión mediante transformada wavelet. Variación en el número<br />

medio de errores por rodaja, en función del número de coeficientes de<br />

la transformada wavelet. Escala logarítmica <strong>para</strong> el eje de ordenadas.<br />

La curva roja corresponde a la isosuperficie de la piel (1200) y la negra<br />

a la del hueso (600). . . . . . . . . . . . . . . . . . . . . . . . . . . . 155<br />

7.5. Compresión mediante diezmado. Variación en el error cuadrático medio<br />

de la imagen renderizada, en función del tanto por uno de puntos considerados<br />

el diezmado. Escala logarítmica <strong>para</strong> el eje de ordenadas. La<br />

curva roja corresponde a la isosuperficie de la piel (1200) y la negra<br />

a la del hueso (600). . . . . . . . . . . . . . . . . . . . . . . . . . . . 156<br />

7.6. Compresión mediante diezmado. Variación en el número medio de errores<br />

por rodaja, en función del tanto por uno de puntos considerados<br />

el diezmado. Escala logarítmica <strong>para</strong> el eje de ordenadas. La curva<br />

roja corresponde a la isosuperficie de la piel (1200) y la negra a la del<br />

hueso (600). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157<br />

7.7. Com<strong>para</strong>ción de la compresión mediante la transformada wavelet<br />

(rojo) y la compresión mediante diezmado (negro). Variación en el<br />

número medio de errores por rodaja, en función del tanto por uno de<br />

elementos considerados <strong>para</strong> realizar la compresión. Escala logarítmica<br />

<strong>para</strong> ambos ejes. Isosuperficie correspondiente a la piel (600). . . . 158<br />

7.8. Ejemplos de renderizaciones. Compresión mediante transformada wavelet.165<br />

7.9. Ejemplos de renderizaciones. Compresión mediante transformada wavelet.166<br />

7.10. Ejemplos de renderizaciones. Compresión mediante diezmado. . . . . 167<br />

7.11. Ejemplos de renderizaciones. Compresión mediante diezmado. . . . . 168<br />

7.12. Ejemplos de renderizaciones. Compresión mediante transformada wavelet,<br />

con dos niveles de compresión. . . . . . . . . . . . . . . . . . . . . . . 169<br />

7.13. Ejemplos de renderizaciones. Compresión mediante diezmado a una<br />

tasa de compresión de 75 %, de dos isuperficies. . . . . . . . . . . . . 170<br />

A.1. Red de visualización de la aplicación. . . . . . . . . . . . . . . . . . . 186<br />

x


A.2. Diagrama OMT de herencia de la clase vtkWaveletFilter. . . . . . . 191<br />

A.3. Mapeado de una zona de detalle en el espacio a los coeficientes de la<br />

transformada wavelet (2D). . . . . . . . . . . . . . . . . . . . . . . . 200<br />

A.4. Factor de significancia de los coeficientes de la transformada wavelet<br />

(2D). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202<br />

A.5. Algoritmo del método Flood Filling “tradicional”. . . . . . . . . . . . 205<br />

A.6. Representación del algoritmo de Flood Filling generalizado, <strong>para</strong> encontrar<br />

cavidades. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206<br />

A.7. (a) Caso patológico en el que no funciona el algoritmo Flood Filling<br />

generalizado. (b) Caso muy similar en el que sí funciona. . . . . . . . 207<br />

B.1. Consiguiendo independencia del dispositivo mediante herencia. . . . . 215<br />

xi


xii


Índice de cuadros<br />

3.1. Colores comunes en los espacios RGB y HSV. . . . . . . . . . . . . . 43<br />

4.1. Requisitos de diseño y posibles opciones . . . . . . . . . . . . . . . . 72<br />

4.2. Algunas aplicaciones que requieren compresión . . . . . . . . . . . . . 73<br />

7.1. Compresión mediante transformada wavelet. Error cuadrático medio<br />

en volumen (MSEV) y número de coeficientes wavelet, en función de<br />

la tasa de compresión. . . . . . . . . . . . . . . . . . . . . . . . . . . 148<br />

7.2. Compresión mediante transformada wavelet. Isosuperficie correspondiente<br />

al hueso (densidad = 1200). Número medio de errores por<br />

rodaja (NMER) y error cuadrático medio de la imagen renderizada<br />

(MSER). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149<br />

7.3. Compresión mediante transformada wavelet. Isosuperficie correspondiente<br />

a la piel (densidad = 600). Número medio de errores por rodaja<br />

(NMER) y error cuadrático medio de la imagen renderizada (MSER). 150<br />

7.4. Compresión mediante diezmado. Isosuperficie correspondiente al hueso<br />

(densidad = 1200). Número medio de errores por rodaja (NMER)<br />

y error cuadrático medio de la imagen renderizada (MSER). . . . . . 151<br />

7.5. Compresión mediante diezmado. Isosuperficie correspondiente a la<br />

piel (densidad = 600). Número medio de errores por rodaja (NMER)<br />

y error cuadrático medio de la imagen renderizada (MSER). . . . . . 152<br />

xiii


Capítulo 1<br />

Introducción<br />

Este proyecto fin de carrera forma parte del proyecto financiado por la Junta de<br />

Castilla y León, con referencia VA78/99, cuyo título es “Desarrollo de una Aplicación<br />

de Compresión y Visualización de Datos Volumétricos <strong>para</strong> los Hospitales de Castilla<br />

y León”.<br />

El proyecto trata de estudiar y com<strong>para</strong>r entre sí los distintos métodos existentes<br />

<strong>para</strong> la compresión y visualización de datos volumétricos. En concreto los datos que<br />

se estudian son los procedentes de aplicaciones médicas, que pueden ser capturadas<br />

por distintos métodos, como pueden ser la Resonancia Magnética (RM), Tomografía<br />

Computerizada (TC) y Ultrasonidos.<br />

Para ser tratados por un ordenador, los datos deben ser digitales, por lo que si<br />

inicialmente tienen naturaleza analógica deberán ser muestreados y cuantificados, de<br />

la misma forma que ocurre con cualquier tipo de señal. Sea cual sea la forma de ser<br />

capturados, estos datos estarán formados por múltiples secciones en dos dimensiones,<br />

que unidas forman la ”imagen”tridimensional.<br />

Si las imágenes bidimensionales tienen como uno de sus mayores problemas el de<br />

su gran tamaño, este problema se ve incrementado en el caso de los datos volumétricos.<br />

Por ello nos planteamos reducirlo mediante algun método de compresión o<br />

simplificación. Este problema es aún más importante si cabe en el caso de que los<br />

datos tengan que ser transmitidos entre los ordenadores de una red, especialmente<br />

si ésta es de área amplia (WAN), como es el caso de una red que una varios centros<br />

hospitalarios.<br />

Los esquemas de compresión multirresolución consisten en ralizar varias versiones<br />

a diferentes resoluciones de los datos; de forma que según la resolución requerida,<br />

se usa un subconjunto de las mismas, reduciendo así la cantidad de datos necesaria.<br />

De esta forma se consigue una versión de la imagen a la resolución deseada, llegando<br />

un compromiso entre resolución y tamaño de los datos, que podrá variar según el<br />

uso concreto, medio de almacenamiento o transmisión de los mismos, etc.<br />

1


Vamos a estudiar e implementar principalmente dos bloques de esquemas <strong>para</strong><br />

la compresión de datos volumétricos<br />

Esquema multirresolución basado en wavelets. Este método de compresión,<br />

muy de moda hoy en día <strong>para</strong> todo tio de señales, hace uso de la transformada<br />

wavelet <strong>para</strong> obtener la versión a baja resolución de los datos.<br />

<strong>Esquemas</strong> no basados en wavelets. Son métodos geométricos de reducir la resolución<br />

y, por tanto, el tamao de los datos volumétricos. Básicamente consisten<br />

en diezmar o simplificar la malla triangular que se obtiene a partir de la red<br />

cúbica que forman los datos, despreciando aquellos que producen un menor<br />

error sobre el resultado.<br />

Ambos son, en nuestro caso, métodos de compresión con pérdidas, pues aunque<br />

la transformada wavelet permiten obtener una versión multirresolución sin pérdidas,<br />

nuestro objetivo es reducir el tamaño de los datos volumétricos, por lo que en ambos<br />

casos se obtendrá una versión suavizada de la imagen.<br />

Por otro lado en el caso de aplicaciones médicas, normalmente no interesa la<br />

imagen de forma global, sino sólo una pequeña parte de la misma, pero sin perder<br />

toda la información del resto. En este caso es muy interesante obtener una versión<br />

a muy baja resolución de toda la imagen (muy suavizada), excepto de la parte que<br />

interesa. De esta forma se debe reducir mucho su tamaño, sin perder información<br />

útil.<br />

Nuestro objetivo no ha sido solamente la compresión de los datos volumétricos,<br />

sino que al final, de deben visualizar las imágenes renderizadas. Se ha empleado renderización<br />

de isosuperficies (ver capítulo 3). Dependiendo del método de compresión,<br />

obtenemos la isosuperficie en un momento distinto:<br />

En el caso de usar wavelets, como se transforma una función en otra, se aplica<br />

directamente sobre los datos volumétricos, antes de calcular la isosuperficie,<br />

que se obtiene de los datos reconstruidos a baja resolución.<br />

En el caso de usar otros esquemas geométricos basados en diezmado de mallas<br />

triangulares, se deben obtener en primer lugar la isosusperficie. Una vez<br />

obtenida, se realiza el diezmado de la misma.<br />

Los Objetivos perseguidos durante la realización del proyecto han sido los siguientes:<br />

Implementar los esquemas de compresión basados en la transformada wavelet<br />

y en diezmado de mallas triangulares.<br />

Crear una aplicación interactiva, <strong>para</strong> los dos esquemas estudiados, que realice<br />

la compresión multirresolución de los datos volumétricos, así como la<br />

visualización de los mismos.<br />

2


Com<strong>para</strong>r los dos esquemas de compresión en cuanto a error de la compresión,<br />

y tamaño de los datos.<br />

Conseguir mediante compresión wavelet que la resolución de la imagen renderizada<br />

varíe de una zona a otra. De esta forma se ve la imagen con mayor<br />

resolución en la zona de interés.<br />

Las fases de su realización serán:<br />

Estudio y lectura de los artículos y manuales necesarios <strong>para</strong> la realizacin del<br />

proyecto.<br />

Programación de herramientas <strong>para</strong> implementar ambos métodos de compresión,<br />

así como su posterior visualización.<br />

Realizar un estudio <strong>para</strong> com<strong>para</strong>r ambos esquemas de compresión.<br />

Obtener conclusiones y posibles líneas futuras, a partir del estudio anterior.<br />

La memoria del proyecto se ha estructurado de la forma que sigue.<br />

En el capítulo 2 se describen los principales métodos empleados <strong>para</strong> captación<br />

de datos volumétricos en medicina. En concreto se describe la tomografía computerizada,<br />

la resonancia magnética y los ultrasonidos.<br />

En el capítulo 3 se estudia la visualización o renderización de datos volumétricos.<br />

Se hace un estudio general de todos los elementos implicados en el problema, como<br />

pueden ser colores, luces, cámaras, . . . Además se describe el método de obtención<br />

de isosuperficies más empleado: Marching Cubes [13].<br />

En el capítulo 4 se explican los principales esquemas de compresión usados en<br />

varias disciplinas de tratamiento digital de datos. En concreto, se estudian los métodos<br />

de compresión de voz y audio, y los métodos de compresión de imágenes y datos<br />

volumétricos. Al final del capítulo se describe el esquema de diezmado usado <strong>para</strong><br />

la realización del proyecto.<br />

El capítulo 5 se dedica por completo a la transformada wavelet. En primer lugar<br />

se estudia la transformada wavelet unidimensional, y a continuación la transformada<br />

<strong>para</strong> datos volumétricos (3D), extensión de la anterior. También se trata la aplicación<br />

de la transformada wavelet a compresión de datos volumétricos.<br />

En el capítulo 6 se realiza una descripción de la aplicación a nivel de usuario,<br />

prestando especial atención a la interfaz gráfica de usuario, pues es el medio de<br />

interacción con el usuario. También se describen, dos ejemplos de utilización de la<br />

aplicación que pueden servir como manual de usuario de la misma.<br />

El capítulo 7 es el manual de referencia de la aplicación. En él se describe de<br />

forma más concreta y profunda el programa de la aplicación. Se describen además<br />

los algoritmos que ha sido necesario diseñar e implementar.<br />

3


En el capítulo 8 se muestran los resultados obtenidos <strong>para</strong> medidas de error con<br />

los dos métodos de compresión. Estos resultados son analizados y comentados.<br />

En el capítulo 9, se enumeran las conclusiones obtenidas y se proponen líneas<br />

futuras <strong>para</strong> continuar lo desarrollado aquí.<br />

El proyecto se ha realizado utilizando el sistema de programación de aplicaciones<br />

de visualización llamado Visualization Toolkit [12], que se describe en el<br />

apéndice A. También se describe, brevemente el lenguaje de programación interpretado<br />

Tcl/Tk [15].<br />

Finalmente, en el apéndice B aparece parte del código fuente de las partes de<br />

la aplicación que se han considerado más importantes. Aparece suficientemente comentadas<br />

<strong>para</strong> continuar con el trabajo realizado.<br />

4


Capítulo 2<br />

Captación de Imágenes Médicas<br />

2.1. Introducción<br />

Las aplicaciones médicas de visualización 3D constan de varios pasos: adquisición<br />

de datos, procesado de imagen (filtrado, compresión, máscaras de conectividad, etc.),<br />

creación de modelos (obtención de isosuperficies), y operaciones de visualización.<br />

En este capítulo se estudia el primer paso, la captación de los datos volumétricos<br />

<strong>para</strong> aplicaciones médicas 1 . La captación se realiza mediante dispositivos de hardware<br />

que muestrean ciertas propiedades en el cuerpo de los pacientes y producen<br />

múltiples “rodajas” bidimensionales de información. Los datos muestreados dependerán<br />

de la técnica de adquisición empleada.<br />

La Tomografía Computerizada (TC) mide la variación espacial del coeficiente de<br />

atenuación de los rayos-x. Las imágenes tomográficas muestran la estructura interna<br />

del cuerpo. Para aplicaciones 3D, la TC se usa normalmente <strong>para</strong> mirar la estructura<br />

de los huesos, pero también sirve <strong>para</strong> ver tejidos blandos, como se verá a lo largo<br />

del proyecto.<br />

La Resonancia Magnética (RM) mide, principalmente, tres propiedades físicas.<br />

Una propiedad es la distribución de los núcleos móviles de hidrógeno, Las otras<br />

dos propiedades miden los tiempos de relajación del núcleo. La imágenes obtenidas<br />

mediante RM muestran un excelente contraste entre distintos tejidos blandos.<br />

Finalmente, los ultrasonidos se basan en generar ondas acústicas (ultrasonidos)<br />

hacia el interior de la zona a explorar, midiendo los ecos recibidos. Los transductores<br />

usados hoy en día, permiten realizar barridos electrónicos de la zona de estudio sin<br />

necesidad de moverlos mecánicamente.<br />

1 La información necesaria <strong>para</strong> la realización de este capítulo ha sido obtenida de [1].<br />

5


Figura 2.1: Dibujo esquemático de una instalación de TC. Consta de (1) consola de<br />

control, (2) soporte de la grúa, (3) mesa del paciente (4) soporte <strong>para</strong> la cabeza (5)<br />

impresor de imagen láser.<br />

2.2. Tomografía Computerizada<br />

2.2.1. Instrumentación<br />

El desarrollo de la tomografía computerizada (TC) a principio de los años 70<br />

revolucionó la radiología médica, ya que por primera vez se pudo obtener imágenes<br />

tomográficas (secciones axiales) de alta calidad de las estructuras internas del cuerpo.<br />

La sofisticación técnica ha aumentado enormemente desde entonces y hoy en día, la<br />

tomografía computerizada continúa madurando.<br />

Las imágenes se reconstruyen a partir de un gran número de medidas de la transmisión<br />

de rayos-x a través del paciente (datos proyectados). Las imágenes resultantes<br />

son “mapas” tomográficos del coeficiente de atenuación lineal de los rayos-x.<br />

La tarea fundamental de los sistemas de TC es hacer un número extremadamente<br />

alto (sobre quinientas mil) de medidas muy precisas de la transmisión de los rayos-x<br />

a través del paciente. Estas medidas deben ser realizadas sobre una geometría muy<br />

exacta y controlada. En la Fig. 2.1 se muestra un típico escáner moderno y en la<br />

Fig. 2.2 aparecen algunas imágenes tomográficas. Un sistema básico, generalmente<br />

consta de grúa, mesa de paciente, consola de control y ordenador. La grúa, a seu<br />

vez, consta de fuente de rayos-x, detectores de rayos-x y el sistema de adquisición<br />

de datos (SAD).<br />

6


Figura 2.2: Imágenes típicas de TC de (a) cerebro, (b) cabeza con las órbitas, (c)<br />

pecho con los pulmones y (d) abdomen.<br />

7


Figura 2.3: Cuatro generaciones de escaners <strong>para</strong> TC, mostrando las geometrías de<br />

haz <strong>para</strong>lelo y en abanico.<br />

Tipos de Geometría <strong>para</strong> la Adquisición de Datos<br />

Los datos proyectados se pueden adquirir mediante varias geometrías, que se describen<br />

a continuación. Estas geometrías dependen de la configuración de exploración<br />

(scanning), movimientos de exploración y estructura del detector. La evolución de<br />

estos sistemas se describe en términos de “generaciones”, como se muestra en las<br />

Figs. 2.3 y 2.4 y muestra su evolución histórica. Actualmente se usan escáners de<br />

3. a , 4. a y 5. a generación, cada uno con sus pros y contras.<br />

Primera Generación: Geometría de Haz Paralelo<br />

Es la más simple técnicamente y la más sencilla <strong>para</strong> entender los principios<br />

de TC. Se usa un solo lápiz <strong>para</strong> el haz de rayos-x y un solo detector. El<br />

haz se traslada de forma lineal a través del paciente <strong>para</strong> obtener un perfíl de<br />

proyección, como se puede ver en la Fig. 2.3. A continuación, se rotan la fuente<br />

y el detector sobre el isocentro del paciente un ángulo de 1 o y se procede de<br />

la misma forma. Este movimiento de traslación y rotación se repite, hasta que<br />

fuente y detector se han movido 180 o . Tiene un excelente rechazo de radiación<br />

8


dispersa en el paciente, pero el complejo movimiento de exploración hace que<br />

el escaneado sea muy lento (aprox. 5 minutos).<br />

Segunda Generación: Haz en Abanico, Múltiples Detectores<br />

El tiempo de exploración se reduce a aproximadamente 30 s con el uso de<br />

haz de rayos-x en abanico y un array lineal de detectores. Aún se usa un<br />

movimiento traslación-rotación; sin embargo, se pueden hacer incrementos de<br />

rotación mayores. Los algoritmos de reconstrucción son más complicados que<br />

los de la primera generación, debido a la proyección en abanico.<br />

Tercera Generación: Haz en Abanico, Detectores Rotatorios<br />

Un haz de rayos-x en abanico se rota 360 o alrededor del isocentro. No hay<br />

movimiento de traslación, por lo que el haz debe ser lo suficientemente ancho<br />

<strong>para</strong> contener completamente al paciente. Se usa un array de detectores<br />

curvado, formado por cientos de detectores independientes acoplados mecanicamente<br />

a la fuente, por lo que rotan juntos. Como consecuencia, se adquieren<br />

los datos de una imagen en 1 s. Rechazan mejor la radiación dispersa, ya que<br />

tienen finas capas de tungsteno entre cada dos detectores y se enfocan a la<br />

fuente de rayos-x.<br />

Cuarta Generación: Haz en Abanico, Detectores Fijos<br />

La fuente con el haz en abanico rota alrededor del isocentro, mientras que el<br />

array de detectores permanece fijo y rodea completamente al paciente. Los<br />

tiempos de escaneado son similares a los de la tercera generación. Los detectores<br />

no están acoplados a la fuente.<br />

Quinta Generación: Exploración por Haz de Electrones<br />

El array de detectores permanece estacionario, mientras un haz de electrones<br />

barre electrónicamente un ánodo de tungsteno con forma semicircular, como<br />

se muestra en la Fig. 2.4. Se producen rayos-x en el punto en el que el haz<br />

de electrones incide sobre el ánodo. Como resultado, se produce una fuente de<br />

rayos-x que gira sobre el paciente sin partes móviles. Los datos proyectdos se<br />

pueden adquirir en 50 ms, lo cual es suficiente <strong>para</strong> obtener, por ejemplo, una<br />

imagen del corazón sin artefactos de movimiento.<br />

Escaneado Espiral/Helicoidal<br />

Mediante este tipo de escaneado se obtienen tiempos de exploración menores,<br />

en especial si lo que queremos –como ocurre en nuestro caso– son escaneados<br />

múltiples <strong>para</strong> obtener imágenes en tres dimensiones. Son sistemas de escaneado<br />

espirales, como se muestra en la Fig. 2.5. Los sistemas de tercera y cuarta<br />

generación consiguen esto mediante canales en forma de anillo sobre los que<br />

rota la grúa. Proporcionan alimentación al sistema y permiten una rotación<br />

continua y un movimiento suave de la fuente de rayos-x. Se adquieren múltiples<br />

imágenes mientras el paciente se mueve a través de la grúa. Esto permite<br />

un movimiento continuo, en lugar de <strong>para</strong>r en la adquisición de cada imagen.<br />

9


Figura 2.4: Esquema de un escáner ultrarrápido de quinta generación. La imagen se<br />

adquiere en 50 ms por el barrido electrónico del ánodo.<br />

Se puede obtener de esta forma una “rodaja” por segundo. Los algoritmos de<br />

roconstrucción son más complicados, ya que deben tener en cuenta el camino<br />

espiral recorrido por la fuente de rayos-x.<br />

Sistema de Rayos-X<br />

El sistema de rayos-x consta de fuente de rayos-x, detectores y sistema de adquisición<br />

de datos.<br />

Fuente de Rayos-X<br />

Produce los rayos-x acelerando un haz de electrones sobre un blanco, que es el<br />

ánodo. El área del ánodo desde el que se emiten los rayos-x, proyectándolos a<br />

lo largo de la dirección del haz, se llama zona focal. Se usa un colimador <strong>para</strong><br />

controlar el ancho del haz en abanico, entre 1.0 y 10 mm. Mediante este haz<br />

se controla el tamaño de las “rodajas”.<br />

La intensidad del haz de rayos-x disminuye por los fenómenos de atenuación y<br />

dispersión, al pasar por el cuerpo del paciente. El grado de atenuación depende<br />

del espectro de energía de los rayo-x, así como del número atómico medio y<br />

densidad de los tejidos del paciente. La intensidad transmitida está dada por<br />

∫ L<br />

I t = I 0 e<br />

µ(x)dx 0 (2.1)<br />

donde I 0 e I t son las intensidades del haz incidente y transmitido, respectivamente;<br />

L es la longitud de la trayectoria de los rayos; y µ(x) es el coeficiente de<br />

atenuación lineal de los rayos-x, que varía con el tipo de tejido y por tanto es<br />

10


Figura 2.5: El escaneado en espiral provoca que el punto focal siga una trayectoria<br />

espiral alrededor del paciente.<br />

una función de la distancia x a través del paciente. La integral del coeficiente<br />

de atenuación es, por tanto<br />

∫ L<br />

0<br />

µ(x)dx = − 1 L ln ( It<br />

I 0<br />

)<br />

(2.2)<br />

El algoritmo de reconstrucción requiere medidas de esta integral a lo largo<br />

de muchos caminos del haz en abanico, en cada uno de los muchos ángulos<br />

alrededor del isocentro. El valor de L es conocido, y I 0 se determina por el<br />

sistema de calibración. Por tanto, el valor de la integral a lo largo de cada<br />

camino se pueden calcular a partir de las medidas de I t<br />

Detectores de Rayos-X<br />

Los detectores de rayos-x deben poseer las siguientes características:<br />

• Alta eficiencia total, <strong>para</strong> minimizar la dosis de radiación sobre el paciente.<br />

• Alto rango dinámico.<br />

• Ser muy estables con el tiempo.<br />

• Ser insensibles a las variaciones de temperatura dentro de la grúa.<br />

Hay tres factores que contribuyen a la eficiencia del detector:<br />

11


Eficiencia geométrica: área de los detectores sensible a la radiación, como<br />

fracción del área total expuesta. Si se ponen finas superficies entre los<br />

detectores <strong>para</strong> reducir la radiación dispersa, esta eficiencia disminuye.<br />

Eficiencia cuántica: fracción de los rayos-x incidentes que son absorbidos y<br />

contribuyen a la señal medida.<br />

Eficiencia de conversión fracción entre la señal eléctrica obtenida a la salida<br />

y la señal de rayos-x en el detector.<br />

La eficiencia total es el producto de las tres anteriores y suele estar entre<br />

0.45 y 0.85. Un valor menor de 1 indica un detector no ideal y produce un<br />

incremento en la dosis de radiación que sufre el paciente, si se quiere mantener<br />

la calidad de la imagen.<br />

Los sistemas comerciales modernos usan uno de estos dos tipos de detectores:<br />

Detectores de estado sólido: Constan de un array de critales centelleantes<br />

y fotodiodos. Los cristales centelleantes son de tungstenato de cadmio<br />

(CdWO 4 ) o materiales cerámicos de tierras raras. Generalmente tienen<br />

una muy alta eficiencia cuántica y eficiencia de conversión, y un gran<br />

rango dinámico.<br />

Detectores de gas ionizado: Están formados por un array de cámaras que<br />

contienen gas a alta presión (normalmente xenón a más de 30 atm.) se<strong>para</strong>das<br />

por finas paredes de tungsteno. Se aplica un alto voltaje a superficies<br />

alternas, <strong>para</strong> recoger los iones producidos por la radiación. Estos detectores<br />

tienen una gran estabilidad y gran rango dinámico; sin embargo<br />

suelen tener menor eficiencia cuántica que los de estado sólido.<br />

Sistema de Adquisición de Datos (SAD) La fracción I t /I 0 a través de<br />

un paciente obeso puede ser menor de 10 −4 . Por tanto, el DAS debe medir<br />

con precisión I t , sobre un rango de más de 10 4 , codificar los resultados a<br />

valores digitales y transmitirlos al sistema de reconstrucción. La mayoría de los<br />

DAS constan de preamplificadores de precisión, conversores corriente–voltaje,<br />

integradores analógicos, multiplexores y conversores analógico–digital. En la<br />

Fig. 2.6 se muestra un esquema. La conversión logarítmica requerida en la<br />

Eq. (2.2) se realiza con un conversor logarítmico analógico o bien con una<br />

tabla de traducción digital, según el fabricante.<br />

La tasa media de transferencia al sistema informático es del orden de 10 Mbytes/s<br />

<strong>para</strong> algunos escáners. Esto se puede conseguir mediante conexiones directas,<br />

en los sistemas con un array de detectores fijo. En los sistemas de tercera<br />

generación, se usan sistemas más sofisticados, como transmisores ópticos.<br />

Sistema informático Los sistemas pueden variar dependiendo del fabricante,<br />

pero un esquema típico se muestra en la Fig. 2.7. Usa 12 procesadores independientes<br />

conectados por un multibus de 40 MBytes/s. Se usan procesadores<br />

12


Elemento<br />

detector<br />

Transductor<br />

I-V<br />

Integrador<br />

Multiplexor<br />

ADC<br />

al ordenador<br />

Figura 2.6: El sistema de adquisición de datos convierte la señal eléctrica producida<br />

en cada detector a un valor digital en el ordenador.<br />

en array <strong>para</strong> conseguir una velocidad conjunta de 200 MFLOPS (millones de<br />

operaciones en punto flotante por segundo) y un tiempo de 5 s <strong>para</strong> reconstruir<br />

una imagen de 1024 × 1024 pixels. Se usa un sistema operativo UNIX simplificado<br />

<strong>para</strong> proporcionar multitarea y entorno multiusuario. De esta forma se<br />

pueden coordinar todas las tareas.<br />

2.2.2. Principios de Reconstrucción: Proyección de los Datos<br />

a la Imagen<br />

El gran impacto de la TC creó un considerable interés en los aspectos formales<br />

de la reconstrucción. Hay muchas descripciones de los procedimientos de reconstrucción<br />

directa. Sin embargo esto queda totalmente fuera de mi estudio. Hay algunos<br />

manuales destinados a cursos de un año de duracion, que se ocupan únicamente de<br />

los principios de reconstrucción. Aquí simplemente daré algunas ideas básicas.<br />

El método estándar de reconstrucción se llama convolución y retroproyección.<br />

El primer paso del método consiste en convolucionar la proyección –un conjunto<br />

de transmisiones hechas a lo largo de líneas <strong>para</strong>lelas en el plano de la rodaja–<br />

con un kernel derivado de la transformada inversa de Radon. La elección del kernel<br />

está determinada por los problemas relacionados con la limitación del ancho de<br />

banda. Se puede modificar <strong>para</strong> tener en cuenta la apertura física del sistema TC,<br />

y se pueden incluir efectos de dispersión.<br />

El siguiente paso es retroproyectar a una matriz bidimensional (la imagen que<br />

queremos obtener) la proyección convolucionada. La retroproyección es el proceso<br />

opuesto a proyección. El valor de la proyección se suma a cada punto a lo largo<br />

de la línea de la proyección. Este procedimiento tiene sentido en una descripción<br />

continua, pero <strong>para</strong> matrices discretas, la suma se debe hacer sobre la matriz de la<br />

imagen. El problema es que muy pocas, o ninguna línea intersecta cada punto de la<br />

matriz. Por eso, <strong>para</strong> estimar el valor de proyección que se debe añadir a un punto,<br />

se interpolan dos valores muestreados de la proyección convolucionada. El esquema<br />

13


Figura 2.7: El sistema informático controla los movimientos de la grúa, adquiere la<br />

medida de las transmisiones de rayos-x, y reconstruye la imagen final. El sistema<br />

mostrado aquí usa 12 CPUs de la familia 68000 (Cortesía de Picker International,<br />

Inc.).<br />

14


de interpolación lineal es sensiblemente mejor que el de elegir la proyección más<br />

cercana al punto de la matriz. No se usan esquemas de interpolación más complejos,<br />

pues son incompatibles con la elección del kernel, que se suele hacer <strong>para</strong> realizar<br />

cierto tratamiento sobre la imagen, como por ejemplo realce de bordes.<br />

Se han desarrollado escáners <strong>para</strong> adquirir un conjunto tridimensional de datos<br />

proyectados. Como ya indiqué anteriormente, el movimiento de la fuente define una<br />

espiral relativa al paciente. El movimiento en espiral define un eje. Como consecuencia,<br />

sólo se dispone de una proyección <strong>para</strong> la reconstrucción de los valores de<br />

atenuación en el plano. Éste es el mismo problema de la imagen bidimensional y la<br />

solución es idéntica: un valor de proyección se interpola a partir de los valores de<br />

proyección existentes, <strong>para</strong> estimar las proyecciones necesarias <strong>para</strong> reconstruir cada<br />

plano. Este procedimiento tiene la ventaja de que las rodajas superpuestas pueden<br />

ser reconstruidas sin una exposición adicional, lo cual elimina el riesgo de que una<br />

pequeña lesión se pierda a causa de que se extienda a ambos lados de rodajas adyacentes.<br />

Los escáners en espiral han hecho posible la adquisición de un conjunto<br />

completo de datos de una sola pasada.<br />

2.3. Resonancia Magnética<br />

2.3.1. Adquisición y Procesado<br />

La Resonacia Magnética (RM) es un método de adquisición de imágenes<br />

médicas muy importante, debido a su excepcional contraste entre tejidos blandos.<br />

Al igual que la tomografía computerizada, se inventó a principios de los años 70.<br />

El primer escáner comercial apareció, sin embargo, diez años después. Durante los<br />

años 80 sólo se encontraban en algunos centros de investigación de Estados Unidos.<br />

Sin embargo, hoy en día hay escáners en los departamentos de radiología de muchos<br />

hospitales de todo el mundo, capaces de obtener imágenes <strong>para</strong> el diagnóstico de<br />

la anatomía interna del cuerpo humano. Estudios no invasivos mediante RM están<br />

sustituyendo a muchos procedimientos convencionales invasivos. Un estudio de 1990<br />

mostró que las principales aplicaciones de las RM son: la exploración de la cabeza<br />

(40 %), la espina dorsal (33 %), huesos y articulaciones (17 %), y el tronco (10 %). El<br />

porcentaje de huesos y articulaciones ha crecido desde entonces. En 1991, había más<br />

de dos mil ochocientos escáners de RM en funcionamiento en todo el mundo y se<br />

hacían más de seis millones de escáners al año. Estas cifras han aumentado mucho<br />

desde entonces.<br />

Aunque la duración típica de un escáner varía entre 1 y 10 minutos, las nuevas<br />

técnicas permiten la adquisición de imágenes en menos de 50 ms. La investigación en<br />

RM tiene que tener en cuenta los compromisos entre resolución, rapidez y relacion<br />

señal a ruido (SNR).<br />

15


Los escáners de RM usan la técnica de resonancia magnética nuclear <strong>para</strong> inducir<br />

y detectar una señal de radiofrecuencia, que es la manifestación del magnetismo nuclear.<br />

El término magnetismo nuclear se refiere a propiedades magnéticas débiles<br />

que exhiben algunos materiales, como consecuencia del spin nuclear asociado con<br />

el núcleo de sus átomos. En particular, el protón, que es el núcleo del átomo de<br />

hidrógeno, posee un spin distinto de 0 y es una excelente fuente de señales de RM. El<br />

cuerpo humano cotiene un número enorme de átomos de hidrógeno –especialmente<br />

en el agua (H 2 O) y moléculas de lípidos. Aunque se pueden obtener señales biológicamente<br />

importantes a partir de otros elementos químicos del cuerpo, como el<br />

fósforo y el sodio, la gran mayoría de los estudios clínicos de RM se basan en los<br />

protones del hidrógeno del paciente.<br />

Fundamentos de Resonancia Magnética<br />

La resonancia magnética explota la existencia de un magnetismo nuclear inducido<br />

en el paciente. Los materiales con un número impar de protones o neutrones<br />

tienen un momento magnético nuclear, que aunque es pequeño, es observable. Las<br />

imágenes normalmente se obtienen de los protones ( 1 H), aunque también tienen<br />

interés el carbono ( 13 C), fósforo ( 31 P), sodio ( 23 Na) y flúor ( 19 F). Los momentos<br />

nucleares están normalmente distribuidos de forma aleatoria, pero cuando se sitúan<br />

bajo un campo magnético intenso, se alinean. Las intensidades típicas de campo<br />

magnético varían de 0.2 a 1.5 T. La magnetización nuclear que se produce es muy<br />

débil com<strong>para</strong>da con el campo magnético aplicado (del orden de 4 × 10 −9 ). A la<br />

colección de momentos nucleares se le suele llamar magnetización o spins.<br />

El momento magnético nuclear es demasiado débil <strong>para</strong> poder ser medido cuando<br />

está alineado con el campo magnético estático. Mediante técnicas de resonancia, este<br />

débil momento puede ser medido. La idea es medir el momento mientras oscila en un<br />

plano perpendicular al campo estático. Cuando el momento es perpendicular a este<br />

campo, sufre un par de torsión proporcional a la intensidad del campo estático. El<br />

par es siempre perpendicular a la magnetización, y provoca que los spins oscilen en<br />

un plano perpendicular al campo estático. La frecuencia de la rotación ω 0 , llamada<br />

frecuencia de Larmor, es proporcional a la intensidad del campo:<br />

ω 0 = −γB 0 (2.3)<br />

donde γ es la relación giromagnética, una constante específica del núcleo, y B 0 la<br />

intensidad del campo magnético estático. La dirección de B 0 define el eje z.<br />

Para poder observar esta oscilación, tenemos que desviar la magnetización de<br />

la dirección del campo estático. Esto se consigue con un campo de radiofrecuencia<br />

(RF) rotatorio débil. Se puede demostrar que un campo de este tipo introduce un<br />

campo ficticio en la dirección z de intensidad ω/γ. Sintonizando la frecuencia de<br />

este campo de RF a ω 0 , de forma efectiva borramos el campo B 0 . El campo de RF<br />

va desviando lentamente la magnetización del eje z.<br />

16


Como los momentos de oscilación constituyen un flujo variable con el tiempo,<br />

producen un voltaje, que puede ser medido en un antena acoplada <strong>para</strong> medir las<br />

componenetes x e y de la inducción. La señal obtenida de la resonancia magnética<br />

nuclear del cuerpo humano se debe, fundamentalmente, a los protones del agua. Como<br />

estos protones están en entornos magnéticos idénticos (moléculas de H 2 O), todos<br />

resuenan a la misma frecuencia, con lo que la señal es simplemente proporcional al<br />

volumen de agua. La innovación clave <strong>para</strong> RM es imponer variaciones espaciales<br />

al campo magnético <strong>para</strong> distinguir los spins por su localización. Aplicando un gradiente<br />

de campo magnético, se produce una oscilación en cada región del volumen<br />

a distinta frecuencia. El campo no uniforme más efectivo consiste en un gradiente<br />

lineal, donde el campo y la frecuencia resultantes varían linealmente con la distancia<br />

a lo largo del objeto estudiado. Como veremos a continuacion, mediante un análisis<br />

de Fourier de la señal, se obtiene un mapa de la distribución espacial de los spins.<br />

Análisis del k-Espacio de Adquisición de Datos<br />

En RM lo que recibimos e una integral de volumen de un array de osciladores.<br />

Si nos aseguramos de que la fase de cada oscilador sea única, podemos asignar<br />

una única localización a cada spin y por tanto reconstruir la imagen. Durante la<br />

recepción, el campo magnético aplicado apunta en la dirección z, mientras que los<br />

spins oscilan en el plano xy a la frecuencia de Larmor. Por tanto, un spin en la<br />

posición r = (x, y, z) tiene una fase única θ, que describe un ángulo relatico al eje y<br />

en el plano xy:<br />

θ(r, t) = −γ<br />

∫ t<br />

0<br />

B z (r, τ)dτ (2.4)<br />

donde B z (r, τ) es la componente z de la densidad instantánea local de flujo magnético.<br />

En esta fórmula se asume que no hay componentes de campo en x e y.<br />

Una bobina suficientemente grande <strong>para</strong> recibir el flujo variante con el tiempo,<br />

de forma uniforme de todo el volumen, produce una señal proporcional a<br />

s(t) ∝ d dt<br />

∫<br />

V<br />

M(r)e −jθ(r,t) dr (2.5)<br />

donde M(r, t) es la densidad del momento de equilibrio en cada punto r.<br />

La idea clave de la reconstrucción es suponer que el campo estático B 0 tiene<br />

variación lineal. Este campo apunta en la dirección z y varía en alguna dirección.<br />

En general este campo es (xG x +yG y +zG z )ẑ, o de forma compacta: G · rẑ. Además,<br />

estas componentes pueden variar con el tiempo, con lo que el campo total es<br />

Con este gradiente, la señal recibida es<br />

s(t) ∝ d dt<br />

B z (r, t) = B 0 + G(t)·r (2.6)<br />

∫<br />

V<br />

e −jγB 0t M(r)e −jγ ∫ t<br />

0 G(τ)·rdτ dr (2.7)<br />

17


La frecuencia central γB 0 es siempre mucho mayor que el ancho de banda de la<br />

señal, por lo que se puede aproximar la derivación por una multiplicación por −jω 0 .<br />

Demodulando, la señal en banda-base es<br />

∫<br />

∫<br />

S(t) ∝ −jω 0 M(r)e −jγ t<br />

G(τ)·rdτ 0 dr (2.8)<br />

Si defino el término k(t) como:<br />

V<br />

k(t) = γ<br />

∫ t<br />

o<br />

G(τ)dτ (2.9)<br />

puedo reescribir la señal en banda base como<br />

∫<br />

S(t) ∝ −jω 0 M(r)e −jk(t)·r dr (2.10)<br />

V<br />

que se puede identificar cono la transformada de Fourier espacial de M(r) evaluada<br />

en k(t). Esto se puede escribir como:<br />

S(t) ∝ ˆM(k(t)) (2.11)<br />

donde ˆM(k) es la transformada de Fourier tridimensional de la distribución en el<br />

objeto M(r). Por tanto podemos ver la RM con un gradiente lineal como una exploración<br />

del k-espacio o de la transformada de Fourier de la imagen. Una vez escaneada<br />

la parte del k-espacio deseada, se obtiene la imagen M(r) mediante la transformada<br />

de Fourier inversa.<br />

Mecanismos de Contraste<br />

La tremenda utilidad de la RM es debida a la gran variedad de mecanismos<br />

que se pueden usar <strong>para</strong> crear una imagen de contraste. Si las imágenes sólo se<br />

pudieran obtener a partir de la densidad de agua, las resonancias magnéticas serían<br />

mucho menos útiles, ya que muchos tejidos aparcerían idénticos. Afortunadamente se<br />

pueden usar muchos mecanismos de contraste <strong>para</strong> distinguir los diferentes tejidos.<br />

Los principales mecanismos usan la relajación de la magnetización. A continuación<br />

describo los dos tipos de relajación:<br />

Relajación de spin-red, T 1 : velocidad de recuperación de la componente z<br />

de magnetización hacia el equilibrio, después de ser polarizado por los pulsos<br />

de RF. La recuperación está dada por<br />

M z (t) = M o (1 − e −t/T 1<br />

) + M z (0)e −t/T 1<br />

(2.12)<br />

donde M 0 es la magnetización de equilibrio. Diferencias en la constante de<br />

tiempo T 1 se pueden usar <strong>para</strong> obtener el contraste de la imagen. En la parte<br />

izquierda de la Fig. 2.8 se puede ver la recuperación de dos componentes T 1<br />

diferentes. La componente con T 1 menor se recupera más rápido y produce<br />

más señal.<br />

18


Figura 2.8: Los dos mecanismos principales de contraste en resonancias magnéticas,<br />

T 1 y T 2 .<br />

Relajación spin-spin, T 2 : velocidad de decaimiento de las componentes<br />

transversales de la magentiazación (M x y M y ), después de ser creadas. La<br />

señal es proporcional a la magnetización transversal y viene dada por<br />

M xy (t) = M xy (0)e −t/T 2<br />

(2.13)<br />

La imagen de contraste se obtiene retrasando la adquisición de datos. En la<br />

parte derecha de la Fig. 2.8 se muestra el decaimiento de dos componentes T 2<br />

diferentes. La señal de la componente con T 2 menor decae más rápidamente.<br />

En el momento de recoger los datos, la componente con T 2 mayor produce más<br />

señal<br />

En la Fig. 2.9 aparecen ejemplos de estos dos tipos básicos de contraste. Estas<br />

imágenes son de la misma sección del cerebro. La imagen de la izquierda está obtenida<br />

a partir de T 1 . El anillo exterior brillante es de grasa (materia blanca), que tiene<br />

un menor T 1 que la materia gris. La imagen de la derecha está obtenida a partir de<br />

T 2 . El fluido cerebroespinal de los ventrículos es más brillante, debido a su mayor<br />

T 2 . La materia blanca tiene un menor T 2 que la materia gris, por lo que aparece más<br />

oscura en la imagen.<br />

Además de estos métodos básicos <strong>para</strong> obtener contraste, se pueden introducir<br />

agentes artificiales. Normalmente se administran de forma intravenosa u oral. Hay<br />

muchos mecanismos de este tipo, pero los agentes más usuales disminuyen T 1 y T 2 .<br />

Disminuyendo T 1 se consigue una recuperación más rápida de la señal y una señal<br />

más alta en una imagen que se base en T 1 . De esta forma, las regiones con el contraste<br />

realzado se muestran más brillantes con respecto al resto de la imagen.<br />

19


Figura 2.9: Ejemplos de imágenes de un voluntario normal mostrando el contraste<br />

T 1 a la izquierda y el T 2 a la derecha.<br />

2.3.2. Hardware e Instrumentación<br />

Para realizar una RM en un paciente, se le debe colocar en un entorno en el que<br />

varios campos magnéticos diferentes se apliquen sobre el mismo de forma simultánea<br />

o secuencial, como ya se ha visto en el apartado anterior. Todos los escáners utilizan<br />

un imán de campo estático fuerte, junto con un conjunto sofisticado de bobinas<br />

de gradiente y bobinas de radiofrecuencia. Las componentes de gradiente y de radiofrecuencia<br />

deben activarse y desactivarse con un patrón temporal preciso. Se usan<br />

diferentes secuencias de pulsos <strong>para</strong> extraer diferentes tipos de datos del paciente.<br />

Las imágenes de RM se caracterizan por el excelente contraste entre varios tipos<br />

de tejidos blandos del cuerpo. Además, <strong>para</strong> pacientes sin cuerpos ferromagnéticos<br />

extraños en el interior de su cuerpo, la RM es perfectamente segura y puede ser<br />

repetida, sin peligro, con tanta frecuencia como sea necesaria. Esto proporciona una<br />

de las principales ventajas de la RM con respecto a los rayos-x convencionales y<br />

los escáners de tomografía computerizada. La señal usada en RM no es bloqueada<br />

en absoluto por regiones de aire o hueso dentro del cuerpo, lo cual supone una importante<br />

ventaja sobre los ultrasonidos. También, al contrario que en el escaneado<br />

mediante medicina nuclear, no es necesario suministrar materiales radioactivos al<br />

paciente.<br />

Fundamentos de Instrumentación <strong>para</strong> RM<br />

Hacen falta tres tipos de campos magnéticos –campos principales o campos<br />

estáticos (B 0 ), campos de gradiente, y campos de radiofrecuencia (RF) (B 1 )– en<br />

los escáners usados <strong>para</strong> RM.<br />

20


Flujos de Datos<br />

Dominio Digital<br />

Pulso de Gradiente y RF<br />

Amplitud y Temporizacion<br />

Dominio Analogico<br />

(Ordenador, Almacenamiento Masivo,<br />

Consolas, Procesador en Array, Red)<br />

Senales NMR en el<br />

Dominio del Tiempo<br />

(Amplificadores de Gradiente y RF,<br />

Transceptor, Pruebas, Iman)<br />

Figura 2.10: Dominios digital y analógico en RM. Las RM requieren el intercambio<br />

de datos y comandos entre estos dos dominios.<br />

Una implementación satisfactoria de un sistema de RM requiere un flujo de infromación<br />

bidireccional entre los formatos analógico y digital, como se puede ver en<br />

la Fig. 2.10. El imán principal, las bobinas de RF y gradiente, y la alimentación de<br />

los sitemas de RF y gradiente, operan en el dominio analógico. El dominio digital<br />

se centra en un ordenador de propósito general, usado <strong>para</strong> proporcionar la información<br />

de control (patrón temporal y amplitud de los pulsos) a los amplificadores<br />

de gradiente y RF, <strong>para</strong> procesar la señal de la RM en el dominio del tiempo y <strong>para</strong><br />

controlar los sistemas de representación y almacenamiento de la imagen. Además,<br />

el ordenador ofrece funciones variadas de control que permiten al operador, por<br />

ejemplo, controlar la posición de la mesa del paciente.<br />

Imanes de Campo Estático<br />

El imán del campo principal se usa <strong>para</strong> producir un campo estático intenso en<br />

toda la región que se va a escanear. Para obtener los resultados deseados, este campo<br />

debe ser extremadamente uniforme en el tiempo y en el espacio. En la práctica, la<br />

variación espacial de este campo en toda la zona de escaneado (de alrededor de 40 cm<br />

de diámetro), debe ser del orden de 1 a 10 partes por millón (ppm). Para conseguir<br />

estos altos niveles de homogeneidad es necesario un diseño y una fabricación muy<br />

cuidadosa. La variación temporal del campo debe ser menor de 0.1 ppm/h.<br />

Actualmente se usan dos unidades de intensidad de campo magnético. El Gauss<br />

(G) se ha usado tradicionalmente y aún se usa por razones históricas. La Tesla (T)<br />

es una unidad adoptada más recientemente; se prefiere en general, pues pertenece<br />

al SI. La tesla es una unidad mucho mayor que el gauss (1 T equivale a 1000 G).<br />

El campo magnético de la tierra vale sobre 0.05 mT (0.5 G), y el campo de un<br />

21


imán permanente bastante potente, 0.5 T (5000 G). El campo magnético estático<br />

de los imanes de los sitemas modernos de RM varían entre 0.5 y 1.4 T. La SNR en<br />

un escáner de RM crece linealmente con la intensidad de campo magnético, por lo<br />

que <strong>para</strong> mejorar la SNR se ha hecho mucho esfuerzo en la obtención de campos<br />

magnéticos estáticos mayores.<br />

Los campos magnéticos se pueden conseguir mediante una corriente eléctrica<br />

o mediante imanes permanentes. En ambos casos, la intensidad de campo decae<br />

rápidamente al alejarse de la fuente, y no se puede conseguir un campo magnético<br />

altamente uniforme lejos de las fuentes. Como consecuencia, <strong>para</strong> conseguir el campo<br />

magnético uniforme necesario <strong>para</strong> RM, es necesario rodear, más o menos, al<br />

paciente con un imán. Por ello, el imán que genera el campo estático debe ser lo<br />

suficientemente grande <strong>para</strong> rodear al paciente. Por estas razones, este imán es el elemento<br />

más importante del sistema, que determina el coste y los resultados obtenidos.<br />

Se han usado cuatro clases de imanes: imanes permanentes, electroimanes, imanes<br />

resistivos e imanes de superconductor.<br />

Imanes Permanentes y Electroimanes. Ambos usan materiales imantados<br />

<strong>para</strong> producir los campos que se aplican al paciente. En el caso de usar imán<br />

permanente, se coloca al paciente en el hueco existente entre los dos polos del<br />

imán. Los electroimanes usan una configuración similar, pero están hechos de<br />

materiales magnéticos débiles que se magnetizan al hacer circular corriente<br />

eléctrica por bobinas enrolladas a su alrededor. La se<strong>para</strong>ción entre los polos<br />

debe ser suficiente <strong>para</strong> albergar al paciente y a las bobinas de RF y gradiente.<br />

Los imanes permanentes tienen algunas ventajas: tienen menores efectos de<br />

bordes y no necesitan corriente <strong>para</strong> funcionar. Sin embargo, suelen ser muy<br />

pesados (más de cien toneladas) y pueden producir campos relativamente bajos<br />

(0.3 T o menos). Además pueden sufrir desviaciones temporales, debido a los<br />

cambios de temperatura. Actualmente se están usando nuevos materiales más<br />

ligeros.<br />

Imanes Resistivos. Son bobinas conectadas a fuentes de corriente continua<br />

muy potentes (40 a 100 kW). Debido a la resistencia de las bobinas, se calientan<br />

mucho, por lo que deben ser refrigerados con agua. Actualmente casi no se<br />

usan, excepto <strong>para</strong> aplicaciones en las que se usen campos magnéticos muy<br />

bajos (0.02 a 0.06 T).<br />

Imanes Superconductores. Desde principio de los años 80, el uso de imanes<br />

superconductores enfriados a temperaturas criogénicas ha sido la mejor solución<br />

al problema de producir campos estáticos en los escáners de RM. Como<br />

se sabe, estos materiales tienen una resistencia nula a temperaturas cercanas<br />

al 0 absoluto. Si las bobinas hechas de materiales con esta propiedad no tienen<br />

defectos que interrumpan el flujo de corriente, y se genera una corriente a<br />

través de las mismas, al juntar los dos extremos de la bobina, se establece<br />

22


una corriente constante y duradera. Esta corriente permanece inalterable indefinidamente,<br />

siempre que se mantenga al superconductor por debajo de su<br />

temperatura de transición. La estabilidad de este tipo de imanes es realmente<br />

enorme –hay imanes que han funcionado durante años totalmente desconectados<br />

de las fuentes de corriente y han mantenido el campo magnético constante,<br />

con variaciones de pocas ppm. Debido a su habilidad <strong>para</strong> proporcionar<br />

intensidades de campo muy estables e intensas sin consumo de energía, son<br />

actualmente los más usados <strong>para</strong> los imanes principales de los escáners de RM.<br />

Homogeneidad del campo magnético. La uniformidad necesaria en el campo<br />

magnético estático sólo se puede conseguir colocando las bobinas en determinadas<br />

posiciones espaciales. Una espira genera en su eje un campo magnético en la dirección<br />

de este eje, que se puede expresar como una suma de armónicos esféricos.<br />

El primer término de la suma es independiente de la posición y es, por lo tanto,<br />

el campo deseado. Los armónicos superiores son inhomogeneidades espaciales que<br />

nos estropean la homogeneidad deseada. Estas inhomogeneidades se pueden reducir<br />

colocando otras bobinas en determinadas posiciones espaciales. Por ejemplo, mediante<br />

un sistema de seis bobinas se puede eliminar hasta el armónico número 12,<br />

haciendo a este sistema útil <strong>para</strong> ser usado en RM.<br />

En la práctica, la presencia de campos magnéticos externos provoca también<br />

inhomogeneidades en el campo constante. Por ello se tratan de reducir colocando,<br />

en determinadas posiciones de la sala de exploración, bobinas (aislamiento activo) o<br />

imanes permanentes (aislamiento pasivo). Las posiciones de estos imanes se determinan,<br />

al instalar el sistema, midiendo el campo magnético en la sala. Si se mueve<br />

un gran objeto cerca de esta sala con materiales magnéticos –como una fuente de<br />

alimentación– puede ser necesario volver a medir el campo magnético en la sala y<br />

recolocar el aislamiento.<br />

Campos Radiados. Un gran imán produce, además, campos en la zona que<br />

rodea al sistema, no sólo en su interior. Estos campos pueden borrar sistemas de<br />

almacenamiento informático, como discos y cintas, además de otros elementos como<br />

tarjetas de crédito. También es un peligro potencial <strong>para</strong> personas con dispositivos<br />

implantados, como pueden ser los marcapasos. Como medida de seguridad, se suele<br />

limitar el acceso a estas zonas (que se suelen extender a una distancia de 10 a<br />

12 m del centro de un imán de 1.5 T. También se suelen usar aislamientos formados<br />

por placas metálicas (aislamiento pasivo) o bobinas con la corriente en la dirección<br />

contraria a la del imán principal (asilamiento activo).<br />

Bobinas de Gradiente<br />

Se usan tres campos de gradiente, uno <strong>para</strong> cada una de las tres direcciones<br />

del sistema de coordenadas cartesiano, x, y y z, <strong>para</strong> codificar la información de la<br />

posición en una señal de RM, y <strong>para</strong> permitir distinguir la señal proviniente de las<br />

23


distintas rodajas que forman el volumen. La dirección del campo estático, a lo largo<br />

del eje del escáner, se toma convencionalmente como eje z y sólo la componente<br />

cartesiana del campo de gradiente en esta dirección, contribuye significativamente<br />

al comportamiento resonante de los núcleos. Por tanto, los tres campos de gradiente<br />

relevantes son B z = G x x, B z = G y y, y B z = G z z. La resonancia magnética se lleva<br />

a cabo sometiendo al sistema de spins a una secuencia de camnpos de gradiente y<br />

RF pulsados. Por lo tanto, es necesario tener tres bobinas se<strong>para</strong>das, una <strong>para</strong> cada<br />

dirección, cada una de ellas con un control y una fuente de alimentación independientes.<br />

Normalmente, la forma más normal de construir las bobinas de gradiente es<br />

enrollándolas alrededor de una horma cilíndrica, que rodea al paciente y está dentro<br />

del imán principal.<br />

Bobinas de Radiofrecuencia<br />

Las bobinas de radiofrecuencia se usan en los escáners <strong>para</strong> dos propósitos fundamentales:<br />

transmitir y recibir señales a la frecuencia de resonancia de los protones<br />

dentro del paciente. La oscilación ocurre, como hemos visto a la frecuencia de Larmor<br />

de los protones, que es proporcional a la intensidad del campo magnético estático.<br />

Las intensidades de campo magnético varían en general de 0.02 a 4 T, por lo que<br />

las frecuencias varían de 0.85 a 170.3 MHz. Estas frecuencias están en la zona del<br />

espectro usada <strong>para</strong> radiodifusión de radio y televisión, por ello los componentes<br />

electrónicos usados en el transmisor y receptor del escáner son muy parecidos a los<br />

de radio y televisión. Una diferencia importante entre ambos sistemas, es que las antenas<br />

de los sistemas de radiodifusión operan en condiciones de campo lejano (están<br />

se<strong>para</strong>das muchas longitudes de onda entre sí). Por el contrario, en los escáners, la<br />

se<strong>para</strong>ción entre transmisores y receptores es mucho menor de una longitud de onda,<br />

por lo que operan en condiciones de campo cercano. En campo lejano, la energía<br />

del campo electromagnético es compartida por igual entre las componentes eléctrica<br />

y magnética del campo, mientras que cerca de un dipolo magnético, prácticamente<br />

toda la energía está en el campo magnético.<br />

Las bobinas de RF se colocan en el espacio entre el paciente y las bobinas de<br />

gradiente. Se usan aislamientos conductores justo dentro de las bobinas de gradiente,<br />

<strong>para</strong> evitar acoplamiento electromagnético entre las bobinas de RF y el resto del<br />

escáner.<br />

Procesado Digital de los Datos<br />

Un protocolo típico de escaneado, confecciona una secuencia de pulsos de RF y<br />

gradiente de duración controlada, en intervalos de 0.1 µs. Para conseguir suficiente<br />

rango dinámico <strong>para</strong> controlar las duraciones de los pulsos, se usan conversores D/A<br />

de 12 a 16 bits. La señal de RF a la frecuencia de Larmor se mezcla con un oscilador<br />

local, como ya vimos, <strong>para</strong> conseguir una señal en banda base con un ancho de banda<br />

24


típico de 16 a 32 kHz. El sistema de adquisición de datos (SAD) debe digitalizar la<br />

señal en banda base a la frecuencia de Nyquist, lo cual requiere muestrear la señal de<br />

RF en intervalos de 5 a 20 µs. De nuevo es necesario tener suficiente rango dinámico;<br />

se usan conversores A/D de 16 a 18 bits. La adquisición de datos se realiza a una<br />

tasa del orden de 800 kBytes/s, y cada imagen puede contener más de 1 MByte de<br />

datos digitales. Se usa un procesador en array <strong>para</strong> realizar rápidamente algoritmos<br />

específicos, como la transformada rápida de Fourier (FFT), usada <strong>para</strong> convertir<br />

datos digitales en el dominio del tiempo, al dominio de la imagen. Típicamente las<br />

imágenes en dos dimensiones se muestran como matrices de 256 × 128 o 256 × 256 o<br />

512 × 512 pixels. Estas imágenes pueden estar disponibles <strong>para</strong> ser visualizadas, tan<br />

solo 1 segundo después de su adquisición. Las imágenes en tres dimensiones requieren<br />

más procesado y por tanto mayor tiempo entre adquisición y visualización.<br />

Para cada pixel de la imagen se calcula un valor de brillo, típicamente de 16<br />

bits de grises –como tenemos en los datos usados <strong>para</strong> la realización del proyecto–<br />

y corresponde a la intensidad de señal originada en cada voxel del objeto. Para<br />

hacer un uso más efectivo de la información contenida en las imágenes, se pueden<br />

usar técnicas sofisticadas de visualización, como por ejemplo visualización de múltiples<br />

imágenes al mismo tiempo, visualización secuencial de las imágenes (como en<br />

el cine) y renderización tridimensional de las superficies anatómicas (que es parte<br />

del objetivo del proyecto). Estas técnicas, especialmente la última, son intensivas<br />

computacionalmente y requieren el uso de hardware específico. Las imágenes de RM<br />

están disponibles como datos digitales, por lo que se hace un gran uso de las redes de<br />

área local (LANs) <strong>para</strong> distribuir la información por el hospital; también se pueden<br />

usar redes de área amplia (WANs), <strong>para</strong> transmitir las resonancias entre hospitales<br />

(teleradiología). Esta es una de las posibles líneas futuras de investigación.<br />

2.4. Ultrasonido<br />

2.4.1. Transductores<br />

Un transductor de ultrasonido genera ondas acústicas, convirtiendo energía magnética,<br />

térmica, o eléctrica a energía mecánica. La técnica más efectiva <strong>para</strong> ultrasonidos<br />

médicos usa el efecto piezoeléctrico, que fue demostrado por primera vez por Jacques<br />

y Pierre Curie en 1880. Aplicaron una presión a un cristal de cuarzo y detectaron una<br />

diferencia de potencial entre las caras opuestas del material. También descubrieron<br />

el efecto piezoeléctrico inverso; aplicando un campo eléctrico a través del cristal, se<br />

induce una deformación mecánica en el mismo. De esta forma, tenemos un transductor<br />

piezoeléctrico, que convierte una señal eléctrica oscilante en una onda acústica<br />

y viceversa. Para usos médicos, el material ferroeléctrico más usado es el titanatocirconato<br />

de plomo (PZT).<br />

Se han conseguido muchos avances en las imágenes obtenidas con ultrasonidos,<br />

25


por las innovaciones en la tecnología de los transductores. Una de esas innovaciones<br />

es el desarrollo de transductores en array lineal. Anteriormente, los sistemas de<br />

ultrasonidos obtenían una imagen moviendo manualmente el transductor por la<br />

región de interés. Incluso los escáners más rápidos tardaban varios segundos en<br />

obtener una imagen, por ello sólo se podían escanear elementos estáticos. Si, por<br />

el contrario, el haz acústico recorre la región de interés rápidamente, los médicos<br />

podrían visualizar elementos móviles, como el corazón latiendo. Además, se pueden<br />

obtener imágenes en tiempo real, permitiendo al médico posicionar inmediatamente<br />

el transductor y los parámetros del sistema.<br />

Para conseguir imágenes en tiempo real, se desarrollaron nuevos tipos de transductores<br />

que direccionan rápidamente el haz acústico. Los transductores en forma<br />

de pistón, se diseñaron <strong>para</strong> rotar sobre un eje fijo direccionando de forma mecánica<br />

el haz ultrasónico. Los arrays lineales secuenciales, se diseñaron <strong>para</strong> enfocar electrónicamente<br />

el haz en una región rectangular. Por último, los transductores en array<br />

lineal de fase, se diseñaron <strong>para</strong> direccionar y enfocar el haz, electrónicamente, con<br />

gran velocidad.<br />

Escaneado con Transductores en Array<br />

Los transductores en array usan los mismo principios que las lentes acústicas <strong>para</strong><br />

enfocar un haz acústico. En ambos casos se aplican retardos variables a lo largo de la<br />

apertura del transductor. Sin embargo, en un array secuencial o de fase, los retardos<br />

se controlan de forma electrónica y pueden cambiar de posición instantáneamente<br />

<strong>para</strong> enfocar el haz a diferentes regiones. Los arrays lineales se desarrollaron en<br />

primer lugar <strong>para</strong> radar, sonar y radio astronomía.<br />

Los arrays lineales no tienen partes móviles, como tenían los de pistón y además,<br />

su enfoque se puede dirigir a cualquier punto del plano de exploración. El sistema<br />

puede generar una gran variedad de formatos de exploración. Las desventajas de los<br />

arrays lineales, son debidas a la mayor complejidad y mayor precio de los sistemas.<br />

Para obtener imágenes de alta calidad, hacen falta arrays de muchos elementos (128<br />

actualmente y subiendo). Los elementos del array tienen, típicamente, menos de<br />

un milímetro de lado y cada uno debe tener conexión independiente a su propia<br />

electrónica de transmisor y receptor. Sin embargo las ventajas son mucho más importantes<br />

que las desventajas. Además, las mejoras en las técnicas de fabricación de<br />

transductores y circuitos integrados permiten transductores y escáners más avanzados.<br />

Enfoque y Direccionado de los Arrays de Fase<br />

Un transductor de arrays de fase puede enfocar y direccionar un haz acústico en<br />

una determinada dirección. Repitiendo este proceso más de 100 veces en una región<br />

bidimensional o tridimensional, se obtiene una imagen a partir de los ultrasonidos.<br />

La Fig. 2.11a muestra un ejemplo simple de array lineal de 6 elementos, enfocando<br />

el haz transmitido. Se puede asumir que cada elemento del array es una fuente<br />

26


Figura 2.11: Enfoque y direccionamiento de un haz acústico usando un array de fase.<br />

Se muestra un array de 6 elementos (a) en el modo de transmisión y (b) en el modo<br />

de recepción.<br />

puntual que radia un frente de onda esférico. En el ejemplo, el elemento superior<br />

está más alejado del foco, por lo que es excitado primero. El resto de elementos se<br />

excitan en instantes adecuados <strong>para</strong> que todos los frentes de onda lleguen al foco<br />

en el mismo instante. De acuerdo con el principio de Huygens, el frente de onda<br />

total es la suma de las señales que han llegado de cada fuente. En el punto focal, las<br />

contribuciones de cada elemento se suman en fase, <strong>para</strong> producir un máximo en la<br />

señal acústica. En cualquier otro punto, al menos algunas de las contribuciones se<br />

suman desfasadas, reduciendo el valor con respecto al máximo.<br />

Para recibir un eco de ultrasonido, el array de fase funciona al revés. La Fig. 2.11b<br />

muestra un eco originado en el foco 1. El eco incide en cada elemento del array en un<br />

instante diferente. Las señales recibidas son retardadas electrónicamente de forma<br />

que se sumen en fase <strong>para</strong> un eco originado en el punto focal. Para ecos generados<br />

en cualquier otro punto, al menos algunas de las señales retrasadas se suman fuera<br />

de fase, con lo que se reduce la señal recibida con respecto al máximo en el foco.<br />

En el modo de recepción, el punto focal puede ser dinámicamente ajustado, de<br />

forma que coincida con el alcance de los ecos que vuelven. Depués de la transmisión<br />

de un pulso acústico, los ecos iniciales vuelven desde los elementos más cercanos al<br />

transductor. El escáner enfoca el array de fase, en estos objetivos, localizados en el<br />

foco 1, en la Fig. 2.11b. A medida que vuelven los ecos de elementos más alejados, el<br />

escáner enfoca a una mayor profundidad (foco 2 en la figura). Este proceso se llama<br />

enfoque dinámico en el receptor.<br />

27


Configuraciones de los Elementos del Array<br />

La imagen ultrasónica se obtiene repitiendo, muchas veces, el proceso que acabo<br />

de describir, <strong>para</strong> explorar una región de tejidos 2D ó 3D. Para una imagen 2D,<br />

el plano de exploración es la dimensión azimuth; la dimensión de elevación<br />

es perpendicular al plano de exploración azimuth. La forma de la región escaneada<br />

está determinada por la configuración de los elementos del array, que se describe a<br />

continuación y se muestran en la Fig. 2.12.<br />

Arrays Lineales Secuenciales. Este tipo de arrays suelen tener 512 elementos en<br />

los escáners comerciales. En cada momento se selecciona una subapertura de<br />

128 elementos. Como se muestra en la Fig. 2.12a, las lineas de exploración<br />

están dirigidas perpendicularmente a la cara del transductor. El haz acústico<br />

se enfoca, pero no se dirige. La ventaja de este esquema es que los elementos<br />

del array tienen alta sensibilidad cuando el haz se dirige de esta forma. La<br />

desventaja es que el campo de vista está limitado a una región rectangular<br />

enfrente del transductor.<br />

Arrays Curvilíneos. Tienen distinta forma que los arrays lineales secuenciales,<br />

pero funcionan de la misma forma. En ambos casos, las líneas de exploración<br />

son perpendiculares a la superficie del transductor. Sin embargo, un array<br />

curvilíneo explora un campo mayor, debido a su forma convexa, como se muestra<br />

en al Fig. 2.12b.<br />

Arrays Lineales de Fase. Los arrays lineales de fase más avanzados tienen 128<br />

elementos. Se usan todos los elementos <strong>para</strong> transmitir y recibir cada línea<br />

de datos. Como se muestra en la Fig. 2.12c, el escáner direcciona el haz de<br />

ultrasonido en una región con forma de sector en el plano azimut. Los escáners<br />

de fase exploran una zona significativamente más ancha que el transductor,<br />

por lo que son adecuados <strong>para</strong> escanear en vantanas acústicas limitadas, como<br />

por ejemplo <strong>para</strong> exploraciones cardiacas, en las que los transductores pueden<br />

evitar las obstrucciones de costillas y pulmones. Los ultrasonidos no pueden<br />

explorar a través de hueso y aire.<br />

Arrays de 1.5D. Este array es similar al array de 2D en su construcción, pero es<br />

un array 1D en su funcionamiento. Contiene elementos a lo largo de ambas<br />

dimensiones. Características como enfoque dinámico y correscción de fase se<br />

pueden implementar en ambas dimensiones, <strong>para</strong> mejorar la calidad de la<br />

imagen, pero como tiene un número limitado de elementos en elevación (de 3 a<br />

9 elementos), no se puede direccionar en esa dirección. La Fig. 2.12d muestra<br />

un escáner de este tipo. Con este tipo de array también se pueden realizar<br />

exploraciones secuenciales.<br />

Arrays de Fase 2D. Tienen un número mayor de elementos en ambas dimensiones.<br />

Este tipo de arrays pueden enfocar y direccionar el haz acústico en<br />

28


ambas dimensiones. Usando un procesamiento <strong>para</strong>lelo en el receptor, un array<br />

2D puede explorar una región piramidal en tiempo real, <strong>para</strong> producir una<br />

imagen volumétrica, como se muestra en la Fig. 2.12e.<br />

2.4.2. Obtención de Imágenes por Ultrasonidos<br />

Se conoce, desde hace mucho tiempo, que los tejidos del cuerpo no son homogéneos<br />

y que las seales que se envían hacia ellos, como por ejemplo pulsos de<br />

sonido de alta frecuencia, son reflejadas y dispersadas por esos tejidos. La dispersión<br />

de parte de la energía de la señal incidente hacia otras direcciones, por pequeñas<br />

partículas, es la causa de que se vea una especie de niebla en las imágenes obtenidas.<br />

La parte de la energía dispersada que vuelve al transmisor se llama retrodispersión.<br />

El desarrollo de las imágenes ultrasónicas siguió el mismo proceso que el radar<br />

y el sonar. Inicialmente sólo se se obtenían respresentaciones con una línea de vista<br />

(modo A). A continuación, guardando las sucesivas líneas en el tiempo se obtenían<br />

grabaciones con el movimiento a lo largo del tiempo (modo M). Finalmente, mediante<br />

un barrido mecánico o electrónico se consiguen imágenes bidimensionales (modo<br />

B o 2D). El modo A se mostraba en un osciloscopio, el modo M se imprimía en papel<br />

térmico especialmente sensitivo a la luz, y el modo B, inicialmente, se mostraba en<br />

televisores como imágenes estáticas. Ahora todos los modos se obtienen en tiempo<br />

real y se pueden grabar en cintas de vídeo (<strong>para</strong> estudios en los que el movimiento<br />

es importante) o películas fotográficas (<strong>para</strong> estudios en los que son importantes las<br />

dimensiones de los órganos, pero no el movimiento).<br />

Desde los primeros sistemas que pudieron mostrar movimiento, se desarrollaron<br />

aplicaciones <strong>para</strong> estudiar el corazón, que se debe mover <strong>para</strong> desarrollar su función.<br />

Los modos A y M servían <strong>para</strong> demostrar, por ejemplo, el movimiento de las válvulas,<br />

como se puede ver en la Fig. 2.13.<br />

Más tarde, cuando las representaciones en 2D estuvieron disponibles, se aplicaron<br />

los ultrasonidos, cada vez más, <strong>para</strong> mostrar los órganos blandos del abdomen en<br />

obstetricia (Fig. 2.14). En este formato, se ven mejor las dimensiones de los órganos<br />

y actualmente, como se obtienen imágenes en tiempo real, se pueden aplicar mejor<br />

a áreas como cardiología, obstetricia, ginecología, optalmología, . . . y se aceptan<br />

ampliamente como un método de obtención de imágenes aceptado y seguro.<br />

Fundamentos<br />

Hablando con precisión, ultrasonido es simplemente cualquier sonido cuya frecuencia<br />

está por encima del límite del oído humano, que normalmente se toma en<br />

20 KHz. Sin embargo, como frecuencia y longitud de onda (y por tanto resolución)<br />

están inversamente relacionados, <strong>para</strong> obtener imágenes del cuerpo se suelen usar<br />

frecuencias alrededor de 1.5 MHz, <strong>para</strong> conseguir una resolución de 1 mm.<br />

29


Figura 2.12: Configuraciones de los elementos del array y regiones escaneadas por<br />

el haz acústico. (a)Array lineal secuencial; (b) array curvilíneo; (c) array lineal de<br />

fase; (d) array 1.5D; (e) Array de fase 2D.<br />

30


Figura 2.13: Ejemplo de imagen de modo A convertido a modo M de un corazón en<br />

dos puntos del ciclo cardiaco. (a) Diástole. (b) Sístole. En la sístole las paredes son<br />

más anchas y la sección ventricular es menor.<br />

31


Figura 2.14: Representación esquemática de un corazón y como se obtiene la imagen<br />

2D a partir de la exploración del transductor.<br />

32


La atenuación de los ultrasonidos aumenta con la frecuencia en los tejidos blandos,<br />

por eso se debe llegar a un compromiso entre profundidad de penetración que<br />

se debe conseguir <strong>para</strong> una determinada aplicación y resolución obtenida. Las aplicaciones<br />

que requieren gran penetración (cardiología, obstetricia, . . . ), usan típicamente<br />

frecuencias en el rango de 2 a 5 MHz, mientras que en otras aplicaciones se<br />

requiere poca penetración, pero alta resolución (optalmología, periferia vascular, testicular,<br />

. . . ), se usan frecuencias en torno a 20 MHz. Los sistemas de representación<br />

intraarteriales, requieren resoluciones mayores y usan frecuencias de 20 a 50 MHz,<br />

y las aplicaciones de laboratorio microscópicas usan frecuencias de 100 e incluso<br />

200 MHz, <strong>para</strong> examinar las estructuras intracelulares.<br />

La distancia d desde el transductor al objeto que causó el eco, está relacionada<br />

con el tiempo t total, entre el instante de enviar el pulso y el de recibirlo, y la<br />

velocidad del sonido en ese medio c:<br />

d = 1 tc (2.14)<br />

2<br />

La velocidad del sonido en tejidos corporales blandos varía poco, de 1450 a 1520 m/s.<br />

Para aproximaciones se puede usar una velocidad de 1500 m/s, que se puede convertir<br />

a 1.5 mm/µs, una unidad más adecuada. Con esta velocidad, los tiempos necesarios<br />

<strong>para</strong> recorrer las distancias más largas (20 cm), están en torno a 270 µs. Para dejar<br />

que todos los ecos y reverberaciones desaparezcan, hay que esperar varios periodos<br />

antes de enviar el siguiente pulso, por lo que se pueden usar fecuencias en torno a<br />

un kiloHertzio.<br />

La intensidad de la señal recibida S(t) está relacionada con la señal transmitida,<br />

T (t), las propiedades del transductor, B(t), la atenuación del camino de ida y vuelta<br />

desde el dispersor, A(t), y la intensidad del dispersor, η(t):<br />

S(t) = T (t) ⊗ B(t) ⊗ A(t) ⊗ η(t) (2.15)<br />

donde ⊗ denota la convolución en el dominio del tiempo. Haciendo la transformada<br />

de Fourier, la convolución temporal se transforma en un producto en el dominio de<br />

la frecuencia<br />

Ŝ(f) = ˆT (f) ˆB(f)Â(f)ˆη(f) (2.16)<br />

Consideraciones Económicas<br />

La obtención de imágenes mediante ultrasonidos tiene ventajas económicas sobre<br />

otros métodos, como tomografía computerizada o resonancia magnética. El sistema<br />

es, típicamente, mucho más barato y no se requiere la pre<strong>para</strong>ción específica de las<br />

instalaciones, como el aislamiento en los rayos-x y tomografía, o el campo magnético<br />

uniforme necesario <strong>para</strong> resonancia magnética. La mayoría de los sistemas de ultrasonidos<br />

pueden transportarse fácilmente de un sitio a otro, de forma que puede<br />

33


ser compartido por varias salas de exploración o incluso llevarse a la habitación de<br />

algunos enfermos especialmente graves.<br />

Los gastos en cada exploración son mínimos, principalmente el gel usado <strong>para</strong><br />

acoplar el transductor a la piel del paciente y la cinta de vídeo o la película <strong>para</strong><br />

realizar la grabación. Estos costes tan bajos hacen que sea preferido sobre otros,<br />

cuando se pueda usar. El bajo costo también implica que estos sistemas puedan<br />

encontrarse en clínicas privadas y ser usados sólo ocasionalmente.<br />

Como un indicador del interés de los ultrasonidos como alternativa a otras modalidades,<br />

en 1993, en un artículo del Wall Street Journal aparecía que el gasto en EEUU<br />

en unidades de RM fue aproximadamente de $520 millones, en unidades de CT, $800<br />

millones, y en sistemas de ultrasonidos, $1000 millones, y las ventas de sistemas de<br />

ultrasonidos crecen el 15 % al año.<br />

34


Capítulo 3<br />

Visualización y Renderización<br />

3.1. Introducción<br />

La visualización 1 es un campo en el que se está trabajando mucho durante los<br />

últimos años. Los avances en software y hardware han permitido que se puedan<br />

visualizar gráficos en prácticamente todos, si no en todos, los sistemas informáticos.<br />

Incluso los ordenadores personales ofrecen, desde hace ya algún tiempo, hardware<br />

especializado <strong>para</strong> gráficos 3D. Además desde que salió Windows95 y OpenGL,<br />

también hay un API <strong>para</strong> gráficos 3D.<br />

La visualización gráfica es una forma de comunicación. Hasta no hace mucho<br />

tiempo, se usaba la capacidad de los gráficos e imágenes en 2D <strong>para</strong> transmitir<br />

información de forma mucho más eficaz, pero las imágenes 3D se usaban en muy<br />

contadas ocasiones y a menudo en sistemas muy específicos. Ahora, desde hace<br />

algún tiempo, las cosas están cambiando y las visualizaciones y animaciones de<br />

imágenes 3D están empezando a ser comunes, e incluso a reemplazar a otras formas<br />

de comunicación como las palabras, los símbolos matemáticos y las imágenes en 2D.<br />

La visualización se puede definir como el acto o proceso de interpretar en términos<br />

visuales o de poner en forma visual 2 . De forma más informal, se puede decir que<br />

es la transformación de datos o información a imágenes. La visualización está íntimamente<br />

relacionada con el principal sentido del hombre, la visión, y también con<br />

el poder de la mente humana. El resultado de todo ello es un medio simple, pero<br />

muy efectivo de comunicar infomación compleja y voluminosa.<br />

La visualización hace uso de las habilidades naturales del sistema visual humano.<br />

Nuestro sistema visual es una parte de nuestro cuerpo muy compleja y al mismo<br />

tiempo potente. Lo usamos y confiamos en él en casi todo lo que hacemos. Dado el<br />

entorno en el que vivieron nuestros antepasados, no es sorprendente que se desarrollaran<br />

ciertos sentidos <strong>para</strong> ayudarles a sobrevivir. Las representaciones visuales son<br />

1 La mayor parte de la información necesaria <strong>para</strong> realizar este capítulo ha sido obtenida de [12].<br />

2 Según el Webster’s Ninth New Collegiate Dictionary<br />

35


más fáciles de entender; no sólo tenemos grandes habilidades <strong>para</strong> ver en 2D, sino<br />

que podemos obtener una imagen mental en 3D de un objeto. Esto nos lleva a la<br />

visualización interactiva, en la que podemos manipular un objeto, el punto de vista,<br />

rotarlo, . . . , <strong>para</strong> obtener una mejor idea tridimensional del mismo.<br />

Ejemplos de Visualización<br />

La visualización está cambiando de cierta forma la vida de las personas y permitiendo<br />

cosas que hace tiempo eran inimaginables. La mejor forma de entender lo<br />

que es la visualización y lo que permite, es mediante algún ejemplo.<br />

Las técnicas <strong>para</strong> mostrar imágenes por ordenador se han convertido en una<br />

herramienta muy importante <strong>para</strong> la medicina moderna. Esto incluye técnicas<br />

como la Tomografía Computerizada, la Resonancia Magnética y los Ultrasonidos,<br />

ya explicadas en el capítulo 2. Estas técnicas usan un proceso de muestreo<br />

o de adquisición de datos, como ya se vio, <strong>para</strong> capturar la información de la<br />

anatomía interna de un paciente vivo. Esta información se encuentra en forma de<br />

imágenes que forman “rodajas” o secciones transversales del cuerpo del paciente. Estas<br />

imágenes son similares a las obtenidas con los rayos-x tradicionales. Después de<br />

obtener los datos y reconstruir estas secciones transversales mediante complicadas<br />

técnicas matemáticas, normalmente se juntas estas “rodajas”, <strong>para</strong> formar datos<br />

volumétricos y completar el estudio.<br />

Cada sección transversal, adquirida por un sistema de los vistos, está formada<br />

por un conjunto de valores que representan la atenuación de los rayos-x (CT), la<br />

relajación de la magnetización del spin del núcleo de hidrógeno (RM), o el eco producido<br />

por los tejidos (ultrasonido). Estos números se ordenan en una matriz. La<br />

cantidad de datos es muy grande, y por tanto, difícil o imposible de interpretar en esta<br />

forma. Sin embargo, si asignamos a estos valores un valor de gris y los mostramos<br />

por pantalla, aparece una estructura, antes imposible de “ver”. Esta estructura es el<br />

resultado de la interacción del sistema visual humano con la organización espacial<br />

de los datos y los valores de intensidades de gris escogidos. Lo que el ordenador representa<br />

como una serie de números, nosotros lo vemos como una sección transversal<br />

a través del cuerpo humano, formada por piel, hueso y músculo.<br />

Si extendemos estas técnicas a tres dimensiones, se consiguen resultados más<br />

efectivos aún. Las secciones transversales se juntan <strong>para</strong> formar volúmenes, y estos<br />

volúmenes se procesan <strong>para</strong> obtener estructuras anatómicas completas. Usando<br />

técnicas modernas, podemos ver el cerebro humano completo, el esqueleto, el sistema<br />

vascular, . . . de un paciente sin necesidad de intervención quirúrgica. Com<strong>para</strong>ndo<br />

por ejemplo una imagen de una radiografía con una visualización de la misma zona<br />

en 3D, se pueden ver las enormes ventajas y posibilidades de todas estas técnicas.<br />

Esta capacidad ha revolucionado los diagnósticos médicos modernos, y seguramente<br />

crezca su importancia a medida que las técnicas de visualización sigan madurando.<br />

36


Otra aplicación de la visualización es en la industria del entretenimiento.<br />

Las películas y la televisión producen gráficos por ordenador, <strong>para</strong> crear mundos<br />

que no podríamos visitar nunca de otra forma. Por ejemplo en las películas Parque<br />

Jurásico o la más moderna Bichos, están tan logrados los gráficos, que hay veces<br />

que nos llegamos a preguntar si realmente son imágenes generadas por ordenador o<br />

reales.<br />

Otra popular aplicación de la visualización a la industria del entretenimiento es<br />

el morphing. El morphing es la transformación, de forma gradual, de un objeto a<br />

otro. Una aplicaciín común es transformar una cara en otra. El morphing es muy<br />

efectivo <strong>para</strong> mostrar los cambios en un diseño, por ejemplo de un coche, de un año<br />

al siguiente.<br />

Otra aplicación, mucho más común que la anterior de la visualización, son los<br />

mapas meteorológicos que vemos todos los días en los telediarios, en los que se usan<br />

técnicas de gráficos por ordenador <strong>para</strong> obtener los isovalores o contornos usados<br />

<strong>para</strong> representar las isobaras, isotermas, etc. Incluso, alguna vez, se ha creado todo<br />

un mundo virtual <strong>para</strong> dar los partes meteorológicos.<br />

Los primeros usos de la visualización fueron en ingeniería y aplicaciones científicas.<br />

Desde que se introdujo el ordenador, se empezó a utilizar como herramienta de<br />

cálculo y simulación de procesos físicos, como por ejemplo, trayectorias en balística,<br />

movimiento de fluidos, mecánica estructural, . . . A medida que el tamaño de las simulaciones<br />

crecía, se hizo necesario transformar los resultados en gráficos e imágenes,<br />

más fáciles de interpretar por el sistema visual humano. De hecho, la visualización<br />

es tan importante, que antes de que fuera posible pr ordenador, los datos se representaban<br />

manualmente. Cualquiera que sea la tecnología existente, las aplicaciones<br />

de la visualización son las mismas, representar los resultados de simulaciones, experimentos,<br />

datos medidos y de la fantasía; y usar estas imágenes <strong>para</strong> comunicar,<br />

entender, y entretener.<br />

3.2. Procesado de Imagen, Gráficos, y Visualización<br />

Normalmente hay confusión alrededor de la diferencia entre procesado de imagen,<br />

gráficos por ordenador y visualización. Se pueden definir como:<br />

Procesado de Imagen. Es el estudio de imágenes en 2D. Incluye técnicas de<br />

transformación (como rotar, escalar, . . . ), extracción de información, análisis<br />

y realce de imágenes.<br />

Gráficos por Ordenador o renderización. Es el proceso de creación de imágenes<br />

usando un ordenador. Esto incluye, tanto las técnicas de dibujo “dibujo y pintura”<br />

en 2D, como las técnicas más sofisticadas de dibujo (o renderización) en<br />

3D.<br />

37


Visualización. Es el proceso de exploración, transformación, y visión de datos en<br />

forma de imágenes, <strong>para</strong> ganar en comprensión sobre los datos.<br />

A partir de estas definiciones, podemos ver que hay un solapamiento entre estos<br />

tres campos. La salida de los gráficos por ordenador o renderización es una imagen,<br />

mientras que la salida de visualización, a menudo es producida por gráficos<br />

por ordenador. Algunas veces, los datos de visualización están en forma de imagen,<br />

o queremos visualizar la geometría de un objeto usando técnicas realistas de<br />

renderización de gráficos por ordenador.<br />

En general, distinguimos la visualización de los gráficos por ordenador y del<br />

procesado de imagen de tres formas.<br />

1. La dimensionalidad de los datos es 3 o más. Muchos métodos se pueden usar<br />

también <strong>para</strong> datos de 2 dimensiones o menos; sin embargo, la visualización<br />

sirve mejor cuando se aplica a datos de más dimensiones.<br />

2. La visualización está relacionada con la transformación de los datos. La información<br />

se crea y modifica repetidamente <strong>para</strong> mejorar el significado de los<br />

datos.<br />

3. La visualización es por naturaleza interactiva, incluyendo al ser humano directamente<br />

en el proceso de crear, modificar, transformar y ver los datos.<br />

Otra forma de verlo es que la visualización es una actividad que comprende los<br />

procesos de exploración y entendimiento de los datos. Esto incluye al procesado de<br />

imagen y a los gráficos por ordenador, así como el tratamiento y filtrado de los datos,<br />

la realización del interfaz de usuario, y el diseño de software.<br />

Como se puede ver en la figura 3.1, el proceso de visualización se centra en los<br />

datos. En el primer paso, los datos se adquieren de alguna fuente. A continuación,<br />

los datos se transforman de varias formas, y después se mapean <strong>para</strong> obtener una<br />

representación adecuada <strong>para</strong> el usuario. Finalmente, los datos son renderizados o<br />

mostrados por pantalla, completando el proceso. A menudo, el proceso se repite a<br />

medida que los datos se entienden mejor o se desarrollan nuevos métodos. A veces,<br />

los resultados de la visualización pueden controlar directamente la generación de<br />

los datos. A esto se le suele llamar control del análisis y es importante <strong>para</strong> la<br />

visualización, pues mejora la interactividad de todo el proceso.<br />

3.3. Renderización<br />

3.3.1. Introducción<br />

Los gráficos por ordenador o renderización son el fundamento de la visualización.<br />

En la práctica, se puede decir que la visualización es el proceso de transformar los<br />

38


Metodos Computacionales<br />

- elementos finitos<br />

- diferencias finitas<br />

- analisis numerico<br />

Datos Medidos<br />

- TC, RM, ultrasonido<br />

- satelite<br />

- digitalizador laser<br />

- stocks / financiero<br />

Datos<br />

Transformar<br />

Mapear<br />

Mostrar<br />

Figura 3.1: El proceso de visualización. Los datos de varias fuentes se transforman<br />

repetidamente <strong>para</strong> obtener, derivar y resaltar la información. Los datos resultantes<br />

se mapean al sistema gráfico.<br />

datos en una serie de primitivas gráficas. A continuación se usan los métodos de<br />

renderización <strong>para</strong> convertir estas primitivas en imágenes y animaciones.<br />

La renderización es el proceso de generación de imágenes mediante un ordenador,<br />

o el proceso de conversión de datos gráficos a una imagen. Hay muchos<br />

procesos de renderización, que varían desde los programas de dibujo en 2D hasta las<br />

técnicas más sofisticadas en 3D. En este apartado me fijaré en las técnicas básicas<br />

de visualización.<br />

En la visualización de datos, el objetivo es transformar los datos a datos gráficos,<br />

o primitivas gráficas, que son renderizadas a continuación. El objetivo de la<br />

renderización, no es tanto el realismo conseguido, como el contenido de información,<br />

aunque esto depende, claro está, de la aplicación (en una película, tiene más importancia<br />

el realismo que el contenido de información). También es interesante conseguir<br />

representaciones interactivas, en las que podamos interactuar con los datos.<br />

En este apartado vamos a tratar como interactúan las luces, cámaras, y objetos<br />

(actores) con el mundo que nos rodea, <strong>para</strong> después ver como simulamos este proceso<br />

en un ordenador.<br />

3.3.2. Fundamentos<br />

Descripción física de la renderización<br />

Vamos a ver lo que ocurre cuando miramos un objeto. La fuente de luz (supongamos<br />

que es el sol), emite rayos de luz en todas las direcciones. Algunos de los rayos<br />

inciden sobre el objeto que estamos mirando; su superficie absorbe parte de la luz<br />

39


incidente (según su color, brillo, transparencia, . . . ) y refleja el resto. Parte de esta<br />

luz reflejada, puede incidir sobre nuestros ojos. Si esto ocurre, vemos el objeto.<br />

Como es fácil de imaginar, la probabilidad de que un rayo de luz procedente del<br />

sol, incida sobre un pequéno objeto, en un pequeño planeta como el nuestro y que<br />

el rayo reflejado incida sobre nuestros pequeños ojos, es extremadamente pequeña.<br />

La única razón por la que podemos ver es, debido a la enorme cantidad de luz que<br />

produce el sol. Esto funciona en el mundo físico, pero intentar simularlo con un<br />

ordenador puede ser muy difícil. Afortunadamente, hay otras formas de afrontar el<br />

problema.<br />

Una técnica común y efectiva de renderización tridimensional se llama raytracing<br />

(trazado de rayos) o ray-casting. La idea básica es simular la interacción<br />

de la luz con los objetos, siguiendo el camino de cada rayo de luz. Normalmente se<br />

sigue al rayo en sentido contrario, desde el ojo hacia el mundo <strong>para</strong> determinar todos<br />

los puntos donde el rayo incide, y por tanto el ojo puede ver. La dirección del rayo<br />

es la dirección en la que el ojo está mirando (dirección de vista) incluyendo efectos<br />

de perspectiva (si se desea). Si el rayo intersecta con un objeto, podemos ver si ese<br />

punto está siendo iluminado por la fuente de luz. Esto se hace trazando un rayo<br />

desde el punto de intersección hacia la fuente de luz. Si el rayo intersecta con la luz,<br />

entonces el punto está iluminado; si intersecta con algo antes de la luz, entonces la<br />

luz no contribuye a iluminar el punto. Si tenemos más de ua fuente de luz, repetimos<br />

este proceso <strong>para</strong> cada una. La contribución de todas las fuentes de luz, además de<br />

la luz de ambiente dispersa, determina la iluminación o sombra de cada punto. El<br />

ray-casting sólo sigue los caminos hacia atrás que terminan incidiendo en el ojo, lo<br />

cual reduce drasticamente el número de rayos que se deben simular.<br />

A pesar de que el método de ray-casting es el método de renderización más obvio,<br />

no se usa muy a menudo. Esto no es debido a la bondad del método en sí, sino a que<br />

es un método bastante lento, a causa de que se suele implementar mediante software.<br />

Se han desarrollado otras técnicas de renderización que usan hardware dedicado.<br />

Métodos de Orden de Imagen y Orden de Objeto<br />

Los procesos de renderización se pueden dividir en dos categorías, según el orden<br />

en que se realiza el proceso de renderización:<br />

Métodos que siguen el Orden de Imagen. Funcionan determinando lo<br />

que le ocurre a cada rayo de luz, uno en cada momento. El ray-casting<br />

pertenece a este tipo de métodos. Se empieza el algoritmo por una esquina y<br />

se sigue en orden, primero todos los pixels de la fila; cuando se termina, se<br />

procede de la misma forma con los de la fila siguiente, y así sucesivamente.<br />

Una vez que se llega a la esquina opuesta, se termina.<br />

40


Métodos que siguen el Orden de Objeto. Funcionan renderizando cada<br />

objeto, uno de cada vez (por ejemplo, el fondo, una silla, una mesa, . . . ), independientemente<br />

de la posición en la que se encuentren en la escena. Podemos<br />

hacerlo de atrás hacia delate, de delante hacia atrás, o en un orden arbitrario.<br />

A estos métodos se les suele llamar poligonales, pues renderizan polígonos. La<br />

forma de realizar este tipo de renderización se explicará en el apartado 3.3.10.<br />

Para obtener los polígonos que se renderizan, se usan métodos, como Marching<br />

Cubes, explicado en el apartado 3.4, empleado <strong>para</strong> la realización del<br />

proyecto.<br />

Cuando se empezaron a representar gráficos por ordenador, se usaron los métodos<br />

que siguen el orden de los objetos. Inicialmente se usaban monitores vectorizados<br />

(poco más que osciloscopios), en los que los gráficos se representaban como segmentos<br />

de línea. Cuando se empezaron a usar los monitores como los que usamos hoy<br />

en día, se siguió usando la representación de gráficos como una serie de objetos.<br />

Desde entonces, el hardware se ha hecho mucho más potente y capaz de representar<br />

primitivas gráficas, mucho más complejas que simples líneas.<br />

A principios de los años 80, se empezó a ver la renderización desde una perspectiva<br />

mucho más física y el ray-casting se convirtió en un serio competidor de las<br />

técnicas de renderización tradicionales, debido en parte a las imágenes tan realistas<br />

que produce. La renderización por objetos ha mantenido su popularidad debido al<br />

hardware de gráficos diseñado <strong>para</strong> renderizar objetos de forma muy rápida. El raycasting<br />

se suele seguir haciendo sin hardware específico, y por tanto, consumiendo<br />

mucho más tiempo.<br />

Renderización de superficie frente a Renderización de Volumen<br />

Hasta ahora, hemos asumido tácitamente, que cuando vemos un objeto, lo que<br />

vemos es su superficie exterior y su interacción con la luz. Sin embargo, muchos objetos,<br />

como nubes, agua y niebla, son translúcidos, o dispersan la luz que pasa a través<br />

de ellos. Tales objetos no pueden ser renderizados usando métodos que tengan únicamente<br />

en cuenta las interacciones con la superficie. Para ello tenemos que considerar<br />

los cambios de propiedades en su interior, <strong>para</strong> renderizarlos de forma adecuada.<br />

Estos dos tipos de renderización se llaman respectivamente: renderización de superficie<br />

(renderizamos la superficie del objeto), y renderización de volumen<br />

(renderizamos la superficie y el interior del objeto).<br />

Cuando renderizamos un objeto con técnicas de renderización de superficie,<br />

modelamos el objeto con la descripción de su superficie, mediante puntos, líneas,<br />

triángulos, polígonos, . . . , mientras que no describimos su interior. Aunque hay<br />

técnicas que permiten hacer la superficie transparente o translúcida, hay muchos<br />

fenómenos que no se pueden simular, sólo renderizando las superficies. Por ejemplo,<br />

puede interesar <strong>para</strong> ver los datos interiores en una tomografía computerizada.<br />

41


La renderización de volumen nos permite ver las inhomogeneidades del interior<br />

de los objetos. En el ejemplo anterior de la TC, podemos ver, tanto la superficie,<br />

como el interior de los datos. Se puede extender el método explicado de ray-casting,<br />

de forma que los rayos no sólo interactúen con la superficie del objeto, sino que<br />

también lo hagan con su interior.<br />

Para la realización del proyecto he usado técnicas de renderización de superficie,<br />

que aunque no son tan potentes como las de volumen, son mucho más rápidas y<br />

permiten interactuar mejor con los datos. Son las más usadas, en general.<br />

3.3.3. Color<br />

El espectro electromagnético visible, <strong>para</strong> el ser humano, contiene longitudes<br />

de onda entre 400 y 700 nanómetros. La luz que entra en nuestros ojos (y que<br />

podemos ver) está compuesta por diferentes intensidades de estas longitudes de<br />

onda. Diferentes distribuciones de intensidad producen colores distintos. El sistema<br />

visual humano no hace uso de toda la información contenida en esta distribución<br />

de intensidades, sino que desaprovecha la mayor parte de la información. En el ojo<br />

tenemos tres tipos de receptores de color, o conos. Cada tipo es sensible a una zona<br />

del espectro entre 400 y 700 nm. Los tres conos tienen sensibilidades con forma más<br />

o menos gaussiana, centradas respectivamente en 419 nm <strong>para</strong> el azul, 531 nm <strong>para</strong><br />

el verde y 559 nm <strong>para</strong> el rojo.<br />

Cualquier color de los que vemos, es codificado por nuestros ojos con estas tres<br />

respuestas, parcialmente superpuestas. Esto supone una gran reducción de la información<br />

que realmente llega a nuestros ojos. Como resultado, el ojo humano es<br />

incapaz de reconocer diferencias entre colores que produzcan las mismas respuestas<br />

en los receptores del ojo. Esta aparente desventaja, tiene ventajas a la hora de representar<br />

los colores en un ordenador; podemos representar los colores en un ordenador<br />

de forma simplificada, sin que el ojo humano distinga la diferencia.<br />

Los dos sistemas más usuales de describir los colores son el RGB y el HSV. El<br />

sistema RGB representa los colores basándose en las intensidades de rojo, verde y<br />

azul. Se puede ver como un espacio tridimensional, siendo los ejes son rojo, verde y<br />

azul. En la tabla 3.1 se muestran los valores RGB de algunos colores comunes.<br />

El sistema HSV se basa en el matiz, saturación y valor del color. El valor se<br />

conoce también como brillo o intensidad, y representa la cantidad de luz que tiene<br />

el color. Un valor de 0.0 siempre produce color negro, y un valor de 1.0 siempre<br />

produce algo brillante. El matiz representa la longitud de onda dominante del color.<br />

El matiz se representa normalmente en un círculo, como se muestra en la Fig. 3.2.<br />

Cada posición en la circunferencia representa un matiz distinto, y se puede especificar<br />

con un ángulo. El matiz varía entre 0.0 y 1.0, donde 0.0 corresponde a 0 o y 1.0 a 360 o .<br />

La saturación indica cuánto del matiz se mezcla con el color. Por ejemplo, poniendo<br />

el valor a 1.0, obtenemos un color brillante; ponemos el tono a 0.66 <strong>para</strong> obtener<br />

42


Cuadro 3.1: Colores comunes en los espacios RGB y HSV.<br />

Color. RGB HSV<br />

Negro 0,0,0 *,*,0<br />

Blanco 1,1,1 *,0,1<br />

Rojo 1,0,0 0,1,1<br />

Verde 0,1,0 1/3,1,1<br />

Azul 0,0,1 2/3,1,1<br />

Amarillo 1,1,0 1/6,1,1<br />

Cyan 0,1,1 1/2,1,1<br />

Magenta 1,0,1 5/6,1,1<br />

Azul cielo 1/2,1/2,1 2/3,1/2,1<br />

una longitud de onda dominante de azul. Si la saturación vale 1.0, obtenemos un<br />

azul primario brillante; si vale 0.5, obtenemos azul cielo (más blanco mezclado); si<br />

vale 0.0, indica que no hay más de ese color que de cualquier otra longitud de onda,<br />

con lo que obtenemos blanco, independientemente del valor del matiz. En la tabla<br />

3.1 se muestran los valores HSV de algunos colores comunes.<br />

3.3.4. Luces<br />

Uno de los factores más importante que controlan el proceso de renderización<br />

es la interacción de la luz con los actores de la escena. Si no hay luces, la imagen<br />

Verde 120<br />

Amarillo 60<br />

Cyan 180<br />

Rojo 0<br />

Azul 240<br />

Magenta 300<br />

Figura 3.2: Representación circular del matiz.<br />

43


esultante será negra y no ofrecerá información. En gran medida, lo que define lo<br />

que vemos es la interacción de la luz emitida con la superficie (y en algunos casos<br />

con el interior) de los actores en la escena. Cuando la luz incide sobre los actores,<br />

entonces podemos ver algo a través de la cámara.<br />

De los muchos tipos de luces que se pueden usar en renderización, usamos el tipo<br />

más sencillo: fuente de luz puntual y a distancia infinita. Esto es una simplificación<br />

com<strong>para</strong>do con las luces que usamos, por ejemplo, en casa. Las fuentes<br />

de luz a las que estamos acostumbrados radian desde una región del espacio (por<br />

ejemplo, un filamento). El modelo puntual de luz, asume que la luz es emitida en<br />

todas las direcciones, desde un punto del espacio. Para una fuente infinita, asumimos<br />

que está colocada a una distancia infinita de lo que está iluminando. Esto implica<br />

que los rayos son <strong>para</strong>lelos, mientras que los rayos emitidos por una fuente local<br />

no son <strong>para</strong>lelos. La intensidad de luz emitida por una fuente de luz infinita, permanece<br />

constante con la distancia a la fuente, mientras que en una fuente local, varía<br />

según 1/distancia 2 . Esta gran simplificación permite obtener ecuaciones mucho más<br />

sencillas <strong>para</strong> la iluminación.<br />

3.3.5. Propiedades de la Superficie<br />

A medida que los rayos de luz viajan por el espacio, algunos de ellos intersectan<br />

con los actores. Cuando esto ocurre, los rayos de luz interactúan con la superficie<br />

del actor <strong>para</strong> producir un color. Parte del color resultante no es debido realmente<br />

a la luz directa, sino que también es debido a la iluminación de ambiente, que se<br />

refleja o dispersa desde otros objetos. Esto se tiene en cuenta mediante un modelo<br />

de iluminación de ambiente. Este modelo aplica la curva de intensidad de luz de la<br />

fuente luminosa al color del objeto, que también se puede expresar como una curva<br />

de intensidad, como ya vimos. El resultado es el color de la luz que vemos cuando<br />

miramos al objeto. Con este modelo es importante darse cuenta de que no se puede<br />

distinguir, por ejemplo, una fuente de luz azul que ilumina un objeto blanco, de una<br />

fuente de luz blanca que ilumina un objeto azul. La ecuación de la iluminación de<br />

ambiente es<br />

R c = L c · O c (3.1)<br />

donde R c es la curva de intensidad resultante, L c la curva de intensidad de la luz, y<br />

O c la curva de color del objeto. Para simplificar las ecuaciones suponemos que todos<br />

los vectores de dirección están normalizados.<br />

Dos componentes del color resultante dependen de la iluminación directa: La<br />

iluminación difusa y la iluminación especular.<br />

La iluminación difusa, también conocida como reflexión Lambertiana, tiene<br />

en cuenta el ángulo de incidencia de la luz en el objeto. Por ejemplo, si una fuente<br />

de luz incide sobre la superficie lateral de un cilindro, desde nuestra posición, su<br />

superficie se ve más clara en el centro, y más oscura a medida que nos alejamos<br />

44


Luz<br />

Ln<br />

angulo<br />

On<br />

-Ln<br />

Objeto<br />

Figura 3.3: Iluminación difusa.<br />

lateralmente de él. El color de toda la superficie es el mismo, pero la luz que incide<br />

sobre la superficie del cilindro cambia. En el centro, donde la luz incidente es casi<br />

perpendicular a la superficie, recibe más cantidad de rayos de luz por unidad de<br />

área. Al movernos hacia los laterales, esta cantidad disminuye, hasta que la luz<br />

incidente es <strong>para</strong>lela a la superficie del cilindro y la intensidad reultante es cero. La<br />

contribución de la iluminación difusa se muestra en la Fig. 3.3 y se expresa mediante<br />

la siguiente ecuación<br />

R c = L c O c [ ⃗ O n · (− ⃗ L n )] (3.2)<br />

donde R c es la curva de intensidad resultante, L c la curva de intensidad de la luz,<br />

y O c la curva de color del objeto. La luz difusa, como se dijo, es función del ángulo<br />

entre el vector de la luz incidente, ⃗ L n y la normal a la superficie ⃗ O n . Por tanto, esta<br />

luz es dependiente del punto de vista.<br />

La iluminación especular representa las reflexiones directas de una fuente de<br />

luz desde un objeto brillante. La potencia especular, O sp , indica lo brillante que es<br />

un objeto. De forma más concreta, indica la rapidez con que disminuye la reflexión<br />

especular a medida que el ángulo de reflexión se desvía de la reflexión perfecta.<br />

Valores mayores indican una caída más rápida, y por tanto, una superficie más<br />

brillante. La Fig. 3.5 muestra el efecto de la potencia especular. Refiriéndome a la<br />

Fig. 3.4, la ecuación <strong>para</strong> la iluminación especular es<br />

R c = L c O c [ ⃗ S · (− ⃗ C n )] Osp<br />

⃗S = 2[ ⃗ O n · (− ⃗ L n )] ⃗ O n + ⃗ L n<br />

(3.3)<br />

donde ⃗ C n es la dirección de proyección <strong>para</strong> la cámara y ⃗ S es la dirección de la<br />

reflexión especular.<br />

Hemos representado las ecuaciones <strong>para</strong> los diferentes modelos de iluminación<br />

de forma independiente. Podemos aplicar los modelos de iluminación de forma si-<br />

45


Luz<br />

Ln<br />

angulo<br />

On<br />

-Ln<br />

S<br />

-Cn<br />

Cn<br />

Camara<br />

Objeto<br />

Figura 3.4: Iluminación especular.<br />

Figura 3.5: Bola iluminada con luz difusa, con reflexión especular en aumento.<br />

multánea, como muestra la siguiente ecuación, que combina la iluminación de ambiente,<br />

difusa y especular.<br />

R c = O ai O ac L c − O di O cd L c ( ⃗ O n · ⃗L n ) + O si OscL c [ ⃗ S · (− ⃗ C n )] Osp (3.4)<br />

El resultado es el color en un punto de la superficie del objeto. Las constantes<br />

O ai , O di y O si controlan las cantidades relativas de iluminación de ambiente, difusa y<br />

especular <strong>para</strong> un objeto. Las constantes O ac , O dc y O sc especifican los colores usados<br />

<strong>para</strong> cada tipo de iluminación. Estas seis constantes, junto con la potencia especular,<br />

son parte de las propiedades de la superficie del material (hay otras propiedades,<br />

como transparencia). Diferentes combinaciones de estas propiedades, pueden simular<br />

desde plástico a metal pulido. Esta ecuación supone fuente de luz puntual e infinita,<br />

pero se puede extender de forma fácil a otros tipos de iluminación.<br />

3.3.6. Cámaras<br />

Tenemos ya rayos de luz emitidos por las fuentes luminosas y actores con propiedades<br />

en sus superficies. En cada punto de la superficie de cada actor en la escena tenemos<br />

la interacción con la luz, lo cual produce un color de composición (color producto<br />

de la combinación de la luz, la superficie del objeto, el efcto especular y el efecto<br />

de ambiente). Todo lo que necesitamos ahora <strong>para</strong> renderizar una escena es una<br />

cámara. Hay un conjunto importante de factores que determinan cómo se proyecta<br />

46


Vista Superior<br />

Posicion<br />

Angulo<br />

de Vista<br />

Punto Focal<br />

Direccion<br />

de Proyeccion<br />

Angulo de Corte Delantero<br />

Angulo de Corte Trasero<br />

Figura 3.6: Atributos de la cámara.<br />

la escena 3D sobre un plano <strong>para</strong> formar la imagen 2D (ver Fig. 3.6). Estos factores<br />

son la posición, orientación y punto focal de la cámara, el método usado <strong>para</strong> la<br />

proyección de la cámara y la localización de los planos de corte.<br />

La posición y el punto focal de la cámara definen la posición de la cámara y<br />

el punto al que enfoca. El vector definido desde la posición de la cámara al punto<br />

focal se llama dirección de proyección. El plano de imagen de la cámara se<br />

sitúa en el punto focal, y es, normalmente, perpendicular al vector de proyección. La<br />

orientación de la cámara se controla mediante su posición y punto focal, además<br />

del vector que indica la vista superior de la cámara.<br />

El método de proyección controla cómo se “mapean” los actores en el plano<br />

de la imagen. En proyección ortográfica o <strong>para</strong>lela, todos los rayos de luz que entran<br />

en la cámara son <strong>para</strong>lelos al vector de proyección. La proyección en perspectiva<br />

ocurre cuando todos los rayos de luz pasan por un punto (punto de vista o centro<br />

de proyección). Para aplicar una proyección en perspectiva, tenemos que especificar<br />

un ángulo de perspectiva o ángulo de vista de la cámara.<br />

Los planos de corte delantero y trasero cortan al vector de proyección, y<br />

normalmente son perpendiculares a él. Se usan <strong>para</strong> eliminar datos que están, o bien,<br />

muy cerca de la cámara, o bien, muy lejos. Como resultado de ello sólo los actores<br />

o porciones de actores que están entre los dos planos de corte pueden ser visibles.<br />

Normalmente estos planos son perpendiculares a la dirección de proyección.<br />

Todos juntos, los parámetros de la cámara definen una pirámide rectangualr, con<br />

el vértice en la cámara y que se extiende a lo largo de la dirección de proyección.<br />

Esta pirámide esta truncada por los dos planos de corte. El tronco de pirámide<br />

de vista define la región del espacio tridimensional visible por la cámara.<br />

47


Los parámetros de la cámara se pueden manipular directamente, pero hay operaciones<br />

comunes que hacen el trabajo más sencillo. Si se cambia el azimut de<br />

una cámara, gira su posición alrededor de su vector de vista superior, centrado en<br />

el punto focal. Esta operación se puede ver como un movimiento de la cámara a la<br />

derecha o izquierda, manteniendo la distancia al punto focal constante. Cambiando<br />

la elevación de la cámara, gira su posición alrededor del producto vectorial de su<br />

dirección de proyección y vista superior, centrada en el punto focal. Esto corresponde<br />

a mover la cámara arriba y abajo. Para girar la cámara, giramos el vector de vista<br />

vertical sobre el vector de plano normal.<br />

Los siguientes dos movimientos mantienen la posición de la cámara constante<br />

y modifican el punto focal. Cambiando la guiñada, gira el punto focal sobre la<br />

vista superior de la cámara, centrada en la posición de la cámara. Es como un<br />

movimiento de azimut, pero moviendo el punto focal en lugar de la posición de la<br />

cámara. El movimiento de balanceo, gira el punto focal sobre el producto vectorial<br />

de la dirección de proyección y la vista superior, centrada en la posición de la cámara.<br />

Los movimientos de dollying in y dollying out mueven la posición de la cámara<br />

a lo largo de la dirección de proyección, acercándose o alejándose del punto focal.<br />

Finalmente el zoom in y zoom out cambian el ángulo de vista de la cámara, de<br />

forma que en el tronco de pirámide de vista entra más o menos de la escena.<br />

Una vez que está situada la cámara, podemos generar la imagen en 2D. Algunos<br />

de los rayos que viajan por el espacio tridimensional, pasan a través de las lentes de<br />

la cámara. Estos rayos inciden sobre una superficie plana <strong>para</strong> producir una imagen.<br />

Este proceso produce la imagen 2D de nuestra escena 3D. La posición de la cámara y<br />

otras propiedades, determinan qué rayos de luz son capturados y proyectados. Sólo<br />

los rayos de luz que intersectan con la posición de la cámara, y están dentro del<br />

tronco de pirámide de vista, afectarán a la imagen resultante.<br />

3.3.7. Sistemas de Coordenadas<br />

En renderización se utilizan cuatro sistemas de coordenadas y dos formas distintas<br />

de representar puntos en los mismos. Aunque esto pueda parecer en principio<br />

excesivo, cada uno tiene su propósito. Los cuatro sistemas de coordenadas son: modelo,<br />

mundo, vista y pantalla.<br />

Sistema de coordenadas del modelo. Es el sistema de coordenadas en el que<br />

está definido el modelo, típicamente un sistema local de coordenadas Cartesiano.<br />

Si uno de los actores tiene una forma geométrica más adaptada a otros<br />

sistemas de coordenadas, como cilíndrico o esférico, estará basado en ellas.<br />

Esto depende del que genere el modelo.<br />

Sistema de coordenadas del mundo. Es el espacio 3D en el que se colocan los<br />

actores. Uno de las responsabilidades del actor es convertir, desde las coordenadas<br />

del modelo, a las coordenadas del mundo. Cada modelo puede tener<br />

48


su propio sistema de coordenadas, pero sólo hay un sistema de coordenadas<br />

del mundo. Para cada actor se debe escalar, rotar y trasladar su modelo al<br />

sistema de coordenadas del mundo. También puede ser necesario transformar<br />

sus sistema natural de coordenadas a un sistema de coordenadas local Cartesiano.<br />

Esto es debido a que los actores típicamente asumen que el sistema de<br />

coordenadas del modelo es un sistema de coordenadas Cartesiano. El sistema<br />

de coordenadas del mundo es, además, el sistema en el que se especifica la<br />

posición y orientación de cámaras y luces.<br />

Sistema de coordenadas de vista. Representa lo que es visible a la cámara.<br />

Consiste en un par de valores x e y, que varían entre (-1,1), y una coordenada<br />

z de profunidad. Los valores x, y especifican una localización en el plano<br />

de la imagen, mientras que la coordenada z representa la distancia desde la<br />

cámara. Las propiedades de la cámara se representan por una matriz de<br />

transformación 4 × 4, que se usa <strong>para</strong> convertir de las coordenadas del mundo<br />

a coordenadas de vista. En este punto es donde se introducen los efectos<br />

de perspectiva de la cámara.<br />

Sistema de coordenadas de la pantalla. Usa la misma base que el sistema de<br />

coordenadas de vista, pero en lugar de tener un rango de variación de -1<br />

a 1, las coordenadas son localizaciones reales de pixels x, y en el plano de la<br />

imagen. Factores tales como el tamaño de la ventana en la pantalla, determinan<br />

como se mapea el rango (-1,1) a las posiciones de los pixels. En este punto se<br />

pueden introducir los puertos de vista. Se puede querer renderizar dos escenas<br />

diferentes en la misma ventana. Esto se puede hacer dividiendo la ventana en<br />

dos puertos de vista. A cada renderizador se le dice que porción de la ventana<br />

debe usar <strong>para</strong> renderizar. el puerto de vista varía entre (0,1) en ambos ejes x<br />

e y.<br />

De forma similar al sistema de coordenadas de vista, el valor z del sistema de<br />

coordenadas de la pantalla también representa la profundidad en la ventana.<br />

El significado del valor z se explicará más adelante.<br />

3.3.8. Transformación de Coordenadas<br />

Cuando creamos imágenes renderizadas, proyectamos los objetos definidos en 3D<br />

a un plano de imagen en 2D. Como se ha visto anteriormente, este proceso incluye,<br />

en general, perspectiva. Para incluir los efectos de proyeccción, tales como puntos de<br />

desvanecimiento, se usa un sistema de coordenadas especial, llamado coordenadas<br />

homogéneas.<br />

La forma usual de representar un punto en 3D es mediante el vector Cartesiano<br />

de tres elementos (x, y, z). Las coordenadas homogéneas se representan mediante un<br />

49


vector de cuatro elementos (x h , y h , z h , w h ). La conversión entre coordenadas Cartesianas<br />

y homogéneas esta dada por:<br />

x = x h<br />

w h<br />

y = y h<br />

w h<br />

z = z h<br />

w h<br />

(3.5)<br />

Usando coordenadas homogéneas, se puede representar un punto infinito, poniendo<br />

w h a cero. Esto lo usa la cámara <strong>para</strong> transformaciones de perspectiva. Las transformaciones<br />

se aplican usando una matriz de transformación 4 × 4. Las matrices<br />

de transformación se usan mucho en renderización, ya que permiten operaciones de<br />

traslación, escalado y rotación de los objetos, mediante multiplicaciones sucesivas de<br />

matrices. tales operaciones no se pueden hacer de forma sencilla mediante matrices<br />

3 × 3.<br />

Por ejemplo, si queremos crear una matriz de transformación <strong>para</strong> trasladar<br />

un punto (x, y, z) del espacio Cartesiano, por el vector (t x , t y , t z ). Sólo tenemos que<br />

construir la matriz de transformación dada por<br />

⎛<br />

T T = ⎜<br />

⎝<br />

1 0 0 t x<br />

0 1 0 t y<br />

0 0 1 t z<br />

0 0 0 1<br />

⎞<br />

⎟<br />

⎠<br />

(3.6)<br />

y a continuación postmultiplicarla por el vector con las coordenadas homogéneas<br />

(x h , y h , z h , w h ). Para ello, construimos el sistema de coordenadas homogéneo a partir<br />

del sistema de coordenadas Cartesiano (x, y, z), poniendo w h = 1, <strong>para</strong> obtener<br />

(x, y, z, 1). Para determinar las coordenadas trasladadas (x , , y , , z , ), premultiplicamos<br />

la posición actual por la matriz de transformación T T , <strong>para</strong> obtener las coordenadas<br />

transformadas. Sustituyendo en la Ec. (3.6), obtenemos<br />

⎛<br />

x , ⎞ ⎛<br />

y ,<br />

⎜<br />

⎝ z , ⎟<br />

⎠ = ⎜<br />

⎝<br />

w ,<br />

1 0 0 t x<br />

0 1 0 t y<br />

0 0 1 t z<br />

0 0 0 1<br />

⎞<br />

⎛<br />

⎟<br />

⎠ ·<br />

⎜<br />

⎝<br />

x<br />

y<br />

z<br />

1<br />

⎞<br />

⎟<br />

⎠<br />

(3.7)<br />

Convirtiendo de nuevo a coordenadas Cartesianas con la Ec. (3.5), obtenemos la<br />

solución esperada<br />

x , = x + t x<br />

y , = y + t y<br />

(3.8)<br />

z , = z + t z<br />

El mismo procedimiento se usa <strong>para</strong> escalar y rotar. Para escalar un objeto, usamos<br />

la matriz de trasnformación<br />

⎛<br />

⎞<br />

s x 0 0 0<br />

0 s<br />

T S =<br />

y 0 0<br />

⎜<br />

⎟<br />

(3.9)<br />

⎝ 0 0 s z 0 ⎠<br />

0 0 0 1<br />

50


donde los parámetros s x , s y y s z , son los factores de escala en cada eje.<br />

De forma similar, se puede rotar un objeto alrededor del eje x con la matriz<br />

alrededor del eje y<br />

y alrededor del eje z<br />

⎛<br />

T Rx = ⎜<br />

⎝<br />

⎛<br />

T Ry = ⎜<br />

⎝<br />

⎛<br />

T Rz = ⎜<br />

⎝<br />

1 0 0 0<br />

0 cos θ − sin θ 0<br />

0 sin θ cos θ 0<br />

0 0 0 1<br />

cos θ 0 sin θ 0<br />

0 1 0 0<br />

− sin θ 0 cos θ 0<br />

0 0 0 1<br />

cos θ − sin θ 0 0<br />

sin θ cos θ 0 0<br />

0 0 1 0<br />

0 0 0 1<br />

⎞<br />

⎟<br />

⎠<br />

⎞<br />

⎟<br />

⎠<br />

⎞<br />

⎟<br />

⎠<br />

(3.10)<br />

(3.11)<br />

(3.12)<br />

Las rotaciones ocurren alrededor del origen de coordenadas. A menudo es más conveniente<br />

rotar alrededor del centro del objeto, u otro punto cualquiera. Si llamamos<br />

a este punto O c , <strong>para</strong> rotar alrededor de él, debemos trasladar, en primer lugar, el<br />

objeto desde O c al origen, aplicar las rotaciones, y después trasladar el objeto de<br />

nuevo a O c .<br />

Las matrices de transfomación se pueden combinar multiplicando matrices, <strong>para</strong><br />

conseguir las combinaciones de traslaciones, rotaciones y escalados. Una sola matriz<br />

puede representar todos los tipos de transformaciones al mismo tiempo. Esta<br />

matriz es el resultado de repetidas multiplicaciones de matrices. El orden de multiplicación<br />

es importante. Por ejemplo, multiplicar una matriz de traslación por<br />

una matriz de rotación, produce distinto resultado que multiplicar una matriz de<br />

rotación por una de traslación.<br />

3.3.9. Geometría de los Actores<br />

Hemos visto como las propiedades de iluminación controlan la aparienciade un<br />

actor, y como la cámara, junto con las matrices de transformación se usan <strong>para</strong><br />

proyectar un actor al plano de la imagen. Nos queda definir la geometría del actor,<br />

y cómo lo colocamos en el sistema de coordenadas del mundo.<br />

Modelado<br />

Un elemento importante en el estudio de los gráficos por ordenador es el<br />

modelado o representación de la geometría de los objetos físicos. Se aplican varias<br />

51


técnicas matemáticas, como combinaciones de puntos, líneas, polígonos, curvas y<br />

splines de varias formas, e incluso funciones matemáticas implícitas. Esto queda<br />

fuera de mi objetivo, pues no lo uso <strong>para</strong> el proyecto. Lo importante es que hay<br />

un modelo geométrico que especifica la posición en la que un objeto se coloca en el<br />

sistema de coordenadas del modelo.<br />

En visualización de datos, como es mi caso, el modelado tiene un papel distinto.<br />

En lugar de generar directamente la geometría <strong>para</strong> representar un objeto,<br />

los algoritmos de visualización calculan estas formas. A menudo, la geometría es<br />

abstracta (como una isosuperficie) y tiene poca relación con la geometría del mundo<br />

real.<br />

La representación de geometría <strong>para</strong> visualización de datos suele ser sencilla,<br />

incluso aunque calcular las representaciones no lo sea. Lo más normal es que estas<br />

primitivas sean puntos, líneas, y polígonos, o datos de visualización, como datos de<br />

volumen. Usamos formas sencillas ya que deseamos un funcionamiento rápido y un<br />

sistema interactivo. Por tanto, usamos técnicas que aprovechan el hardware <strong>para</strong> la<br />

renderización o técnicas especiales como renderización de volumen.<br />

Localización y Orientación de los Actores<br />

Cada actor tiene su matriz de transformación que controla su posición y escalado<br />

en el espacio del mundo. La geometría del actor está definida por un modelo en las<br />

coordenadas del modelo. Especificamos la posición del actor mediante los factores de<br />

orientación, posición y escala a los largo de los ejes coordenados. Además, podemos<br />

definir un origen, alrededor del cual rota el actor (por ejemplo, podemos rotarle<br />

alrededor de su centro, o de otro punto de interés). Como hemos dicho, el orden<br />

de aplicar las transformaciones es importante, si queremos llegar a los resultados<br />

deseados.<br />

3.3.10. Hardware Gráfico<br />

Anteriormente mencionamos que los avances en hardware gráfico han tenido<br />

un gran impacto en la forma en que se hace la renderización. En este apartado<br />

describimos los dispositivos de barrido que han sistituido a las pantallas vectorizadas,<br />

como dispositivos primarios de salida. Depués se describirá como se comunican los<br />

programas con el hardware gráfico. También se mostrará los distintos sistemas de<br />

coordenadas que se usan en gráficos por ordenador, eliminación de superficies y<br />

líneas ocultas, y z-buffering.<br />

Dispositivos de Barrido<br />

Normalmente vemos los gráficos por ordenador en imágenes impresas, o en un<br />

monitor de ordenador. Otras veces se ven en la TV o en una película. Todos estos<br />

52


medios son dispositivos de barrido. Un dispositivo de barrido representa una imagen<br />

usando una matriz bidimensional de elementos gráficos, llamados pixels.<br />

Debido a las limitaciones del hardware, los dispositivos de barrido, como las<br />

impresoras láser y los monitores de ordenador, realmente no dibujan pixels en forma<br />

de cuadrado perfecto, sino que están ligeramente borrosos y superpuestos. Otra<br />

limitación de hardware de los dispositivos de barrido es su resolución.<br />

Los monitores de ordenador en color tienen, de forma típica, una resolución de<br />

80 pixels por pulgada, haciendo de la pantalla una matriz de pixels de aproximadamente<br />

mil pixels de ancho y de alto. Esto produce alrededor de un millón de pixels,<br />

cada uno con un valor que indica su color. Como el hardware de los monitores en<br />

color usa el sistema RGB, tiene sentido usarlo <strong>para</strong> describir los colores de cada<br />

pixel. Desafortunadamente, teniendo más de un millón de pixels, cada uno con su<br />

componente de rojo, verde y azul, puede ocupar mucha memoria. Esto es parte de<br />

lo que diferencia la variedad de hardware gráfico en el mercado. Algunas compañías<br />

usan 24 bits <strong>para</strong> almacenar cada pixel, otros usan ocho, y algunos sistemas avanzados<br />

usan más de 100 bits <strong>para</strong> cada pixel. Cuantos más bits usa por pixel, con<br />

más precisión pueden representarse los colores (y por tanto más colores se pueden<br />

representar).<br />

Una forma de afrontar las limitaciones de colores en el hardware gráfico, es<br />

usando una técnica llamada dithering. Si por ejemplo, se quieren representar varios<br />

niveles de gris, en un sistema gráfico que sólo soporta blanco y negro, el dithering<br />

permite aproximar los niveles de gris usando una mezcla de pixels en blanco y negro.<br />

Desde cierta distancia (aunque sea bastante pequeña), parecen diferentes niveles de<br />

gris, en lugar de mezcla de pixels blancos y negros. La misma técnica se puede usar<br />

con otros colores, <strong>para</strong> obtener colores derivados de los soportados por el hardware<br />

gráfico.<br />

Interfaz con el Hardware<br />

Raramente hay que preocuparse del hardware de la pantalla. La mayoría de la<br />

programación se hace usando primitivas de alto nivel, en vez de tratar con los pixels.<br />

En la Fig. 3.7 se muestra la estructura típica de un programa de visualización. En el<br />

nivel inferior de la jerarquía está el hardware de la pantalla, que ya se ha descrito.<br />

Lo normal es que los programas no interactúen directamente con este hardware. Las<br />

tres capas que están por encima son las capas que hay que tener en cuenta al<br />

programar.<br />

Muchos programas hacen uso de las ventajas que ofrecen las librerías de aplicación,<br />

que son un interfaz de alto nivel a las capacidades gráficas del sistema. Vtk<br />

(Visualization Toolkit) [12] es un ejemplo de librería de aplicación. Permite visualizar<br />

gráficos complejos, haciendo uso de primitivas sencillas. También funciona<br />

como interfaz con distintas librerías gráficas, pues las distintas plataformas de hardware<br />

soportan distintas librería gráficas.<br />

53


Mi Programa<br />

Libreria de Aplicacion - vtk<br />

Libreria grafica<br />

Hardware Grafico<br />

Hardware de la Pantalla<br />

Figura 3.7: Jerarquía típica del interfaz de gráficos.<br />

Las capas de librería gráfica y hardware gráfico realizan funciones similares.<br />

Son las responsables de coger comandos de alto nivel de una librería de aplicación o<br />

de un programa, y ejecutarlos. Esto hace que la programación sea mucho más sencilla,<br />

proporcionando primitivas gráficas mucho más complejas con las que trabajar. En<br />

vez de dibujar los pixes de uno en uno, podemos dibujar primitivas como polígonos,<br />

triángulos, y líneas, sin preocuparnos de los pixels que hay que modificar <strong>para</strong> cada<br />

una de estas primitivas. Algunas primitivas que soportan todas las librerías gráficas<br />

son:<br />

Polígonos: conjunto de lados, normalmente en un plano, que definen una región<br />

cerrada. Por ejemplo: triángulos y rectángulos.<br />

Tiras de triángulos: serie de triángulos en los que cada triángulo comparte sus<br />

lados con los triángulos vecinos.<br />

Línea: Conecta dos puntos.<br />

Poli-línea: serie de líneas conectadas.<br />

Punto: Posición 3D en el espacio.<br />

Esta funcionalidad se divide en dos capas diferentes, porque las diferentes máquinas<br />

pueden tener un hardware gráfico muy diferente. Por ejemplo, si se escribe un programa<br />

que dibuje un polígono rojo, o bien la librería gráfica, o el hardware gráfico,<br />

debe poder ejecutar ese comando. En los sistemas de gama alta, se debe poder hacer<br />

mediante el hardware gráfico, en otros se hará mediante la librería gráfica con<br />

software. De esta forma, algunos comandos deben poder ser usados con una gran<br />

variedad de máquinas distintas, sin preocuparse del hardware gráfico.<br />

El bloque fundamental <strong>para</strong> formar primitivas más complejas es el punto (o<br />

vértice). Un vértice tiene su posición, normal, y color, cada uno de los cuales<br />

es un vector de tres elementos. La posición, especifica dónde se sitúa el vértice, la<br />

normal especifica la dirección hacia la que mira el vértice, y su color especifica las<br />

componentes de rojo, verde y azul del vértice. Un polígono se forma conectando<br />

54


Normal al Vertice<br />

Normal al Poligono<br />

Normal al Vertice<br />

Figura 3.8: Normales de vértices y polígonos.<br />

varios puntos. Un polígono plano sólo puede tener una dirección normal, independientemente<br />

de las normales de sus vértices; la razón de que los vértices tengan su<br />

propia normal es porque muchas veces se usan polígonos <strong>para</strong> aproximar curvas,<br />

como aparece en la Fig. 3.8, en la que se muestra la vista superior de un cilindro;<br />

se puede ver que no es realmente un polígono, sino una aproximación poligonal al<br />

cilindro dibujado en línea más gruesa. Cada vértice se comparte por dos polígonos<br />

y la normal correcta <strong>para</strong> el vértice no es la misma que la normal del polígono. Una<br />

razón similar explica porqué cada vértice tiene su color, en vez de tener un color<br />

<strong>para</strong> todo el polígono.<br />

Rasterización<br />

Nos queda por ver la forma de convertir primitivas gráficas a imágenes de barrido.<br />

Una explicación extensiva de ello queda fuera de mi intención, pero daré alguna<br />

pequeña idea. Ésta es la descripción de los métodos de renderización poligonales<br />

Al proceso de convertir una primitiva gráfica en una imagen de barrido se le<br />

llama comunmente rasterización. La mayoría del hardware gráfico actual, usa<br />

técnicas de rasterización siguiendo el orden de objetos. Como vimos, esto quiere<br />

decir que procesamos los actores en orden, y como los actores están formados por<br />

polígonos, procesamos un polígono cada vez. Normalmente se habla de triángulos,<br />

pues caulquier polígono se puede descomponer en triángulos mediante un proceso<br />

de triangulación. Además, el algoritmo de obtención de isosuperficies Marching<br />

Cubes que se describe en el apartado 3.4 genera triángulos.<br />

El primer paso es transformar el polígono, usando la matriz de transformación.<br />

Además proyectamos el polígono sobre el plano de la imagen usando proyección<br />

55


<strong>para</strong>lela o en perspectiva. También hay que recortar los polígonos que quedan fuera<br />

del tronco de cono de vista, con lo cual tenemos que generar nuevas fronteras. Con los<br />

polígonos recortados y proyectados sobre el plano de vista, comenzamos el proceso<br />

de “escaneado de línea”. El primer paso es identificar la primera línea de escaneado;<br />

<strong>para</strong> ello se ordenan los valores ‘y’ de los vértices del polígono. A continuación, se<br />

identifican los dos lados que se unen en ese vértice, a la derecha e izquierda del mismo.<br />

Usando las pendientes de los lados, junto con los valores de los datos, obtenemos las<br />

deltas <strong>para</strong> los valores de los datos. Estos datos son, típicamente, las componentes<br />

R, G y B del color. Otros valores de los datos pueden ser la transparencia y los<br />

valores de profundidad z (necesarios si se usa el método z-buffer que veremos a<br />

continuación). La fila de pixels dentro del polígono se llama: una extensión. Los<br />

valores de los datos se interpolan desde los bordes de la extensión, <strong>para</strong> generar los<br />

valores internos. Este proceso continúa extensión a extensión, hasta que se rellena<br />

todo el polígono. Según se van encontrando nuevos vértices, hay que calcular nuevas<br />

deltas <strong>para</strong> los valores de los datos.<br />

El sombreado del polígono (interpolación de color dentro del polígono) depende<br />

del atributo de interpolación del actor. Hay tres posibilidades:<br />

Sombreado Plano: calcula el color de un polígono aplicando las ecuaciones de<br />

iluminación solamente a una normal, que suele ser la normal a la superficie.<br />

Sombreado Gouraud: Calcula el color de un polígono en todos sus vértices, usando<br />

las normales en los vértices y las ecuaciones de iluminación.<br />

Sombreado Phong: es el más realista de los tres. Calcula la normal en cada posición<br />

del polígono interpolando las normales en los vértices. Se usan las ecuaciones<br />

de iluminación, <strong>para</strong> determinar el color resultante de cada pixel.<br />

Los sombreados Plano y Gouraud son, normalmente, los métodos empleados. La<br />

complejidad del sombreado Phong ha hecho que no sea soportado, a menudo, por<br />

hardware.<br />

Z-Buffer<br />

El método de renderización ray-casting tiene la ventaja de que los rayos que<br />

salen de nuestros ojos inciden sobre el primer actor que encuentran en su camino, e<br />

ignoran el resto de posibles actores que haya detrás. Sin embargo, cuando se usan<br />

métodos de renderización poligonales, no tenemos, en principio, forma de saber<br />

que polígonos están tapados y cuáles no. No se puede esperar, en principio, que los<br />

polígonos están ordenados. Lo que se hace es emplear una serie de métodos, llamados<br />

de “línea o superficie oculta” <strong>para</strong> renderización poligonal.<br />

El algoritmo del pintor u ordenación del pintor, consiste, simplemente en<br />

ordenar los polígonos de atrás hacia delante, <strong>para</strong> renderizarlos en ese orden. Su<br />

56


Figura 3.9: Problema con el algoritmo del pintor.<br />

mayor problema se puede ver en la Fig. 3.9. Sea cual sea el orden en el que se<br />

coloquen los tres triángulos, no podemos obtener el resultado deseado, ya que cada<br />

triángulo está delante, y detrás, de otro triángulo. Hay algoritmos que ordenan y<br />

dividen los polígonos cuando es necesario, <strong>para</strong> evitar este problema. Esto requiere<br />

un preprocesado, que se debe realizar cada vez que cambia la imagen o la cámara,<br />

lo cual puede ralentizar la renderización.<br />

Otro algoritmo de “superficie oculta” es el z-buffering, que no requiere ordenación.<br />

Hace uso del valor en la coordenada z (valor en profundidad a lo largo de la<br />

dirección de proyección) en el sistema de coordenadas de vista. Antes de dibujar un<br />

nuevo pixel, su valor z se com<strong>para</strong> con el valor z actual del pixel. Si el nuevo pixel<br />

debería estar delante del pixel actual, entonces se dibuja y el valor z del pixel se<br />

actualiza. En caso contrario, se mantiene el pixel actual y el nuevo pixel se ignora.<br />

El z-buffering ha sido comunmente implementado en hardware, debido a su simplicidad<br />

y robustez. El problema es que requiere una gran cantidad de memoria,<br />

llamada z-buffer, <strong>para</strong> almacenar el valor z de cada pixel. La mayoría de los sistemas<br />

usan un z-buffer <strong>para</strong> almacenar una profundidad de 24 ó 32 bits, lo cual se<br />

traduce, en un monitor de 1000 × 1000, en 3 ó 4 MegaBytes, sólo <strong>para</strong> el z-buffer.<br />

Otro problema es que su precisión depende de la profundidad, por lo que puede no<br />

ser suficiente si los objetos están muy cercanos. Cuando se tienen problemas de precisión<br />

con el z-buffering, la solución es acercar los planos de corte lo máximo posible<br />

a la geometría.<br />

3.4. Obtención de Isosuperficies: Marching Cubes<br />

Como dijimos en el apartado 3.3.10, el método de rasterización nos permite<br />

convertir primitivas gráficas en imágenes de barrido. Es un método de render-<br />

57


ización de superficie. Para poder renderizar esas primitivas gráficas de superficie,<br />

antes debemos obtenerlas, a partir de los datos volumétricos. El algoritmo marching<br />

cubes [13], describe una forma de obtener una superficie formada por triángulos<br />

que pasa por un determinado valor de densidad de los datos volumétricos. Una de<br />

las ventajas de este algoritmo es la generación de triángulos, que son muy adecuados<br />

<strong>para</strong> realizar la renderización, pues es el polígono más sencillo, soportado por todos<br />

los tipos de hardware gráfico.<br />

3.4.1. Descripción del Algoritmo<br />

El algoritmo consiste, básicamente en localizar la superficie correspondiente al<br />

valor de isosuperficie especificado y generar triángulos.<br />

Se usa una técnica de división y conquista, <strong>para</strong> localizar la superficie en<br />

una celda cúbica, formada por ocho puntos: cuatro de cada una de las dos rodajas<br />

adyacentes que forman los datos volumétricos.<br />

El algoritmo determina cómo intersecta la superficie a este cubo. A continuación<br />

se mueve (marcha) al siguiente cubo, y así sucesivamente. Para encontrar la superficie<br />

de intersección con un cubo, asignamos un uno a los vértices del cubo, en los<br />

que el valor de los datos en dicho vértice excede, o es igual, al valor de la superficie<br />

que estamos construyendo. Estos vértices están dentro, o sobre, la superficie. Los<br />

vértices del cubo con valores por debajo de la superficie se ponen a cero, y están<br />

fuera de la superficie.<br />

La superficie corta a cada arista del cubo, cuando un vértice está dentro (uno) y<br />

el otro fuera (cero). De esta forma, determinamos la topología de la superficie dentro<br />

de un cubo, encontrando la localización de la intersección después. Este método de<br />

obtención de la superficie, calcula las intersecciones de la misma con las aristas<br />

del cubo, de forma totalmente independiente, lo cual, como veremos más adelante<br />

produce ciertos problemas.<br />

Como tenemos ocho vértices en cada cubo y dos posibles estados, dentro y fuera,<br />

hay 2 8 = 256 formas en que la superficie puede intersectar al cubo. Triangular los 256<br />

casos es posible, pero tedioso. Sin embargo, aplicando dos operaciones de simetría<br />

sobre el cubo, reducimos el problema de 256 casos a tan sólo 15. Estas operaciones<br />

de simetría son:<br />

1. La topología de la superficie triangulada no cambia, si los valores de los vértices<br />

del cubo se invierten. Los casos complementarios en los que los vértices mayores<br />

que el valor de la superficie se intercambian con aquellos menores que ese valor,<br />

son equivalentes. Por tanto, sólo tenemos que considerar los casos con 0 a 4<br />

vértices mayores que el valor de la superficie (uno). Esto reduce el número de<br />

casos a 128.<br />

58


2. Aplicando simetría rotacional, por inspección se puede ver que reducimos el<br />

número de casos posibles a 15.<br />

En la Fig. 3.10, se pueden ver estos 15 casos. El caso más simple, 0, ocurre si todos<br />

los vértices están por encima (o por debajo) del valor seleccionado, y no produce<br />

ningún triángulo. El siguiente caso, el 1, ocurre si la superficie se<strong>para</strong> un vértice<br />

de los otros siete, produciendo un triángulo, definido por a intersección con las tres<br />

aristas que se juntan en ese vértice. Los otros casos producen múltiples triángulos.<br />

Mediante permutación de estos 15 patrones básicos, usando las propiedades de<br />

complementariedad y rotación simétrica, se consiguen los 256 casos posibles.<br />

Se crea un índice de 8 bits, en el que se almacena el estado binario de cada vértice<br />

de la celda. Este índice se usa como puntero a una tabla de casos <strong>para</strong> buscar el<br />

estado topológico de la celda.<br />

Una vez se selecciona el caso correcto, se calcula la posición exacta de la intersección<br />

de la superficie con cada arista del cubo, mediante interpolación. Se usa<br />

interpolación lineal, por ser la más sencilla. Lorensen y Cline [13] han experimentado<br />

con interpolaciones de orden mayor, pero consiguen poca mejora, por lo que no<br />

merece la pena.<br />

3.4.2. Problema del Algoritmo: Ambigüedad<br />

La principal ventaja de este algoritmo es su secillez, debido a que el algoritmo<br />

se aplica a cada celda de forma independiente.<br />

Sin embargo, al tratar cada celda por se<strong>para</strong>do, se producen problemas de ambigüedad.<br />

Si se observan cuidadosamente los casos 3, 6, 7, 10, 12 y 13, se puede<br />

observar que en estas celdas, la superficie puede pasar por distintas aristas. El resultado<br />

de estas ambigüedades es que se pueden producir agujeros en la superficie.<br />

Se han propuesto varias soluciones <strong>para</strong> evitar estos problemas de ambigüedad:<br />

[12]<br />

Una técnica divide los cubos en tetraedros, y usa una técnica de marching<br />

tetraedra. Esto funciona porque marching tetraedra no tiene casos ambiguos. El<br />

problema es que este algoritmo genera mayor número de triángulos. Además, la<br />

división de un cubo en tetraedros requiere elegir la orientación de los tetraedros<br />

dentro del cubo, lo cual puede producir salientes artificiales sobre la supericie,<br />

a causa de la interpolación a lo largo de las diagonales de las caras del cubo.<br />

Otra técnica estudia el comportamiento asimptótico de la superficie, <strong>para</strong> elegir<br />

entre unir o se<strong>para</strong>r la isosuperficie. Se basa en un análisis de la variación del<br />

valor escalar a lo largo de la superficie ambigua.<br />

59


0 1<br />

2<br />

3 4 5<br />

6<br />

7<br />

8<br />

9 10 11<br />

12<br />

13<br />

14<br />

Figura 3.10: Los 15 cubos triangulados del algoritmo Marching Cubes.<br />

60


Caso 3c<br />

Caso 6c<br />

Caso 7c<br />

Caso 10c<br />

Caso 12c<br />

Caso 13c<br />

Figura 3.11: Casos complementarios <strong>para</strong> el algoritmo Marching Cubes.<br />

Una solución sencilla y efectiva (la que usamos), consiste en extender los 15<br />

casos vistos, con otros complementarios, <strong>para</strong> los casos que pueden tener ambigüedad.<br />

Estos casos se diseñan <strong>para</strong> que sean compatibles con los casos<br />

vecinos y eviten agujeros en la superficie. Los seis casos complementarios que<br />

se necesitan, <strong>para</strong> los casos 3, 6, 7, 10, 12 y 13 se muestran en la Fig. 3.11.<br />

61


Capítulo 4<br />

Compresión de Datos<br />

4.1. Introducción<br />

La compresión de datos se puede enmarcar en el modelo de referencia OSI<br />

(Interfaz de Sistemas Abiertos) de ISO (Organización Internacional de Normas)<br />

dentro del nivel de presentación [2] (nivel número seis). Éste nivel, a diferencia de los<br />

cinco inferiores, que solamente se ocupan del movimiento ordenado de bits, desde el<br />

extremo fuente, hasta el extremo destino, se encarga de la preservación del significado<br />

de la información transportada. Cada ordenador puede tener su propia forma de<br />

representación interna de los datos, por ello el trabajo de la capa de presentación<br />

consiste en codificar los datos estructurados, del formato interno utilizado en la<br />

máquina transmisora, a un flujo de bits adecuado <strong>para</strong> la transmisión y después,<br />

decodificarlos y mostrarlos en el formato del extremo destinatario.<br />

La compresión de datos reduce el número de bytes <strong>para</strong> representar un conjunto<br />

de datos. La compresión se ha usado en casi todos los campos en los que se trabaja<br />

con información digital: texto, audio, imagen, . . .<br />

La compresión reduce la cantidad de memoria (espacio de disco) necesario <strong>para</strong><br />

almacenar los datos y como consecuencia de ello, también reduce la cantidad de<br />

tiempo necesario <strong>para</strong> transmitir los datos a través de un enlace de comunicaciones,<br />

a una determinada tasa de transmisión.<br />

4.1.1. Terminología General<br />

Para describir los esquemas de compresión se usan los siguientes términos [6]:<br />

Relación de Compresion o Compresión. Relación entre el tamaño de los datos<br />

originales y el tamño de los datos comprimidos:<br />

Relación de Compresion =<br />

N.o bytes de datos originales<br />

N. o bytes de datos comprimidos<br />

63<br />

(4.1)


La relación de compresión se expresa como un número o como dos números,<br />

siento el segundo número normalmente 1. Por ejemplo, 10:1 significa que 10<br />

bytes de los datos originales se representa con 1 byte de los datos comprimidos.<br />

Relaciones de compresión mayores significan menor tamaño de los datos<br />

comprimidos.<br />

Bits por Pixel o Voxel. En compresión de imagen, indica el número medio de<br />

bits necesario <strong>para</strong> representar el valor de un pixel de la imagen o de un voxel<br />

del volumen. Es otra forma de indicar la relación de compresión.<br />

Compresión sin Pérdidas. Compresión en la que no se pierde nada de la información<br />

contenida en los datos originales.<br />

Compresión con Pérdidas. Compresión en la que se puede perder parte de los<br />

datos originales. Estos esquemas pueden conseguir compresiones mayores que<br />

los esquemas sin pérdidas, pero pagando el precio de la pérdida de información.<br />

Codificación. Proceso que toma como entrada un conjunto de datos y produce<br />

como salida el conjunto de datos comprimido correspondiente.<br />

Decodificación. Proceso que toma como entrada un conjunto de datos comprimido<br />

y produce como salida el conjunto de datos original (o una aproximación al<br />

mismo si se usa compresión con pérdidas).<br />

Codec. Se refiere al codificador, al decodificador o a ambos. Es una abreviatura de<br />

codificador/decodificador.<br />

Entropía. Medida de la aleatoriedad de un conjunto de datos. Un conjunto de<br />

datos totalmente aleatorio no puede ser comprimido. Cuanto más orden tenga<br />

un conjunto de datos, más se puede comprimir. Si una serie de datos contiene<br />

algún tipo de orden, podemos eliminar la información redundante <strong>para</strong><br />

comprimirlo.<br />

A continuación describo algunos métodos de compresión usados en voz, audio,<br />

imagen y datos volumétricos, campos en los que, debido al gran tamaño de los datos,<br />

es muy importante la compresión.<br />

4.2. Compresión de Audio y Voz<br />

La copresión de audio ha alcanzado un gran desarrollo últimamente, <strong>para</strong> abaratar<br />

los sistemas de almacenamiento de audio digital. Por su parte, la compresión de voz<br />

se ha desarrollado hace más tiempo. Ambas tienen una diferencia:<br />

64


Voz: está generada por el sistema fonador humano. Los esquemas de compresión<br />

se pueden basar en sus características, con lo cual tenemos información<br />

de la señal a comprimir.<br />

Audio: la fuente puede ser cualquiera que genere sonido, con lo que no se<br />

pueden usar las estrategias usadas <strong>para</strong> voz. Sin mebargo, sí tenemos información<br />

del funcionamiento del oído, que se puede utilizar <strong>para</strong> realizar la<br />

compresión; siempre que introduzcamos un error que quede enmascarado por<br />

el oído, no lo percibiremos. Durante muchos años se han usado técnicas de<br />

codificación de forma de onda, que son generales.<br />

A continuación voy a describir métodos de codificación y compresión, empezando<br />

por los más elementales y a su vez generales, que valen tanto <strong>para</strong> audio como <strong>para</strong><br />

voz. Después explicaré someramente y como ejemplo algunos métodos específicos<br />

usados <strong>para</strong> voz. Estos esquemas tratan de reducir el coste de la señal de audio<br />

digital, minimizando la distorsión introducida.<br />

4.2.1. Codificadores de Forma de Onda<br />

Están basados en un procesado muestra a muestra de la señal digital y son válidos<br />

<strong>para</strong> cualquier señal que sea limitada en banda y en amplitud [3].<br />

PCM<br />

El esquema básico de codificación tanto de voz como de audio es PCM (Modulación<br />

por Pulsos Codificados), que consiste, simplemenete, en un cuantificador<br />

uniforme, en el que hay 2 N niveles de cuantificación y se usan N bits <strong>para</strong> representar<br />

cada muestra. Este tipo de codificación no tiene ningún tipo de compresión. Se<br />

codifican directamente las muestras.<br />

log-PCM o Cuantificación Logarítmica<br />

El problema básico de PCM es que el nivel de cuantificación se elige <strong>para</strong> que<br />

no se produzca saturación con los niveles mayores de la señal. Sin embargo, especialmente<br />

en voz, tienen más importancia los niveles de señal bajos, en los que se<br />

produce mucho error relativo. Por ello se suele usar un cuantificador logarítmico que<br />

da más importancia a los niveles pequeños de señal.<br />

ADPCM o PCM Adaptativo<br />

La mayor limitación de los dos esquemas anteriores de codificación es que no<br />

tienen en cuenta las variaciones de amplitud de la señal. Una solución a este problema<br />

es adaptar dinámicamente el escalón del cuantificador, <strong>para</strong> adecuarse a las<br />

65


x[n]<br />

d[n] c[n] c[n] d[n]<br />

+ Cuantificador Codificador Decodificador<br />

+<br />

xr[n]<br />

xe[n]<br />

Predictor<br />

P<br />

xr[n]<br />

+<br />

Canal<br />

xe[n]<br />

Predictor<br />

P<br />

Transmisor<br />

Receptor<br />

Figura 4.1: Esquema del sistema DPCM (PCM Diferencial).<br />

características de la señal a codificar. La adaptación se puede hacer cada muestra o<br />

cada varias muestras, dependiendo de la estacionariedad de las señales a codificar.<br />

El escalón del cuantificador se determina multiplicando el escalón anterior por<br />

una constante, que se calcula a partir de la anterior muestra codificada. Si esta<br />

muestra es pequeña, se coge una constante mayor que 1 y viceversa.<br />

En el decodificador se calcula el tamaño del escalón a partir de cada muestra<br />

que llega, con lo cual la codificación es reversible. Este esquema de codificación, por<br />

ejemplo en telefonía, supone una reducción de 1 bit por muestra <strong>para</strong> una misma<br />

SNR respecto a log-PCM.<br />

DPCM o PCM Diferencial<br />

Normalmente, las señales de voz y audio suelen tener una cierta correlación entre<br />

muestras sucesivas. Por tanto, la diferencia entre las mismas, tiene menor varianza<br />

y rango dinámico que la señal original, con lo que se puede codificar con menos bits,<br />

<strong>para</strong> una cierta SNR. Lo que se hace es estimar la muestra actual, x e [n] a partir de<br />

las anteriores, restarla de la muestra actual, x[n], y cuantificar la diferencia, d[n],<br />

como se muestra en la Fig. 4.1. La señal reconstruida, x r [n] se obtiene sumando la<br />

diferencia cuantificada, d[n], a la señal estimada, x e [n]. x e [n] se obtiene como una<br />

combinación lineal de las muestras anteriores de x r :<br />

N∑<br />

x e [n] = a k x r [n − k] (4.2)<br />

k=1<br />

donde N puede variar (desde 1 a 14 son valores típicos) y los coeficientes a k se<br />

obtienen de forma experimental. Cuanto mejor sea la predicción, menor será la<br />

diferencia, d[n], y por tanto se consigue mayor compresión.<br />

66


DM o Modulación Delta<br />

Este tipo de modulación es un caso limitado de la técnica DPCM. En el sistema<br />

DM, el predictor de la Fig. 4.1 es un predictor de primer orden<br />

x e [n] = ax r [n] (4.3)<br />

donde a es una constante ligeramente menor que 1 y se usa un cuantificador de 1 bit,<br />

cuya salida es +δ(1), si la diferencia es positiva y −δ(0), si la diferencia es negativa.<br />

Como se usa un cuantificador de 1 bit, la tasa binaria es igual a la frecuencia de<br />

muestreo, pero se debe usar una frecuencia de muestreo varias veces la de los sistemas<br />

PCM convencionales. Si la frecuencia de muestreo es demasiado baja, se produce<br />

ruido de sobrecarga (el sistema no puede seguir los cambios rápidos de la señal).<br />

Se puede reducir aumentando la frecuencia de muestreo o aumentando el escalón<br />

de cuantificación. Sin embargo, si se aumenta el escalón, se produce mayor ruido<br />

granular (1s y 0s alternados), que es más importante cuando no hay señal.<br />

Una mejora al sistema DM es usar un escalón de cuantificación adaptativo<br />

(ADM), de forma similar al sistema APCM. Una estrategia posible <strong>para</strong> ello es<br />

multiplicar o dividir por dos el escalón, dependiendo de que el siguiente bit cuantificado<br />

sea diferente o igual al anterior. La variación del escalón se limita entre un<br />

valor mínimo y otro máximo. De esta forma se reduce tanto el error de sobrecarga,<br />

como el granular. El mayor problema de ADM es que, como el escalón varía<br />

instantáneamente, cambios bruscos en la señal de entrada –como los producidos por<br />

el ruido– pueden provocar que el sistema tarde en recuperarse.<br />

ADPCM o DPCM Adaptativo<br />

La codificación DPCM usa un escalón de cuantificación fijo y un predictor con<br />

coeficientes constantes. Se puede conseguir una importante mejora en la relación<br />

de compresiónsi se añade un cuantificador adaptativo y/o un predictor adaptativo.<br />

El esquema es similar al de la Fig. 4.1, pero con el predictor, el cuantificador, el<br />

codificador y el decodificador (o algunos de ellos) con bloques de predicción. La<br />

sincronización entre el transmisor y el receptor se consigue haciendo que el transmisor<br />

y el receptor sean esencialmente iguales, y además usando sólo la diferencia<br />

transmitida, c[n], (presente en transmisor y receptor) <strong>para</strong> realizar la adaptación.<br />

Normalmente se hace una mezcla entre adaptación instantánea y adaptación cada<br />

cierto intervalo de tiempo, por lo que se puede adaptar tanto a señales estacionarias,<br />

como no estacionarias.<br />

La complejidad de un sistema ADPCM depende, en su mayor parte de la complejidad<br />

del algoritmo de predicción. Este tipo de codificación se estableció como<br />

estándar en la transmisión de voz y datos a 32 Kbits/s sobre la red telefónica. El<br />

estándar usa log-PCM a 64 Kbits/s y lo convierte a 32 Kbits/s, usando ADPCM.<br />

67


4.2.2. Algunos Codificadores Específicos de Voz<br />

Las técnicas vistas anteriormente operan en el dominio del tiempo directamente<br />

y consiguen reducir la tasa binaria, explotando la correlación o redundancia entre<br />

muestras sucesivas de la señal. En este capítulo se describen algunas técnicas usadas<br />

<strong>para</strong> compresión de voz [3], con tasas binarias que varían entre 16 Kbits/s y menos<br />

de 1 Kbit/s. En todas estas técnicas se codifica, o bien una versión modificada<br />

o transformada de la señal (en el dominio de la frecuencia). Estos codificadores se<br />

dividen en <strong>para</strong>métricos y no <strong>para</strong>métricos. En los <strong>para</strong>métricos (vocoders) se intenta<br />

se<strong>para</strong>r la información del tracto vocal de la información de la señal excitadora,<br />

mientras que en los no <strong>para</strong>métricos, no se hace esta se<strong>para</strong>ción.<br />

Codificación en Sub-Bandas<br />

En las técnicas explicadas en el apartado anterior, se codifica la señal de banda<br />

completa. En la codificación en sub-bandas, lo que se hace es dividir la señal en subbandas,<br />

mediante filtros paso-banda y después se codifica la salida de cada filtro<br />

usando una de las técnicas vistas entonces.<br />

Las salidas de los filtros están bastante incorreladas, por lo que se elimina la<br />

redundancia de la señal de entrada. La asignación de bits se hace siguiendo criterios<br />

perceptuales. Se asignan más bits a las bajas frecuencias (tienen más importancia<br />

<strong>para</strong> el oído) y menos a las altas.<br />

En el codificador, la señal de entrada se filtra con un determinado número de<br />

filtros paso-banda, que cubren todo el espectro de interés <strong>para</strong> voz (300 - 3400<br />

Hz <strong>para</strong> telefonía); normalmente se usan de 2 a 5 bandas, cuyo ancho de banda<br />

normalmente es menor <strong>para</strong> los filtros centrados en frecuencias bajas. La salida de<br />

cada filtro se demodula a banda base, y como estas señales en banda base tienen<br />

mucho menor ancho de banda que la señal original, se pueden diezmar por un factor<br />

D n , que depende de la frecuencia de muestreo de la señal original, f s , y el ancho de<br />

banda del filtro correspondiente, b n . A partir del teorema de Nyquist y suponiendo<br />

filtros ideales:<br />

D n = f s /2b n (4.4)<br />

Cada sub-banda se cuantifica y codifica con uno de los esquemas vistos en el apartado<br />

anterior. Normalmente se usa PCM (APCM) y alguas veces ADPCM o ADM. Las<br />

sub-bandas así codificadas, se multiplexan y se transmiten o almacenan, según el<br />

caso.<br />

En el decodificador se usa el procedimiento contrario. Primero se demultiplexa<br />

la señal, se interpola cada sub-banda insertando ceros <strong>para</strong> deshacer el diezmado,<br />

se modula a cada banda correspondiente y se filtra, con filtros idénticos a los del<br />

codificador. Las salidas de estos filtros se suman y de esta forma se reconstruye la<br />

señal original.<br />

68


La codificación en subbandas se puede usar <strong>para</strong> codificación de voz, con tasas<br />

binarias de 9.6–32 Kbits/s, siendo su calidad en este rango similar a la de ADPCM,<br />

a la misma tasa binaria. Su relativa calidad de sonido y no muy alta complejidad,<br />

lo hace muy adecuado <strong>para</strong> codificación por debajo de 16 Kbits/s. Sin embargo, la<br />

complejidad al aumentar la tasa binaria, aumenta más que en otros esquemas, por<br />

lo que por encima de 20 Kbits/s no es adecuado.<br />

Compresión de audio basada en sub-Bandas<br />

Para compresión de audio también se puede utilizar un esquema en sub-bandas [5].<br />

Como ya dijimos, <strong>para</strong> compresión de audio, se puede codificar sólo la información<br />

que el oído no enmasacara. En efecto, el oído permite cierta distorsión, pues sólo<br />

parte de la señal analógica que recibe la considera necesaria y suficiente. De esta<br />

forma, se permite cierto error en la compresión, que si es enmascarado por el oído,<br />

no devalúa la calidad percibida del sonido.<br />

Para audio, el ancho de banda de cada filtro puede ser constante o ir aumentando<br />

conforme aumenta su frecuencia central (de la misma forma que las bandas críticas<br />

del oído).<br />

El elemento clave en los sistemas de compresión de audio basados en sub-bandas<br />

es el cálculo del umbral enmascarado, que se obtiene a partir de modelos psicoacústicos<br />

y sirve <strong>para</strong> calcular la distribución en la asignación del número de bits a cada<br />

cuantificador, a la salida de cada filtro.<br />

TC o Codificación Transformada<br />

Es una técnica de codificación en el dominio de la frecuencia, en la cual se realiza<br />

una transformación corta (short time) de la señal original y se codifica de forma<br />

eficiente, asignando más bits a los coeficientes de la transformada más importantes<br />

que a los menos importantes. En el receptor se usa la transformación inversa, <strong>para</strong><br />

recuperar la señal original. Para reflejar la no estacionariedad de la señal de voz,<br />

los coeficientes de la transformada se actualizan cada cierto intervalo de tiempo fijo<br />

(10-20 ms).<br />

Se pueden usar distintas transformadas, <strong>para</strong> obtener la información espectral,<br />

como la transformadad de Fourier discreta (DFT), pero es más eficiente usar la<br />

transformada del coseno distreta (DCT), que está relacionada con la anterior,<br />

pero es más eficiente. La DCT está definida por las siguientes relaciones:<br />

N−1 ∑<br />

X(k) = C k<br />

x[n] = 1 N C k<br />

N−1 ∑<br />

n=0<br />

n=0<br />

x[n] cos ((2n + 1)kπ/N) , k = 0, 1, . . . , N − 1 (4.5)<br />

X(K) cos ((2n + 1)kπ/N) , k = 0, 1, . . . , N − 1 (4.6)<br />

donde C k = 1, k = 0, y C k = 2 1/2 , k = 1, 2, . . . , N − 1, x[n] es la señal de voz<br />

enventanada de N muestras y X(k) los coeficientes de la DCT.<br />

69


La longitud típica de los segmentos suele ser de 128-256 muestras. La Ec. (4.5)<br />

se puede escribir en forma matricial:<br />

X(k) = A(k, n) · x(n), A(k, n) = α(k)cos((2n + 1)kπ/N) (4.7)<br />

donde X(k) y x(n) son vectores columna de longitud N que representan, respectivamente,<br />

los coeficientes de la DCT y la señal enventanada y A(k,n) es una matriz<br />

cuadrada (N × N) de transformación. En el receptor se recupera la señal, multiplicando<br />

el vector de coeficientes por la inversa de la matriz de transformación. En<br />

el caso de la DCT, la matriz de transformación es unitaria, lo cual implica que su<br />

inversa es igual a su traspuesta (esto simplifica las cosas). En la práctica, la DCT<br />

se puede calcular más rápidamente usando la DFT, mediante el algoritmo de la<br />

transformada rápida de Fourier (FFT), que es más rápido. Para ello, se debe<br />

extender la longitud de los datos originales de N a 2N, colocando los datos de forma<br />

simétrica, haciendo la FFT de 2N puntos y cogiendo los N primeros. Esto es<br />

válido, porque la DFT de una secuencia real y simétrica produce una secuencia con<br />

coefientes reales, que se corresponden con los coeficientes de la serie de cosenos.<br />

La mayoría de las codificaciones transformadas, usadas en la práctica, son adaptativas<br />

(ATC), en el sentido de que el número de bits usados <strong>para</strong> codificar los<br />

coeficientes de la transformada, permanece constante, pero la asignación de bits<br />

a cada coeficiente puede variar de muestra a muestra. Esta variación se controla<br />

por la variación de los estadísticos del habla, que deben ser transmitidos junto con<br />

los coeficientes codificados. ATC permite tasas binarias entre 9.6 y 20 Kbits/s. Su<br />

complejidad es algo más elevada que la de codificación en sub-bandas.<br />

Vocoders<br />

Los vocoders (voice coders) o codificadores de análisis/síntesis incorporan una<br />

etapa de análisis en el transmisor, <strong>para</strong> extraer un conjunto de parámetros que representan,<br />

por un lado la señal de excitación del tracto vocal, y por otro la envolvente<br />

del espectro del tracto-vocal. Estos parámetros son codificados y enviados al receptor.<br />

En el receptor está la etapa de síntesis, en la que se reconstruye la señal original<br />

de voz a partir de los parámetros recibidos.<br />

Hay varios tipos de vocoders, según los parámetros usados. El más sencillo es<br />

el vocoder de canal, cuyo funcionamiento básico voy a explicar a continuación.<br />

El analizador consta de un número de filtros paso-banda (normalmente 16-19), que<br />

cubren la banda de voz (pongamos 0-4 KHz); las salidas de los mismos son rectificadas<br />

y filtradas paso bajo y muestreadas de forma síncrona cada 10-30 ms,<br />

obteniendo de esta forma la amplitud del espectro corto (short time) de la señal de<br />

voz, que nos da la información del tracto vocal. Además se realiza una decisión de<br />

voz/ruido (hay fonemas sonoros, como las vocales, y otros ruidosos, como la ‘s’) y se<br />

obtiene la frecuencia del tono de voz. Ambas forman la información de la excitación.<br />

Se multiplexa toda esta información y se envía al receptor.<br />

70


En el receptor está la etapa de síntesis, en la que la información de excitación se<br />

usa <strong>para</strong> elegir entre una fuente ruidosa o una fuente tonal a la frecuencia del tono<br />

de voz. La señal recibida en cada canal se usa <strong>para</strong> modular la amplitud de la señal<br />

de excitación que entra a cada filtro paso-banda del sintetizador. Las frecuencias<br />

centrales de estos filtros son las mismas que las de los filtros del analizador, pero<br />

tienen anchos de banda menores. La salida de estos filtros se suma <strong>para</strong> obtener la<br />

señal sintetizada.<br />

Los esquemas usados en realidad, son algo más complejos, pues incluyen elementos<br />

como control automático de ganancia (CAG), <strong>para</strong> aumentar la eficiencia de<br />

codificación y protección frente a errores, <strong>para</strong> mejorar el rendimiento en canales<br />

muy ruidosos. Se pueden conseguir tasas binarias de 2.4 Kbits/s.<br />

Un vocoder más complejo y avanzado es el vocoder cepstral. Está basado en<br />

el análisis cepstral. Esta técnica se<strong>para</strong> la excitación y el espectro del tracto vocal,<br />

haciendo la transformada inversa de Fourier del logaritmo del espectro, <strong>para</strong> obtener<br />

de esta forma, el cepstro de la señal. Los coeficientes del cepstro de baja frecuencia<br />

corresponden a la envolvente espectral del tracto vocal y la excitación aparece como<br />

un tren de pulsos en los múltiplos de la frecuencia del tono de voz. Los coeficientes<br />

del cepstro correspondientes al tracto vocal, se se<strong>para</strong>n de los correspondientes a la<br />

excitación, mediante un filtrado lineal (enventanado). La posición en el tiempo del<br />

primer pulso en el cepstro nos da la información del tono de voz y la presencia o no<br />

de pulsos marcados indica si el sonido es sordo o sonoro. Toda esta información es<br />

multiplexada y codificada.<br />

En el receptor (sintetizador), se hace la transformada de Fourier de los coeficientes<br />

del cepstro correspondientes al tracto vocal, a continuación la exponencial<br />

y después la transformada inversa de Fourier, <strong>para</strong> obtener la respuesta al impulso<br />

del tracto vocal. Convolucionando esta respuesta al impulso con una señal de excitación<br />

sintética (ruido o tren de pulsos al tono de voz), se reconstruye la señal de<br />

voz original.<br />

4.3. Compresión de Imágenes y Datos Volumétricos<br />

4.3.1. Criterios de Diséno<br />

La compresión de datos gráficos es útil <strong>para</strong> una gran variedad de aplicaciones, y<br />

cada una impone diferentes criterios y requisitos en los esquemas de compresión. Por<br />

tanto, como en cualquier tarea de diseño en ingeniería, se deben alcanzar compromisos<br />

entre coste y funcionamiento. En la tabla 4.1 aparecen los distintos requisitos<br />

de diseño y las diferentes opciones existentes <strong>para</strong> cada uno, y en la tabla 4.2 aparecen<br />

algunos ejemplos de aplicaciones que necesitan compresión, como se muestra en<br />

71


Cuadro 4.1: Requisitos de diseño y posibles opciones<br />

Requisit.<br />

Opciones<br />

Tiempo<br />

Coste<br />

Pérdida Info.<br />

• Se requiere codificación y decodificación en tiempo real<br />

• Sólo se requiere decodificación en tiempo real<br />

• Se puede aceptar procesado más lento que en tiempo real<br />

• Requiere sólo software<br />

• Requiere coprocesador de punto flotante (coste adicional)<br />

• Requiere hardware de propósito especial (más caro)<br />

• Sin pérdida de información<br />

• Alguna pérdida, pero no distinguible por el ojo humano<br />

• Más pérdida de información, pero la que requiere la<br />

aplicación se mantiene<br />

Compresión • Basta con poca compresión (2:1 o 3:1)<br />

• Basta con compresión media (5:1 o 10:1)<br />

• Se necesita gran compresión (20:1, 100:1 o más)<br />

[6].<br />

Como aparece en [7], en general se usan tres criterios <strong>para</strong> evaluar las distintas<br />

técnicas de compresión:<br />

Relación de Compresión o tasa de compresión (o el inverso, bits/pixel)<br />

Fidelidad de la imagen de salida (o el inverso, medida del error)<br />

Coste computacional (o el inverso, simplicidad computacional)<br />

De esta forma, las técnicas de compresión se pueden evaluar mejor en un espacio<br />

tridimensional, con uno de los elementos anteriores en cada eje. Una técnica puede<br />

tener mucha compresión, pero poca calidad, o al revés, o tener una compresión<br />

moderada, pero también una fidelidad media. A su vez, cada técnica tiene su propio<br />

coste computacional.<br />

La relación de compresión o tasa de compresión, es la medida cuantitativa fundamental<br />

<strong>para</strong> evaluar la efectividad de un método de compresión. En imagen se<br />

puede ver como la relación entre los bits/pixel, en la imagen original, y en la imagen<br />

72


Cuadro 4.2: Algunas aplicaciones que requieren compresión<br />

Aplicacion Caract. de dispositivo Requisitos<br />

Videodisc Dispositivo de sólo lectura • No hace falta codif. en tiempo real<br />

• Decodif. en tiempo real<br />

Videoteléfono Ancho de banda pequeño • Compresión alta<br />

• Codif. y decod. casi en tiempo real,<br />

que tarden aprox. el mismo tiempo<br />

Teleconferencia Ancho de banda grande • Compresiones altas, <strong>para</strong> imágenes<br />

de alta calidad<br />

Telemedicina Anchos de banda • Mínima pérdida de info. <strong>para</strong> evitar<br />

pequeños y grandes malos diagnósticos<br />

comprimida. Si se usan imágenes originales con los mismos bits/pixel, basta con dar<br />

el número de bits/pixel de la imagen comprimida como medida de la compresión del<br />

método.<br />

La fidelidad de la imagen es una medida cualitativa del proceso de compresión y<br />

es el siguiente criterio más importante. De todas las formas, ambos criterios están<br />

muy relacionados. Para com<strong>para</strong>r diferentes esquemas de compresión hace falta una<br />

buena medida de la distorsión introducida por el método de compresión. Las medidas<br />

usadas <strong>para</strong> evaluar la fidelidad son:<br />

MSE: error cuadrático medio.<br />

RMS: raíz cuadrada del error cuadrático medio.<br />

NMSE: MSE normalizado.<br />

NRMS: RMS normalizado.<br />

Es importante decir que estos parámetros no son suficientes <strong>para</strong> evaluar cualitativamente<br />

el algoritmo. Además son deseables com<strong>para</strong>ciones visuales, que aunque<br />

son subjetivas, la mayoría de las imágenes están pensadas <strong>para</strong> ser vistas por el ojo<br />

humano. También se pueden definir otras medidas de la distorsión, que sólo son adecuadas<br />

<strong>para</strong> determinadas técnicas, como son los métodos de transformación, que se<br />

verán más adelante. Cuando estas imágenes van a ser después procesadas con procedimientos<br />

estadísticos, la mejor medida puede ser la fidelidad de los estadísticos<br />

en las regiones de la imagen, en vez de las intensidades de cada pixel. De hecho, no<br />

hay ningún método <strong>para</strong> medir la fidelidad, que sea útil <strong>para</strong> todas las aplicaciones.<br />

73


El coste computacional debe tener en cuenta dos procesos, la compresión y la<br />

descompresión de la imagen. Para la compresión, en aplicaciones que requieran un<br />

espacio de almacenamiento mínimo, el coste de compresión tiene menos importancia<br />

que el de descompresión, que debe realizarse cada vez que se lee la imagen del sistema<br />

de almacenamiento. Para aplicaciones de compresión diseñadas <strong>para</strong> la transmisión<br />

rápida a través de una red, es igual de importante el coste de compresión como el<br />

coste de descompresión.<br />

En general, la fidelidad de la imagen disminuye al aumentar la relación de compresión;<br />

y al aumentar la fideliedad de la imagen, normalmente aumenta el coste<br />

computacional. El compromiso de diseño entre los tres se puede ver como un problema<br />

en tres dimensiones, en el que la fidelidad de la imagen es la variable dependiente<br />

del coste computacional y de la fidelidad.<br />

4.3.2. Métodos de Compresión<br />

Cualquier esquema de compresión de datos se puede dividir, como se muestra<br />

en [6] en tres métodos diferentes: 1) transformación, 2) precisión reducida, y 3)<br />

minimización del número de bits.<br />

Método 1. Transforma el conjunto de datos original en otro conjunto de datos<br />

equivalente, que ocupa menos que el original. Algunas transformaciones reducen<br />

el número de elementos en el conjunto de datos. Otras transformaciones<br />

reducen el tamaño numérico de los elementos del conjunto de datos, lo que<br />

permite representarlos con menos bits.<br />

Método 2. Reduce la precisión de los valores de los elementos del conjunto de<br />

datos, lo que reduce el número de bits <strong>para</strong> representar cada elemento. Este<br />

método también puede reducir la aleatoriedad en una ristra de datos.<br />

Método 3. Representa cada elemento del conjunto de datos, de forma que se minimiza<br />

el número de bits <strong>para</strong> representar todo el conjunto de datos. En la<br />

Fig. 4.2 se muestra la idea, metodología y nombre técnico dado a cada uno de<br />

los métodos de compresión.<br />

4.3.3. Método 1: Transformación<br />

Se puede reducir el tamaño de un conjunto de datos explotando los patrones<br />

(orden) existente en los mismos. Un patrón es cualquier forma de repetición o<br />

redundancia que existe en los valores de los datos. Si un conjunto de datos no<br />

contiene patrones (es totalmente aleatorio), no se puede comprimir e incluso si se<br />

aplica un esquema de compresión a datos totalmente aleatorios, potencialmente<br />

puede aumentar su tamaño.<br />

74


Metodo 1<br />

Metodo 2 Metodo 3<br />

Idea<br />

Minimizar la cantidad de datos Minimizar precision de datos Minimizar # bits por elemento<br />

Metodo<br />

Reconocimiento de patrones<br />

Division de datos<br />

Codificacion de datos<br />

Nombre<br />

tecnico<br />

Mapeado<br />

Cuantifiacion<br />

Codificacion<br />

<strong>Esquemas</strong><br />

generales<br />

Encontrar<br />

patrones<br />

unidimensionales<br />

Encontrar<br />

patrones<br />

bidimensionales<br />

Dividir por<br />

factores de precision<br />

Codigos de<br />

longitud fija<br />

Codigos de<br />

longitud variable<br />

Encontrar<br />

patrones<br />

tridimensionales<br />

Figura 4.2: <strong>Esquemas</strong> generales <strong>para</strong> los tres métodos principales de compresión.<br />

El nombre técnico de este conjunto de técnicas es mapeado, en el sentido de<br />

que mapean patrones de los datos originales a códigos en los datos transformados.<br />

Los patrones se pueden encontrar de tres formas:<br />

Búsqueda de patrones en una dimensión: los datos se procesan como una lista<br />

secuencial.<br />

Búsqueda de patrones en dos dimensiones: los datos se procesan como una<br />

matriz de dos dimensiones.<br />

Búsqueda de patrones en tres dimensiones: Los datos se procesan como una<br />

lista secuencial de matrices de dos dimensiones.<br />

A continuación describo cada uno de estos esquemas.<br />

Reconocimiento de Patrones en una Dimensión<br />

Si el conjunto de datos se trata como una lista de valores unidimensionales,<br />

entonces se pueden buscar patrones entre los valores adyacentes en la lista. Dentro<br />

de estos métodos, hay varios esquemas posibles:<br />

Reducción del número de datos. A su vez se divide en:<br />

• Codificación run-length: se basa en la repetición de valores adyacentes<br />

idénticos.<br />

• Codificación LZW: se basa en la repetición de valores adyacentes no<br />

idénticos.<br />

Reducción de la magnitud de los valores de los datos individuales.<br />

75


5 5 12 12 12 12 5 5 5 5 5 5 12 5<br />

Datos Originales (14 bytes)<br />

2 5 4 12 6 5 1 12 1<br />

Longitud: datos<br />

5<br />

Datos Comprimidos (10 bytes)<br />

Figura 4.3: Ejemplo de codificación run-length.<br />

• Diferenciación: Reemplazar cada dato con la diferencia con el dato<br />

anterior.<br />

A continuación describo cada uno de ellos:<br />

Codificación run-length:<br />

Es un esquema de compresión de reconocimiento de patrones que busca la repetición<br />

de valores idénticos en una lista. Los datos se pueden comprimir reemplazando la<br />

secuencia repetida, con el valor de los datos repetidos y el número de datos. Por<br />

ejemplo, en la Fig. 4.3 se muestra un ejemplo. Se puede ver que los dos últimos<br />

bytes de los datos originales requieren 4 bytes de los datos comprimidos. Esto es<br />

debido a la falta de repetición en los datos. Sin embargo, la mayoría de los esquemas<br />

run-length usados tienen esquemas <strong>para</strong> evitar estos casos de ineficiencia.<br />

Normalmente, en los métodos run-length, cada línea se codifica por se<strong>para</strong>do <strong>para</strong><br />

permitir métodos de almacenamiento en los que las líneas no se almacenen de forma<br />

secuencial. Además esto permite mejor recuperación de errores, si se corrompen los<br />

datos.<br />

El orden de los datos afecta a la eficiencia de los métodos run-legth, especialmente<br />

<strong>para</strong> imágeens en color. Si los valores de los pixels están ordenados por planos de<br />

color (todos los valores de rojo, después azul y después verde), es mucho más fácil<br />

que haya repeticiones entre datos adyacentes.<br />

La repetición en los valores de los pixels no es exclusiva, por supuesto de las<br />

líneas horizontales. Algunas imágenes digitales contienen más redundancia en las<br />

líneas verticales. Sin embargo, la complejidad añadida, no se ve compensada con<br />

la ventaja de implementar esquemas en ambas direcciones. Todos estos esquemas<br />

dependen de los datos. El objetivo es encontrar un método que funcione bien con<br />

un tipo de datos general, asociado con una aplicación concreta y asegurarse de que<br />

el peor caso ocurre sólo en raras ocasiones <strong>para</strong> esa aplicación.<br />

El tipo de datos afecta al esquema run-length usado. Por ejemplo, en las imagenes<br />

bilevel, sólo hay dos colores, por lo que no hace falta almacenar el color, sino simplemente<br />

el número de repeticiones de cada uno. El sistema de codificación en una<br />

76


5 23 7 12 5 23 7 6 12 5 23 7 6 12<br />

Datos Originales (14 bytes)<br />

C1 12<br />

C1 C2 C1 C2<br />

Datos Comprimidos (6 bytes)<br />

C1 y C2 representan codigos unicos<br />

Figura 4.4: Ejemplo de asignación de códigos únicos a secuencias de datos repetidas.<br />

Codificación LZW.<br />

dimensión, usado <strong>para</strong> Fax Grupo 3 de la CCITT emplea esta tipo de codificación<br />

<strong>para</strong> imágenes de dos niveles.<br />

Como conclusión, se puede decir que la tasa de compresión de los esquemas runlength,<br />

depende del tipo de datos y de la repetición presente en ellos. Compresiones<br />

típicas que se pueden conseguir con estos esquemas, son desde 2:1 hasta 5:1. Algunos<br />

formatos gráficos que usan este tipo de almacenamiento, además del Fax Grupo 3<br />

ya indicado, son PCX, PostScript y TIFF.<br />

Codificación LZW<br />

Si una secuencia de datos no idénticos, se repite más de una vez en el conjunto<br />

de datos, no necesariamente de forma contigua, entonces esta repetición se puede<br />

explotar <strong>para</strong> comprimir los datos. Se puede asignar a la secuencia repetida un código<br />

único y a continuación almacenar este código, en lugar de la secuencia completa. En<br />

la Fig. 4.4 se muestra un ejemplo. Contiene dos secuencias que se repiten, Hay<br />

otras secuencias que no se han marcado y se les ha asignado código, como por<br />

ejemplo, las secuencias (12, 5) y (5, 23, 7, 6, 12) se repiten dos veces, pero no se<br />

usan <strong>para</strong> codificar. Este ejemplo pone de manifiesto el problema de encontrar la<br />

mejor agrupación de los datos, <strong>para</strong> conseguir la mayor compresión.<br />

Encontrar la mejor forma de agrupar los datos supone examinar todas las posibles,<br />

lo cual es muy ineficiente computacionalmente. Lo que se suele hacer es “recordar”<br />

las secuencias de datos, según van apareciendo y las reconoce cuando se repiten.<br />

En el ejemplo visto, la secuencia (5, 23, 7), se encontró en primer lugar, y por tanto,<br />

se usa <strong>para</strong> codificar la siguiente ocurrencia de esta secuencia. De esta forma, no se<br />

encontró la secuencia más larga (5, 23, 7, 6, 12).<br />

Los códigos y las secuencias de datos asociados con los mismos, forman lo que<br />

se llama lista de códigos, y debe ser almacenada junto con los datos codificados,<br />

pues si no, el decodificador podría no tener forma de saber lo que representan los<br />

códigos. Si la lista de códigos es grande, la sobrecarga de almacenar la lista, puede<br />

disminuir, de forma importante la compresión alcanzada.<br />

77


Un esquema de compresión, desarrollado por Lempel-Ziv y Welch, normalmente<br />

llamado compresión LZW, evita tener que almacenar la lista de códigos. La compresión<br />

LZW tiene varias ventajas. Procesa los datos de forma secuencial, con lo que<br />

tiene pocos requerimientos de memoria. Se basa en que el codificador y el decodificador<br />

construyen la misma lista de códigos, a medida que la los datos son leídos de<br />

forma secuencial. El codificador sustituye una secuencia por su código, sólo cuando<br />

la secuencia se encuentra más de una vez. La primera vez que encuentra una secuencia,<br />

la escribe en su lista de códigos y escribe en la salida la secuencia sin codificar.<br />

El decodificador, por su parte, recibe esta secuencia y la coloca en su propia lista de<br />

códigos. Cuando el codificador ve una secuencia por segunda vez, escribe el código<br />

de su lista de códigos en la salida y el decodificador reconoce el código, porque ha<br />

generado una lista de códigos idéntica a la del codificador.<br />

La técnica de compresión LZW funciona bien en una gran variedad de casos. La<br />

tasa de compresión depende de los datos a comprimir. Por ejemplo, <strong>para</strong> dibujos, se<br />

puede conseduir una compresión de 16:1 o más. En el otro extremo, <strong>para</strong> fotografías<br />

escaneadas (como por ejemplo un paisaje), no se consigue compresión. Las compresiones<br />

típicas que se consiguen varían entre 9:1 y 2:1. Algunos formatos gráficos<br />

que usan este tipo de compresión son GIF, PostCript y TIFF. Otras utilidades de<br />

compresión, como compress de UNIX y arc de MS-DOS, también lo usan.<br />

Diferenciación Unidimensional<br />

La diferenciación es un esquema que trata de reducir el tamaño de cada elemento<br />

individual que forma la imagen. Los datos más pequeños se pueden codificar usando,<br />

entonces, menos bits.<br />

Las imágenes tienen, a menudo, valores parecidos en los pixels vecinos. En lugar<br />

de almacenar los valores de cada pixel, se almacena la diferencia entre el valor del<br />

pixel actual con el anterior (excepto <strong>para</strong> el primer pixel, cuyo valor sí se almacena).<br />

En muchos casos, la diferencia es mucho menor en magnitud que los valores<br />

origianales. Este esquema es el mismo que la modulación delta, vista <strong>para</strong> compresión<br />

de audio y voz.<br />

Combinando diferenciación con un método de minimización (método 3), se pueden<br />

conseguir tasas de compresión que varían enre 1.5:1 y 3:1. Estas tasas dependen de<br />

los datos. Alguos formatos gráficos que usan este tipo de compresión son: JPEG<br />

<strong>para</strong> la codificación sin pérdidas, JPEG y MPEG <strong>para</strong> los coeficientes de la DCT<br />

en compresión con pérdidas, y LANDSAT (Land Satellites) de la NASA.<br />

Reconocimiento de Patrones Bidimensionales<br />

Ahora se describen los esquemas más complejos, que tratan los datos como una<br />

matriz bidimensional. Se explotan los patrones que existen en subconjuntos bidi-<br />

78


mensionales de los datos. Hay varios esquemas posibles:<br />

Codificación Fractal: Encuentra patrones “auto-similares”.<br />

Codificación DCT: Usa la trasformada discreta del coseno.<br />

Diferenciación Bidimensional: Reemplaza cada dato con la diferencia con<br />

los anteriores.<br />

La codificación fractal está basada en una serie de transformaciones, en lugar de<br />

una transformación. La diferenciación en dos dimensiones es una extensión directa<br />

del esquema visto en una dimensión. La codificación DCT es un esquema que consigue<br />

una gran compresión, pero es un esquema con pérdidas. Reduce la significancia<br />

de muchos de los valores, lo que permite que los valores menos significantes puedan<br />

ser eliminados.<br />

Fractales<br />

Esta técnica de compresión reconoce patrones extensos contenidos en una imagen<br />

(o subconjuntos de una imagen). La idea principal es encontrar un conjunto<br />

de datos sencillo que se puede cambiar mediante transformaciones sucesivas, hasta<br />

convertirse en la imagen original (o una cercana aproximación). Estos pequeños<br />

conjuntos de datos son “auto-similares” a los datos originales. La compresión se consigue<br />

almacenando sólo el pequeño conjunto de datos y la serie de transformaciones.<br />

El proceso de codificación normalmente necesita grandes cantidades de tiempo <strong>para</strong><br />

encontrar los patrones “auto-similares”. Se pueden conseguir tasas de compresión<br />

hasta de 1000:1 en algunos tipos de datos. Las tasas de compresiones más típicas<br />

varían desde 50:1 hasta 100:1. El nivel de compresión también depende del tipo de<br />

datos.<br />

Diferenciación Bidimensional<br />

La diferenciación bidimensional es distinta <strong>para</strong> imágenes bilevel (bloco y negro)<br />

y <strong>para</strong> imágenes en color.<br />

Diferenciación Bidimensional par Imágenes en Blanco y Negro:<br />

Los estándars de CCITT <strong>para</strong> Fax Grupo 3 y Grupo 4, definen un esquema de<br />

diferenciación <strong>para</strong> imágenes bilevel. La idea principal es la siguiente: la mayoría de<br />

los pixels “negros”están agrupados juntos en una página. Si estamos al final de un<br />

conjunto de pixels “negros”, convendría poder “saltar” los siguientes pixels blancos<br />

79


<strong>para</strong> llegar al siguiente grupo de pixels “negros” 1 . En un caso típico, la anterior línea<br />

escaneada nos da una buena referenca de adónde saltar en la línea actual. Saltar<br />

muchos pixels, usando la anterior línea como guía de referencia, puede proporcionar<br />

buenas compresiones sin pérdidas, <strong>para</strong> ciertos tipos de datos, como imágenes de<br />

texto.<br />

Este esquema está diseñado <strong>para</strong> imágenes bilevel. No es efectivo <strong>para</strong> imágenes<br />

en color, a no ser que haya grandes áreas del color de fondo en la imagen.<br />

Diferenciación Bidimensional <strong>para</strong> Imágenes en Color:<br />

La idea <strong>para</strong> imágenes en color es exactamente la misma que en el caso unidimensional,<br />

excepto que el valor de la diferencia no tiene porqué calcularse necesariamente<br />

a partir del valor del pixel anterior en la misma línea.<br />

Combinando la diferenciación bidimensional con un método de minimización<br />

(método 3), se pueden conseguir tasas de compresión desde 1.5:1 hasta 3:1. La compresión<br />

conseguida depende del tipo de datos. Algunos ejemplos de uso de este<br />

esquema de compresión, son el JPEG y el MPEG <strong>para</strong> compresión sin pérdidas.<br />

Esquema DCT<br />

Existen muchas transformaciones matemáticas que, en general, transforman un<br />

conjunto de valores de un sistema de medida a otro. Algunas veces, los datos representados<br />

en el nuevo sistema, tienen propiedades que facilitan la compresión de<br />

los datos. Algunas de estas transformaciones matemáticas, se han inventado <strong>para</strong><br />

compresión y otras, usadas <strong>para</strong> otras aplicaciones, se han usado en compresión de<br />

datos. Una pequeña lista incluye:<br />

Transformada Discreta de Fourier (DFT).<br />

Transformada Discreta del Coseno (DCT).<br />

Transformadas de Hadamard-Haar (HHT).<br />

Transformadas de Karhunen-Loeve (KLT).<br />

Transformada de Slant-Haar (SHT).<br />

Transformada de Walsh-Hadamard (WHT).<br />

1 Los colores “blanco” y “negro” están entre comillas <strong>para</strong> indicar que su significado puede ser<br />

el contrario y la idea es la misma, es decir, cambiando el “blanco” por “negro” y viceversa.<br />

80


No voy a describir cada una de ellas, pues queda fuera de mi propósito. De todas<br />

las transformaciones anteriores, la DCT ha sido la técnica que ha predominado, y<br />

se ha usado en muchos formatos estándar de compresión. Las razones de esto son:<br />

La DCT tiene algunas propiedades computacionales apropiadas, principalmente<br />

un algoritmo rápido <strong>para</strong> hacer la transformada.<br />

Pruebas extensivas han mostrado que la DCT produce visualmente imágenes<br />

de mayor calidad, con tasas de compresión mayores que la mayoría de los otros<br />

esquemas de transformación.<br />

La DCT directa (FDCT) transforma un bloque de los datos originales al dominio<br />

transformado, mientras que la DCT inversa (IDCT), restaura los datos originales a<br />

partir de los del dominio transformado. En teoría no se pierde información cuando<br />

los datos se trasnforman y a continuación se restauran. Sin embargo, en la práctica,<br />

hay cierta pérdida de información debida a: 1) los valores del coseno no se pueden<br />

calcular de forma exacta, ya que son números transcendentales, 2) y cálculos repetidos<br />

usando números de precisión limitada introducen errores de redondeo en los<br />

resultados finales. La variaciones entre los datos originales y los restaurados suelen<br />

ser pequeñas, pero dependen del método usado <strong>para</strong> hacer la DCT.<br />

La DCT se puede aplicar a bloques de datos de cualquier tamaño, pero se ha<br />

visto, mediante pruebas, que con bloques de 8 × 8, se obtienen buenas tasas de<br />

compresión, manteniendo la calidad de la imagen. Además, cuando se empezaron<br />

a implementar comercialmente los métodos de compresión que usaban la DCT, un<br />

bloque de 8 × 8 se podía meter en un chip de lógica LSI, mientras que un bloque de<br />

16 × 16, no.<br />

En los esquemas de compresión de voz, ya se explicó la DCT unidimensional. La<br />

DCT bidimensional es una extensión de la anterior. Las relaciones que la definen,<br />

<strong>para</strong> un bloque de datos 8 × 8, son las siguientes:<br />

F DCT : S vu (v, u) = 1 ( ) ( )<br />

7∑ (2x + 1)uπ (2y + 1)vπ<br />

4 C uC v cos<br />

cos<br />

x=0<br />

16<br />

16<br />

IDCT : s yx (x, y) = 1 ( ) ( )<br />

7∑ (2x + 1)uπ (2y + 1)vπ<br />

4 C uC v cos<br />

cos<br />

y=0<br />

16<br />

16<br />

(4.8)<br />

(4.9)<br />

Igual que en una dimensión, se puede calcular la DCT a partir de la FFT bidimensional,<br />

extendiendo el bloque de datos de forma simétrica, en este caso <strong>para</strong><br />

formar cuatro bloques con simetría, en vez de 2. La idea es como si tuvieramos el<br />

bloque original y 3 espejos, <strong>para</strong> formar entre los 4 una matriz de bloques, con dos<br />

filas y dos columas, siendo todas ellas simétricas en cada dirección. La imagen original<br />

se puede colocar en cualquiera de las 4 posiciones. Una vez hecha la FFT de la<br />

81


matriz 32 × 32, se coge sólo la submatriz correspondiente al bloque original. Para la<br />

transformada inversa, IDCT, se hace lo mismo con la IFFT.<br />

Algunos formatos gráficos que usan este tipo de compresión son: JPEG <strong>para</strong><br />

compresión con pérdidas, MPEG <strong>para</strong> compresión con pérdidas de imágenes en<br />

movimiento, y el sistema de codificación del sistema de TV de alta definición (HDTV).<br />

Transformada Wavelet<br />

Este método se incluye entre los métodos de transformación junto con la DCT y<br />

todas las demás transformadas que he nombrado, pero debido a su importancia <strong>para</strong><br />

la realización del proyecto, se tratará en el capítulo 5. Es un método cuya aplicación<br />

a compresión de imagen es relativamente reciente, por lo que no hay ningún formato<br />

gráfico estándar que lo use hasta el momento (o por lo menos que sea de uso común).<br />

Reconocimiento de Patrones Tridimensionales<br />

En esta sección se discuten esquemas que tratan un conjunto de datos como<br />

una matriz tridimensional de valores. Los esquemas de reconocimiento de patrones<br />

en tres dimensiones son extensiones naturales de los de una y dos dimensiones.<br />

Dos candidatos naturales al método de diferenciación en tres dimensiones son los<br />

datos volumétricos (como las tomografías computerizadas y las resonancias<br />

magnéticas) y los datos de vídeo, en los que una secuencia de imágenes fijas forma<br />

una imagen en movimiento. La compresión de datos volumétricos <strong>para</strong> medicina es<br />

uno de los objetivos del proyecto, por lo que se estudiará con mayor detenimiento.<br />

Aquí me voy a referir a algunos métodos de compresión de vídeo. A cada imagen<br />

que forma el vídeo, se le llama frame.<br />

El método de compresión de vídeo consiste, simplemente, en comprimir cada<br />

frame por se<strong>para</strong>do (compresión intra-frame). Esto no es suficiente en muchos casos<br />

debido al gran número de frames que hay, hasta en secuencias muy cortas de vídeo.<br />

Se puede conseguir más compresión, aprovechando la redundancia que hay entre<br />

frames, debido al pequeño intervalo de tiempo entre frames consecutivos (compresión<br />

inter-frame).<br />

Diferencias entre frames<br />

Un esquema simple de compresión de vídeo, consiste en almacenar sólo los pixels<br />

que producen una diferencia distinta de cero, cuando se restan de los pixels correspondientes<br />

en el frame anterior. Los formatos de vídeo FLI y FLC usan este<br />

esquema.<br />

En el sistema FLI, el primer frame se codifica usando un esquema de codificación<br />

run-length. Los frames sucesivos se almacenan como “diferencias entre frames”, que<br />

82


es codificada como “líneas comprimidas”. Cada línea comprimida se compone de un<br />

conjunto de “paquetes run-length”, cada uno de los cuales está compuesto por tres<br />

valores: un salto, una longitud y un valor. El salto especifica el número de pixels<br />

que se saltan en la línea actual, a partir del pixel actual. Estos pixels se mantienen<br />

sin cambios desde el frame anterior. La longitud y el valor forman una codificación<br />

run-length. Se puede saltar una línea completa, si no se incluye ningún “paquete<br />

run-length” <strong>para</strong> ella. Este esquema se implementa fácilmente en software y es sin<br />

pérdidas, pero no permite altas tasas de compresión, en general.<br />

Compensación de Movimiento<br />

Otro esquema de compresión consiste en calcular las diferencias entre los pixels<br />

de frames consecutivos y codificar la diferencia, en lugar de los valores originales.<br />

Lo ideal es que las diferencias sean lo menor posibles, <strong>para</strong> mejorar la compresión.<br />

Como estamos tratando imágenes en movimiento, se puede asumir que los valores<br />

de los pixels se han desplazado de posición de un frame al siguiente. Si podemos<br />

encontrar ese desplazamiento, y calcular la diferencia con los pixels desplazados,<br />

podemos minimizar las diferencias. Esta técnica se conoce como compensación de<br />

movimiento.<br />

Realizar la compensación de movimiento de cada pixel por se<strong>para</strong>do, no facilita<br />

la compresión, pues habría que almacenar el desplazamiento y la diferencia de cada<br />

pixel. Sin embargo, realizar la compensación de movimiento de un conjunto de pixels<br />

puede ser muy eficiente. Si desplazamos un bloque de pixels de un frame, de tal<br />

forma que todas las diferencias se minimicen, entonces se puede aumentar la tasa<br />

de compresión aunque tengamos que almacenar el desplazamiento del bloque con<br />

los datos comprimidos. El desplazamiento se conoce técnicamente como vector de<br />

movimiento y se representa mediante un par de números que indican el desplazamiento<br />

en horizontal y en vertical.<br />

La mayoría de los métodos que usan compensación de movimiento, como por<br />

ejemplo MPEG, suelen usarlo junto con otro método de compresión, normalmente<br />

la DCT, por lo que el tamáno de los bloques suele ser de 8 × 8 o 16 × 16. Para<br />

calcular el vector de movimiento usan un método de predicción.<br />

4.3.4. Método 2: Reducción de la Precisión<br />

En algunas ocasiones, los datos gráficos tienen mayor precisión de la que necesitan<br />

tener, como por ejemplo el caso de una imagen a todo color <strong>para</strong> ser usada en un<br />

artículo de periódico en blanco y negro. Si se conoce la aplicación final de una<br />

imagen, entonces se puede eliminar la precisión innecesaria de los datos originales,<br />

y la reducción de precisión se puede aprovechar <strong>para</strong> comprimir los datos.<br />

83


En otras ocasiones, la información gráfica recogida produce datos que son más<br />

precisos de lo que el ojo humano puede distinguir. Un ejemplo típico es el de una<br />

imagen obtenida mediante escaneado digital de una fotografía. Por ejemplo, parte<br />

de una línea de escaneado puede tener los siguientes valores (niveles de intensidad<br />

de gris):<br />

122 121 123 122 121 122 123<br />

No hay repetición en la secuencia, pero los valores de los datos son muy similares.<br />

De hecho, esta parte de la imagen es, básicamente, un nivel de gris constante <strong>para</strong><br />

nuestro ojo, aunque el escáner haya detectado pequeñas variaciones de intensidad,<br />

que normalmente no son información necesaria. Para realizar este método se usa un<br />

cuantificador.<br />

Esto es lo mismo que ocurría con las señales de audio y de voz; podemos aprovechar<br />

el conocimiento del sistema visual humano <strong>para</strong> eliminar la información que no es<br />

capaz de distinguir, y de esta forma conseguir compresión. Por ejemplo, si tenemos<br />

una grabación de música con ruido de fondo. El ruido de fondo no es deseado, y<br />

puede corromper la pureza de la música. Por tanto, es beneficioso filtrar el sonido,<br />

aunque esto suponga perder parte de la información original.<br />

Lo mismo ocurre con nuestra imagen, la pequeña variación de intensidad, se<br />

puede considerar error, por lo que si se filtra, probablemente será más agradable<br />

<strong>para</strong> nuestro ojo.<br />

A pesar de las razones por las que puede interesar reducir la precisión de los<br />

datos, al hacerlo, tenemos un esquema de compresión con pérdidas, en el que los<br />

datos perdidos no pueden ser recuperados. Usado con moderación, puede facilitar la<br />

compresión de datos con una mínima pérdida de información <strong>para</strong> el ojo humano.<br />

Usado en exceso, puede producir cambios abruptos en los valores de intensidad de<br />

un pixel al siguiente, lo cual es muy perceptible <strong>para</strong> el ojo humano.<br />

Un esquema de compresión, que se puede englobar dentro de este grupo, específico<br />

<strong>para</strong> compresión de volúmenes es el diezmado de superficies. Debido a su uso<br />

dentro de la aplicación del proyecto, se describe al final del presente capítulo, en el<br />

apartado 4.3.7.<br />

4.3.5. Método 3: Minimización del Número de Bits<br />

El tercer método de compresión de datos consiste en la reducción del número<br />

total de bits <strong>para</strong> codificar los datos. El proceso de codificación asigna un valor<br />

de código único a cada elemento de los datos. Dependiendo del código usado, el<br />

valor puede representar a un sólo elemento de los datos, o bien a una secuencia<br />

de elementos. Hay muchos posibles esquemas de codificación, de los cuales voy a<br />

describir algunos de los principales.<br />

Códigos de longitud fija. Cada valor del código usa el mismo número de<br />

bits <strong>para</strong> su representación.<br />

84


Códigos de longitud variable. Todos los valores de código usados no son de<br />

la misma longitud; algunos códigos pueden tener sólo unos pocos bits, mientras<br />

que otros pueden tener muchos bits. Son códigos llamados de “entropía”, como<br />

por ejemplo los códigos de Huffman y aritmético.<br />

Código instantáneo. Dada una ristra de bits que representa una lista secuencial<br />

de valores de código, cada código puede ser reconocido de forma instantánea,<br />

cuando se recibe el último bit. No hace falta mirar en ningún sitio<br />

<strong>para</strong> reconocer que se ha recibido un valor de código completo.<br />

Ahora voy a describir, de forma un poco más concreta, algunos esquemas.<br />

Códigos de Desplazamiento<br />

Si en un conjunto de datos, en la mayoría de los elementos, los valores pertenencen<br />

a un pequeño subconjunto de los posibles valores, parece razonable seleccionar una<br />

longitud <strong>para</strong> el código que pueda representar este pequeño subconjuto de valores.<br />

Para los casos menos probables, en que un valor esté fuera de este subconjunto,<br />

se pone un código de “sobrecarga”, que señala el comienzo de otro conjunto de<br />

códigos, usados <strong>para</strong> representar estos valores que no ocurren tan a menudo. Se<br />

consigue compresión, porque la mayoría de los valores se codifican con un número<br />

mínimo de bits.<br />

Un sistema que usa este esquema es el LANDSAT de la NASA, que como hemos<br />

visto anteriormente, emplea también diferenciación unidimensional. Observaron que<br />

la mayoría de las diferencias estaban comprendidas entre -7 y +7, y muy pocas<br />

estaban fuera de este rango (entre -128 y -7 ó +7 y +127), por lo que se puede usar<br />

4 bits, en lugar de 8 bits. Los códigos de sobrecarga pueden ser 0000 <strong>para</strong> valores<br />

menores de -7 y 1111 <strong>para</strong> valores mayores o iguales de +7.<br />

Este tipo de código es muy directo y fácil de implementar. La compresión típica<br />

que consigue está entre 2:1 y 3:1 y varía según los datos a comprimir.<br />

Códigos Huffman<br />

Fueron inventados, como su propio nombre indica por D. A. Huffman en 1952.<br />

Asignan una longitud de código variable a cada elemento de datos, de forma que<br />

los valores que ocurren más a menudo en el conjunto de datos, tienen códigos de<br />

menor longitud y los menos probables tienen códigos de mayor longitud. Dada la<br />

probabilidad de ocurrencia de cada valor en los datos, el algoritmo de Huffman puede<br />

crear una asignación de códigos automática <strong>para</strong> cada valor.<br />

El algoritmo de Huffman se describe en cualquier libro de compresión, por ejemplo<br />

en [8]. Este esquema de compresión, y el que se describe después, de codificación<br />

aritmética, se suelen usar junto con otros esquemas (a continuación de ellos) de<br />

85


Probabilidades<br />

por Simbolo<br />

Palabra<br />

Codigo (0)<br />

0 0.5 0.5 0.5 0.5 0.5 0.5 1.0<br />

11<br />

(0)<br />

0.2 0.2 0.2 0.2 0.3 0.5<br />

(1)<br />

1000<br />

(0)<br />

0.1 0.1 0.12 0.18 0.2<br />

(1)<br />

1001<br />

(0)<br />

0.08 0.08 0.1 0.12<br />

(1)<br />

1011<br />

0.05<br />

(0)<br />

0.07<br />

0.08<br />

(1)<br />

10100<br />

(0)<br />

0.04 0.05<br />

(1)<br />

10101<br />

0.03<br />

(1)<br />

Figura 4.5: Ejemplo de codificación Huffman.<br />

compresión vistos, como transformación, predicción, sub-bandas, . . . , pues es mucho<br />

más eficiente codificar los datos de salida con un código de longitud variable que<br />

de longitud fija.<br />

El algoritmo comienza ordenando los elementos a codificar, según su probabilidad<br />

de ocurrencia en orden descendente, como se muestra en el ejemplo de la Fig. 4.5.<br />

En la segunda columna aparecen las probabilidades asociadas con los niveles de salida<br />

de una fuente de 7 símbolos. En la siguiente columna, se han sumado las dos<br />

probabilidades más pequeñas, y la probabilidad combinada se incluye (reordenada)<br />

en la tercera columna. El algoritmo prosigue de la misma forma hacia la derecha,<br />

hasta que se llega a una única probabilidad de 1.0. Ahora se asigna a cada probabilidad<br />

que ha entrado en una combinación un “1” o un “0” y leyendo estos bits de<br />

derecha a izquierda, se forman las palabras de código que aparecen en la columna<br />

de la izquierda.<br />

Si en vez de hacer una codificación con un número variable de bits, usáramos<br />

un código de longitud fija, necesitaríamos 3 bits de longitud (en teoría log 2 7 = 2,81<br />

bits/símbolo). Con el código Huffman conseguimos una longitud media de<br />

7∑<br />

¯L = P i L i = 2,17 bits/símbolo<br />

i=1<br />

86


donde P i es la probabilidad por símbolo y L i la longitud por símbolo.<br />

Se define entropía por símbolo de un código, como aparece en [2], de acuerdo<br />

con la teoría de la información como<br />

N∑<br />

H = − P i log 2 P i (4.10)<br />

i=1<br />

donde N es el número de símbolos, 7 en el ejemplo. La entropía por símbolo representa<br />

el contenido de información del código. No se puede diseñar un código con una<br />

longitud media por debajo de este valor (perderíamos información). En el ejemplo,<br />

la entopía por símbolo vale 2.15 bits/símbolo. Desafortunadamente, no hay un código<br />

<strong>para</strong> alcanzar este límite teórico de longitud, pues habría que asignar un número<br />

fraccionario de bits a una palabra de código. La codificación Huffman, aunque no es<br />

óptima, es una buena aproximación.<br />

El código Huffman, como indica [6], produce lo que se llama un código mínimo.<br />

Esto quiere decir que las asignaciones de códigos de Huffman son “lo mejor que se<br />

puede hacer” cuando se crea un esquema de codificación uno-a-uno (una palabra<br />

de código <strong>para</strong> cada elemento original). Los códigos de Huffman producen tasas de<br />

compresión típicas desde 1.5:1 a 2:1, pero dependen de los datos.<br />

Hay dos posibilidades básicas <strong>para</strong> crear los códigos de Huffman:<br />

Crear un código de Huffman genérico <strong>para</strong> una clase de datos general y que<br />

funcione bien en el “caso genérico”.<br />

Crear un nuevo código de Huffman <strong>para</strong> cada conjunto de datos que se codifiquen.<br />

Si se crea un código cada vez que se codifica un conjunto de datos, debemos<br />

almacenarlo junto con los datos comprimidos, <strong>para</strong> que el decodificador sepa interpretar<br />

las palabras de código. Esto reduce la eficiencia de la compresión. Si usamos<br />

un código de Huffman genérico, entonces no será el óptimo <strong>para</strong> el comjunto de<br />

datos comcreto a comprimir, pero no tenemos que almacenarlo. El mejor esquema<br />

depende del tamaño de los datos a comprimir (a mayor tamaño, menor sobrecarga<br />

supone guardar la tabla de traducción) y de la cantidad de similitudes que se pueden<br />

encontrar <strong>para</strong> el conjunto de imágenes a las que se aplica el mismo código genérico.<br />

Los códigos Huffman se usan en muchos formatos gráficos y esquemas de compresión<br />

genéricos. Entre los primeros se encuentra, por ejemplo, el Fax Grupo 3 de<br />

la CCITT, que lo usa <strong>para</strong> comprimir los valores de run-length; en JPEG y MPEG<br />

se usa <strong>para</strong> codificar los valores de cada bloque.<br />

Codificación Aritmética<br />

Los códigos aritméticos se basan en los valores de secuencias de datos. En lugar<br />

de asignar un código único a cada elemento individual de los datos, este esquema<br />

87


Salto<br />

Minimizacion de la<br />

cantidad de datos<br />

Mapeado<br />

Minimizacion de la<br />

precision de datos<br />

Cuantificacion<br />

Minimizacion del num.<br />

bits por elemento de datos<br />

Codificacion<br />

Repeticion<br />

Figura 4.6: Combinaciones típicas de métodos de compresión.<br />

produce una serie de valores, que corresponden a secuencias únicas de datos. Se usa<br />

la probabilidad de ocurrencia de cada dato individual <strong>para</strong> crear las palabras de<br />

código. No voy a descibir aquí la realización concreta de este tipo de algoritmo de<br />

compresión. Más información y ejemplos se pueden encontrar por ejemplo en [6] y<br />

[8].<br />

La codificación aritmética es más compleja de implementar y entender que la<br />

codificación Huffman, que he explicado por su sencillez y gran utilización. Sin embargo,<br />

puede producir entre un 5 % y un 10 % de mayor compresión.<br />

4.3.6. Combinación de Métodos de Compresión<br />

Algunos esquemas de compresión, como ya se ha indicado en los párrafos anteriores,<br />

combinan dos o más de los métodos explicados anteriormente, <strong>para</strong> conseguir<br />

tasas de compresión mayores de las que se puede conseguir con un sólo esquema<br />

de forma individual. Las posibilidades de combinación de esquemas que se pueden<br />

hacer son muy grandes, pero un esquema típico de compresión se consigue con un<br />

proceso secuencial de transformación, reducción de precisión y codificación. La codificación<br />

es siempre la etapa final del proceso, pero a veces hay varias iteraciones de<br />

transformación y reducción. Este proceso se muestra en la Fig. 4.6.<br />

En la mayoría de los casos, la distinción entre los esquemas de compresión sin<br />

pérdidas y con pérdidas, depende de la exclusión o inclusión de la etapa de cuantificación.<br />

Los esquemas que incluyen etapa de cuantificación, son siempre con pérdidas.<br />

Los esquemas de compresión sin pérdidas pueden conseguir una tasa de compresión<br />

limitada. No describo aquí los métodos de compresión estándar, como JPEG<br />

y MPEG, que usan básicamente combinaciones de los esquemas que he explicado.<br />

Una descripción de los mismos se puede encontrar por ejemplo en [6].<br />

88


4.3.7. Diezmado de Superficies<br />

Este esquema de compresión, se puede englobar, como ya se dijo anteriormente,<br />

dentro de los métodos que reducen la precisión (método 2). Pero como es un método<br />

que se emplea <strong>para</strong> la realización del proyecto, se ha creído más conveniente<br />

describirlo en otro apartado.<br />

Como todos los esquemas que reducen la precisión de los datos, es un esquemas<br />

de compresión con pérdidas. Por otro lado, como en casi todos los métodos de<br />

compresión de datos volumétricos, se busca que reduzca bastante el tamaño de los<br />

datos, aunque se incluya cierto error o distorsión.<br />

Las técnicas de reducción de polígonos [12], [14], reducen el número de<br />

polígonos necesarios <strong>para</strong> modelar un objeto. El tamaño de los modelos, en términos<br />

del número de polígonos, ha crecido tremendamente durante los últimos años. La<br />

causa de esto es que muchos modelos se han creado usando dispositivos digitales<br />

de medida, tales como escáners láser o satélites. Estos dispositivos pueden generar<br />

datos a tasas enormes. Por ejemplo, un digitalizador láser puede generar del orden<br />

de quinientos mil triángulos en una exploración de 15 segundos. Los algoritmos de<br />

visualización, como marching cubes, descrito en el apartado 3.4, también pueden<br />

generar un gran número de polígonos. Por ejemplo, un número típico de triángulos<br />

generados mediante marching cubes <strong>para</strong> un volumen de 512 3 , es de uno a tres<br />

millones.<br />

Una técnica de reducción de polígonos es el algoritmo de diezmado [12]. El<br />

objetivo de este algoritmo es reducir el número total de triángulos de una malla triangular,<br />

manteniendo la topología original y obteniendo una buena aproximación a<br />

la geometría original. Una malla triangular es una forma especial de malla poligonal,<br />

en la que todos los polígonos son triángulos. Si es necesario, una malla poligonal se<br />

puede convertir en malla triangular, usando algún método de triangulación, como el<br />

método de Delaunay, descrito por ejemplo en [12], pág. 398. En nuestro caso, como<br />

usamos el método de obtención de isosuperficies marching cubes, ya tenemos una<br />

malla triangular.<br />

Algunas características del algoritmo de diezmado son las siguientes:<br />

Sólo trata mallas triangulares.<br />

La elección de qué puntos borrar es función del criterio de diezmado, una<br />

medida del error local introducido al borrar un punto.<br />

La triangulación del hueco creado al borrar el punto se llava a cabo de forma<br />

que se preserven los bordes u otras carcterísticas importantes.<br />

El algoritmo de diezmado se lleva a cabo visitando iterativamente cada punto de la<br />

malla triangular. Para cada punto se llevan a cabo tres pasos, como se muestra en<br />

la Fig. 4.7, clasificación del punto, criterio de diezmado y triangulación.<br />

89


Clasificacion del Vertice<br />

Simple Complejo Fronterizo Borde<br />

Esquina<br />

Interior<br />

Distancia al Plano<br />

Evaluacion del Error<br />

distancia a la Linea<br />

d<br />

d<br />

Triangulacion 3D<br />

Recursiva<br />

Triangulacion<br />

Subdivision<br />

con planos<br />

Figura 4.7: Los tres pasos del algoritmo de diezmado.<br />

90


Clasificación del Punto. El primer paso del algoritmo de diezmado caracteriza la<br />

geometría local y la topología de un punto dado. El resultado de la clasificación<br />

determina si el vértice es un posible candidato a ser borrado o no, y si lo es,<br />

que criterio usar.<br />

Cada punto se puede clasificar en cinco tipos distintos: simple, complejo, fronterizo,<br />

borde interior y esquina. En la Fig. 4.7 se muestra un ejemplo de cada<br />

uno de ellos.<br />

Un punto simple está rodeado por un ciclo completo de triángulos, y cada<br />

lado que está conectada a ese punto, es usado por dos triángulos, exactamente.<br />

Si el lado no es usado por dos triángulos, o si el punto es usado por un triángulo<br />

que no esté en el ciclo de triángulos, entonces el punto es complejo. Un<br />

punto que está en la frontera de la malla, que está dentro de un semiciclo<br />

de triángulos, es un punto fronterizo. Un punto simple, se puede además<br />

clasificar en: borde interior y esquina. Estas clasificaciones se basan en la<br />

geometría local de la malla.<br />

Si el ángulo entre las normales a la superficie de dos triángulos adyacentes,<br />

es mayor que un determinado ángulo característico, entonces existe un borde<br />

característico. Cuando un punto es usado por dos bordes característicos, el<br />

punto es un borde interior. Si uno, tres, o más bordes característicos usan<br />

el punto, entonces el punto es una esquina.<br />

Los vértices complejos y las esquinas no se borran de la malla triangular; todos<br />

los demás puntos son candidatos a ser borrados.<br />

Criterio de Diezmado. Una vez que tenemos un candidato a ser borrado, estimamos<br />

el error que resultaría de borrar el punto y reemplazarlo (y sus triángulos<br />

asociados) con otra triangulación. Hay varias posibles medidas del error,<br />

pero la más simple está basada en distancias de planaridad local o colinearidad<br />

local (Fig. 4.7).<br />

En la región local que rodea a un punto simple, la malla se considera “casi<br />

plana”, ya que por definición no hay ángulos característicos. Los puntos simples<br />

usan una medida del error basada en la distancia al plano. El plano que pasa<br />

a través de la región local se puede calcular, por ejemplo, mediante mínimos<br />

cuadrados.<br />

Los puntos clasificados como fronterizos o bordes interiores, se considera<br />

que están en un borde, y usan como medida de error la distancia al borde. Es<br />

decir, se calcula la distancia a la que se encuentra el punto candidato a ser<br />

borrado, al nuevo borde que se forma durante el proceso de triangulación.<br />

Un punto satisface el criterio de diezmado, d, si su medida de distancia es<br />

menor que d. En ese caso, el punto puede ser borrado. Todos los triángulos<br />

que usan el triángulo también se borran, dejando un hueco en la malla. El<br />

hueco se tapa mediante el proceso de triangulación local.<br />

91


Triangulación. Después de borrar un punto, el hueco resultante debe ser retriangulado.<br />

Aunque el hueco, definido por un bucle de bordes, es topológicamente<br />

bidimensional, generalmente es no-planar, con lo que no se pueden usar técnicas<br />

de triangulación 2D. En su lugar se usa una técnica recursiva especial 3D,<br />

del tipo “división y conquista”.<br />

El proceso es el siguiente. Se elige un plano inicial de división, <strong>para</strong> se<strong>para</strong>r<br />

el bucle en dos sub-bucles. Si todos los puntos de cada sub-bucle están en<br />

lados opuestos del plano, entonces la división es válida. Además, se hace una<br />

comprobación de la relación de aspecto, <strong>para</strong> asegurarse de que el bucle<br />

generado no es demasiado largo y fino, produciendo triángulos como agujas.<br />

La relación de aspecto es el cociente entre la longitud de la línea de división y<br />

la mínima distancia de un punto del sub-bucle al plano de división. Si el plano<br />

de división no es válido, o no satisface el criterio de la relación de aspecto,<br />

se busca otro plano de división candidato. Una vez encontrado un plano de<br />

división válido, continúa la división de cada sub-bucle recursivamente, hasta<br />

que se llega a un sub-bucle que contiene tres lados. En este caso, el sub-bucle<br />

genera un triángulo y se <strong>para</strong> el proceso recursivo.<br />

En algunas ocasiones, la triangulación falla, a causa de que no se encuentran<br />

planos de división válidos. En este caso, el punto candidato no se borra, y<br />

la malla se deja en su estado original. Esto no es ningún problema <strong>para</strong> el<br />

algoritmo, y el diezmado continúa visitando el siguiente punto de la malla.<br />

Resultados. Relaciones de compresión típicas varían de 2:1 a 100:1, siendo<br />

10:1 una cifra nominal <strong>para</strong> mallas “grandes” (por ejemplo con 10 5 triángulos). Los<br />

modelos de CAD, típicamente son los que menos se reducen, ya que tienen muchos<br />

bordes agudos y otras características detalladas; además los modeladores de CAD<br />

suelen producir triangulaciones mínimas. Los datos de terreno, especialmente de<br />

zonas relativamente planas, pueden reducirse hasta 100:1.<br />

La ventaja del diezmado con respecto al sub-muestreado, es que con el diezmado,<br />

la malla se modifica adaptativamente <strong>para</strong> retener los máximos detalles en<br />

las áreas de máxima curvatura. El diezmado elimina puntos de forma homogénea,<br />

independientemente de las características de los mismos.<br />

Técnicas Avanzadas<br />

La reducción de polígonos es un campo de investigación en estos momentos. Se<br />

han presentado muchos algoritmos más allá del diezmado. Dos de las principales<br />

líneas de estos algoritmos son:<br />

<strong>Esquemas</strong> progresivos: permiten transmisión incremental y reconstrucción de<br />

mallas triangulares. Esto es especialmente importante <strong>para</strong> visualización de<br />

geometría basada en la Web, o usada <strong>para</strong> ser transmitida a través de una red.<br />

92


Algunos algoritmos recientes modifican la topología de la malla. Esto es esencial<br />

<strong>para</strong> obtener niveles arbitrarios de reducción de la malla.<br />

Una red progresiva está formada por una serie de mallas triangulares, M i ,<br />

relacionadas entre sí por las operaciones<br />

( ˆM = M n ) → . . . → M 1 → M 0 (4.11)<br />

donde ˆM y M n representan la malla a resolución completa, y M 0 es una malla base<br />

simplificada. La característica clave de las mallas progresivas, es la posibilidad de<br />

elegir las operaciones de la malla, de tal forma que sean invertibles. De esa forma,<br />

las operaciones se pueden aplicar en orden inverso (empezando con la malla base<br />

M 0 )<br />

M 0 → M 1 → . . . → M n−1 → M n (4.12)<br />

<strong>para</strong> obtener una malla con el nivel deseado de reducción (asumiendo que el nivel<br />

de reducción es menor que el de la malla base M 0 ).<br />

Un operador invertible de ese tipo es la fusión del borde, y su inverso es la<br />

división del borde, mostrados en la Fig. 4.8(a). Cada fusión de un borde interior<br />

de la malla produce, como resultado, la eliminación de dos triángulos (o un triángulo<br />

si el vértice que se funde está un una frontera). La operación se representa por cinco<br />

valores<br />

Fusión/División de Borde(v s , v t , v l , v r , A) (4.13)<br />

donde v s es el vértice que se fusiona o se divide, v t es el vértice al que se fusiona<br />

o del que se divide, y v l y v r son dos vértices adicionales a la izquierda del borde<br />

fusionado o dividido. Estos dos vértices, junto con v s y v t definen los dos triángulos<br />

borrados o añadidos. A representa los atributos de información del vértice, que como<br />

mínimo contiene las coordenadas del vértice fusionado o dividido v s . 2<br />

El algoritmo de mallas progresivas, nos permite almacenar y transmitir de forma<br />

compacta las mallas de triángulos; sin embargo, se mantiene el problema de que el<br />

tamaño de la malla base es a menudo mayor que el nivel de reducción deseado. Como<br />

en algunas aplicaciones deseamos conseguir cualquier nivel de reducción, queremos<br />

que la malla base no tenga ningún triángulo<br />

( ˆM = M n ) → M n−1 → . . . → M 1 → (M 0 = M(V, ∅)) (4.14)<br />

Para conseguir esto, se extiende el operador de fusión/división de bordes –que conserva<br />

la topología– con un operador de división/fusión de vértices. Este operador<br />

modifica la topología de la malla y permite niveles de reducción arbitrarios.<br />

Se produce una división de la malla cuando reemplazamos el vértice v s con el<br />

vértice v t , <strong>para</strong> uno o más de los triángulos que usaban originalmente el vértice v s (Fig. 4.8(b)).<br />

2 En el contexto del algoritmo de diezmado, el operador de fusión del borde sustituye al proceso<br />

recursivo de triangulación.<br />

93


vr<br />

Fusion<br />

vr<br />

vs<br />

Division<br />

vt<br />

vt<br />

vl<br />

vl<br />

(a) Fusion/Division de borde<br />

vr<br />

Division<br />

vr<br />

vs<br />

Fusion<br />

vs<br />

vt<br />

vl<br />

vl<br />

(b) Division/Fusion de vertice<br />

Borde<br />

Interior<br />

Esquina<br />

Complejo<br />

Otros<br />

tipos<br />

(c) Division de veritices aplicada a varios tipos de puntos.<br />

Las lineas mas gruesas indican bordes caracteristicos.<br />

Figura 4.8: Operadores <strong>para</strong> crear mallas progresivas: fusión/división de bordes y<br />

división/fusión de vértices.<br />

94


Al nuevo vértice, v t , se le dan exactamante las mismas coordenadas que a v s . Las<br />

divisiones introducen una “rotura” o “agujero” en la malla. Es preferible no dividir<br />

la malla, pero <strong>para</strong> altas tasas de reducción, evita limitaciones de la topología y<br />

permite más diezmado. Sólo se realiza esta división de la malla cuando no se puede<br />

encontrar un lado válido <strong>para</strong> fusionar o cuando un vértice no puede ser triangulado<br />

(por ejemplo en los vértices complejos). Una vez ocurre la operación de división, se<br />

introducen los vértices v s y v t en la cola de evaluación de la eliminación de puntos.<br />

Dependiendo de la clasificación del punto se usan distintas técnicas de división<br />

Fig. 4.8(c)). Los vértices de los bordes interiores y de las esquinas, se dividen a lo<br />

largo de los lados característicos. Los vértices complejos se dividen en partes se<strong>para</strong>das,<br />

que sean topológicamente 2D. En cualquier otro tipo de vértice, la división<br />

se realiza se<strong>para</strong>ndo de forma arbitraria el bucle, en dos partes. Por ejemplo, si un<br />

vértice simple no se puede borrar, porque no se encuentra un lado de fusión válido, el<br />

bucle de triángulos se divide automáticamente, de forma arbitraria, en dos mitades.<br />

De la misma forma que la fusión/división de bordes, la división/fusión de vértices,<br />

también puede representarse como una operación compacta. Una operación de división/fusión<br />

de vértices se puede representar con cuatro valores<br />

División/Fusión de Vértice(v s , v t , v l , v r ) (4.15)<br />

donde los vértices v l y v r definen una tira de triángulos (desde v r hasta v l ) que se<br />

van a se<strong>para</strong>r del vértice original v s .<br />

95


Capítulo 5<br />

Transformada Wavelet<br />

5.1. Introducción<br />

Como ya hemos visto, la tomografía computerizada, la resonancia magnética y<br />

los ultrasonidos, tienen cada vez más importancia en los diagnóticos médicos. Sin<br />

embargo, el problema es su enorme tamaño, que hace de los datos obtenidos con<br />

estas técnicas, difíciles de manejar, almacenar y transmitir. Por tanto sería deseable<br />

una técnica de compresión de estos datos volumétricos, que aunque tenga pérdidas,<br />

mantenga la forma fundamental de los datos y sus estructuras.<br />

Pero además esta técnica debe ser multirresolución, es decir, permitir que unos<br />

datos se muestren a distintos niveles de resolución (según el nivel de compresión);<br />

e incluso, dentro de los mismos datos, la resolución varíe de unos puntos a otros.<br />

Esto último es especialmente importante en medicina, pues el médico, normalmente<br />

realiza su estudio y posterior diagnóstico a partir de una pequeña zona de los datos,<br />

en la que está la lesión, tumor, . . . Sin embargo, no le podemos dar exclusivamente<br />

una imagen de esta zona, pues debe tener una idea espacial de una zona más amplia.<br />

Por ello, nos interesa emplear una técnica que permita obtener una zona con mayor<br />

detalle (menor compresión) que el resto.<br />

Para conseguir distintas resoluciones de una señal (1D, 2D, 3D, . . . ), se han empleado<br />

a lo largo de los años distintos métodos, como codificación en sub-bandas mediante<br />

la transformada de Fourier o del coseno (descrita en el capítulo 4), armónicos<br />

esféricos, multirresolución piramidal . . . [9], [11]. El mayor problema de todas estas<br />

técnicas es que los resultados de la transformación no tienen localidad espacial, con<br />

lo que no se puede obtener una representación con distintos niveles de resolución<br />

de una imagen o volumen. También se han usado técnicas más complejas, como el<br />

modelo de Blobby [9], que permitía aproximar la forma de un objeto con un número<br />

pequeño de primitivas. Sin embargo, la aproximación de las primitivas a los datos, se<br />

realiza mediante un problema de minimización, que, además de sólo dar una solución<br />

local, implica un enorme número de operaciones.<br />

97


Una versión multirresolución de una señal (1D, 2D, 3D, . . . ), consiste en reorganizar<br />

la información de la señal en un conjunto de detalles, a diferentes resoluciones<br />

[11] (y una versión a baja resolución). Dada una secuencia de resoluciones crecientes<br />

(r j ) j∈Z , los detalles de la señal a la resolución r j , se definen como la diferencia de información<br />

entre su aproximación a la reolución r j y su aproximación a la resolución<br />

inferior, r j−1 .<br />

Una descomposición multirresolución nos permite tener una interpretación invariante<br />

con la escala del volumen, en nuestro caso. La escala varía con la distancia<br />

entre la escena y el centro óptico de la cámara. Cuando se modifica la escala, nuestra<br />

interpretación de la escena no debería cambiar. Una representación multirresolución<br />

puede ser parcialmente invariante con la escala, si la secuencia de los parámetros<br />

de resolución (r j ) r∈Z varía exponencialmente. Supongamos que existe un paso de<br />

resolución α ∈ R de tal forma que <strong>para</strong> todos los enteros j, r j = α j (en general se<br />

elige α = 2). Si la cámara se acerca α veces a la escena, cada objeto de la escena<br />

se proyecta sobre el plano focal de la cámara en un área α 2 veces mayor. Esto es,<br />

cada objeto se mide a una resolución α veces mayor. De esta forma, los detalles de<br />

la nueva imagen a la resolución α j , se corresponden con los detalles de la imagen<br />

previa a la resolución α j+1 . Reescalando el volumen por α, se trasladan los detalles<br />

de la imagen a lo largo del eje de resolución. Si los detalles de la imagen se procesan<br />

idénticamente en todas las resoluciones, entonces nuestra interpretación de la<br />

información del volumen no se modifica.<br />

Una representación multirresolución ofrece un sistema de organización jerárquico<br />

y sencillo <strong>para</strong> interpretar la información de la imagen. Esto se suele usar <strong>para</strong> reconocimiento<br />

de imágenes por ordenados, pero como el médico también tiene que<br />

reconocer e interpretar la imagen, es totalmente aplicable. A las diferentes resoluciones,<br />

los detalles de una imagen caracterizan estructuras físicas distintas de la<br />

escena. A muy baja resolución, los detalles forman las estructuras mayores, que<br />

proporcinan una información de contexto. Es interesante y adecuado, por tanto,<br />

analizar primero la imagen a baja resolución y progresivamente incrementar la<br />

resolución (posiblemente no en todas las zonas del volumen).<br />

La representación multirresolución es invertible, por lo que tenemos la misma<br />

información que en el volumen original (salvo por los pequeños errores de redondeo<br />

de todos los sistemas digitales).<br />

Al contrario que con otras representaciones multirresolución (como la piramidal)<br />

[11], con la transformada wavelet, los datos a distintos niveles están incorrelados,<br />

lo cual hace de esta transformada, la más adecuada <strong>para</strong> obtener nuestra<br />

representación multirreoslución de los datos y posterior compresión, pues al estar<br />

los datos incorrelados, no tenemos información redundante y se podrá hacer una<br />

representación más compacta.<br />

La transformada wavelet es una técnica de análisas de señal, que descompone una<br />

señal en una familia de funciones con propiedades de localidad, tanto en el dominio<br />

98


del espacio (tiempo en 1D), como en el de la frecuencia. Aplicando la transformada<br />

wavelet ortogonal en 3D, podemos expresar un volumen de datos, como una suma<br />

de funciones ortogonales (funciones de escalado y wavelets). Esto permite obtener<br />

una expresión multirresolución de los datos volumétricos.<br />

Para realizar una compresión del volumen, como es nuestro objetivo, lo que<br />

se hace es obtener la representación multirresolución del volumen, mediante la transformada<br />

wavelet ortogonal en 3D (compresor), <strong>para</strong> a continuación quedarnos sólo<br />

con las funciones, o coeficientes, más significativos, y eliminando el resto. Realizando<br />

la transformación inversa (descompresor), obtenemos una aproximación multirresolución<br />

(según el nivel de compresión) al volumen original, en la que, con un número<br />

pequeño de funciones, obtenemos las formas fundamentales del objeto. Además, como<br />

he dicho, se puede obtener una aproximación multirresolución, en el sentido<br />

de permitir distintas resoluciones dentro del mismo volumen de datos, debido a la<br />

localidad espacial y en frecuencia de la transformada wavelet.<br />

5.2. Expresión Multirresolución y Wavelets Ortogonales<br />

En primer lugar, voy a describir la transformada wavelet en una dimension, <strong>para</strong><br />

a continuación, extenderla a 3 dimensiones; algo que será casi inmediato.<br />

Una expresión multirresolución de una función f(x), medible y de energía finita<br />

(f(x) ∈ L 2 (R)), como se indica en [11], [9], consiste en una jerarquía de espacios de<br />

aproximación a f(x), definidos a diferentes escalas. Llamamos A 2 j al operador que<br />

aproxima la señal a la resolución 2 j . Podemos caracterizar A 2 j por las propiedades<br />

que se pueden esperar de un operador de este tipo [11]:<br />

1. A 2 j es un operador lineal. Si A 2 jf(x) es la aproximación de una función f(x) a<br />

la reolución 2 j , entonces A 2 jf(x) no se modifica si lo aproximamos otra vez a la<br />

resolución 2 j . Este principio muestra que A 2 j ◦A 2 j = A 2 j. Por tanto, el operador<br />

A 2 j es un operador de proyección en el espacio vectorial V 2 j ⊂ L 2 (R). El<br />

espacio vectorial V 2 j se puede interpretar como todo el conjunto de posibles<br />

aproximaciones a la resolución 2 j de funciones en L 2 (R).<br />

2. Entre todas las funciones de aproximación a la resolución 2 j , A 2 jf(x) es la<br />

función más parecida a f(x)<br />

∀g(x) ∈ V 2 j, ‖g(x) − f(x)‖ ≥ ‖A 2 jf(x) − f(x)‖ (5.1)<br />

Por tanto, el operador A 2 j<br />

espacio vectorial V 2 j.<br />

es un operador de proyección ortogonal en el<br />

99


3. La aproximación de una señal a la resolución 2 j+1 contiene toda la información<br />

necesaria <strong>para</strong> calcular la misma señal a una resolución menor, 2 j . Esta en una<br />

propiedad de causalidad. Como el operador A 2 j es un operador de proyección,<br />

este principio es equivalente a<br />

∀j ∈ Z, V 2 j ⊂ V 2 j+1 (5.2)<br />

4. Una operación de aproximación es similar en todas las resoluciones. Los epacios<br />

de las funciones de aproximación deben derivarse por tanto uno de otro,<br />

escalando cada función de aproximación por la relación de sus valores de resolución.<br />

∀j ∈ Z, f(x) ∈ V 2 j ⇔ f(2x) ∈ V 2 j+1 (5.3)<br />

5. La aproximación A 2 jf(x) de una señal f(x), se puede caracterizar por 2 j muestras<br />

por unidad de longitud. Cuando se traslada f(x) una longitud proporcional<br />

a 2 −j (longitud proporcional a una muestra, es decir, un número entero<br />

de muestras), A 2 jf(x) se traslada la misma cantidad, y se caracteriza por las<br />

mismas muestras que han sido trasladadas. Como consecuencia de la Eq. (5.3),<br />

es suficiente con expresar esta propiedad a la resolución j = 0. Las traslaciones<br />

matemáticas consisten en lo siguiente:<br />

Caracterización discreta:<br />

Traslación de la aproximación:<br />

Existe un isomorfismo I de V 1 en I 2 (Z) (5.4)<br />

∀k ∈ Z, A 1 F k (x) = A 1 f(x − k), donde f k (x) = f(x − k) (5.5)<br />

Traslación de las muestras:<br />

I (A 1 f(x)) = (α i ) i∈Z ⇔ I (A 1 f k (x)) = (α i−k ) i∈Z (5.6)<br />

6. Cuando se calcula una aproximación de f(x) a la resolucción 2 j , se pierde parte<br />

de la información de f(x). Sin embargo, a medida que la resolución crece hacia<br />

+∞, la señal aproximada debe converger con la señal original. Por el contrario,<br />

a medida que la resolución disminuye hasta cero, la señal aproximada tiene<br />

menos y menos información y converge a cero.<br />

Como la señal aproximada a la resolución 2 j es igual a la proyección ortogonal<br />

sobre el espacio vectorial V 2 j, este principio se puede expresar de la siguiente<br />

forma<br />

+∞ lím V 2<br />

j→+∞ j = ⋃<br />

V 2 j es denso en L 2 (R) (5.7)<br />

y<br />

j=−∞<br />

lím<br />

j→−∞ V 2 j<br />

= +∞ ⋂<br />

j=−∞<br />

V 2 j = {0} (5.8)<br />

100


A cualquier conjunto de espacios vectoriales (V 2 j) j∈Z que satisface las Ecs. (5.2)-<br />

(5.8), se le llama aproximación multirresolución de L 2 (R). El conjunto asociado<br />

de operadores A 2 j que satisfacen las Ecs. (5.1)-(5.6), dan una aproximación de<br />

cualquier función de L 2 (R) a una resolcuión 2 j .<br />

Mallat [11] determinó la relación entre el análisis multirresolución y las transformadas<br />

wavelet, y presentó un método <strong>para</strong> construir una wavelet ortogonal, mediante<br />

la introducción de los siguientes tres teoremas:<br />

Teorema 1: Sea (V 2 j) j∈Z una aproximación multirresolución de L 2 (R). Entonces<br />

existe una única función φ(x) ∈ L 2 (R), llamada función de escalado, de tal<br />

forma que<br />

(√<br />

2j φ(2 j x − n) ) (5.9)<br />

n∈Z<br />

es una base ortonormal de V 2 j.<br />

Teorema 2: Sea φ(x) una función de escalado, y sea h un filtro discreto con respuesta<br />

al impulso h(n) =< 2 −1 φ(2 −1 u), φ(u − n) >. Sea H(ω) la serie de<br />

Fourier definida por<br />

H(ω) =<br />

+∞ ∑<br />

n=−∞<br />

H(ω) satisface las siguientes propiedades:<br />

h(n)e −inω (5.10)<br />

|H(0)| = 1 y h(n) = O(n 2 ) en el infinito.<br />

|H(ω)| 2 + |H(ω + π)| 2 = 1.<br />

Por otro lado, sea H(ω) una serie de Fourier que satisface estas propiedades,<br />

y tal que<br />

|H(ω)| ≠ 0 <strong>para</strong> ω ∈ [0, π/2].<br />

La función definida por<br />

ˆφ(ω) =<br />

+∞ ∏<br />

p=1<br />

H(2 −p ω) (5.11)<br />

es la transformada de Fourier de la función de escalado. La notación ˆ indica<br />

transformada de Fourier.<br />

Teorema 3: Sea (V 2 j) j∈Z una serie de espacios vectoriales multirresolución, φ la<br />

función de escalado, y ¯h el correspondiente filtro conjugado. Sea ψ(x) la función<br />

cuya transformada de Fourier viene dada por<br />

( ) ( ) ω ω<br />

ˆψ(ω) = G ˆψ<br />

(5.12)<br />

2 2<br />

con<br />

G(ω) =<br />

+∞ ∑<br />

n=−∞<br />

g(n)e −inω = e −iω H(ω + π) (5.13)<br />

101


Sea O 2 j el complemento ortogonal de V 2 j, en V 2 j+1. V 2 j+1 se puede escribir<br />

como<br />

V 2 j+1 = V 2 j ⊕ O 2 j (5.14)<br />

Entonces:<br />

(√<br />

2j ψ(2 j x − n) ) (5.15)<br />

n∈Z<br />

es una base ortonormal de O 2 j, y<br />

(√<br />

2j ψ(2 j x − n) ) (5.16)<br />

(n,j)∈Z 2<br />

es una base ortonormal de L 2 (R). A ψ(x) se le llama wavelet ortonormal.<br />

Como hemos visto, la Ec. (5.9) constituye una base ortonormal de V 2 j, y la<br />

Ec. (5.15) constituye una base ortonormal de O 2 j . Por tanto, como indican [11],<br />

[10], podemos aproximar cualquier función de L 2 (R), f(x) a la resolución 2 j como<br />

A 2 jf =<br />

=<br />

+∞ ∑<br />

n=−∞<br />

+∞ ∑<br />

n=−∞<br />

< f(u), √ 2 j φ(2 j u − n) > √ 2 j φ(2 j x − n)<br />

(A d 2 jf) nφ(2 j x − n) (5.17)<br />

y la proyección ortogonal de f(x) sobre el espacio O 2 j, se descompone como<br />

P O2 j f = +∞ ∑<br />

=<br />

n=−∞<br />

+∞ ∑<br />

n=−∞<br />

< f(u), √ 2 j ψ(2 j u − n) > √ 2 j ψ(2 j x − n)<br />

(D 2 jf) n ψ(2 j x − n) (5.18)<br />

El símbolo < f(x), g(x) >, denota el producto interno de dos funciones L 2 (R),<br />

f(x) y g(x). A d 2 j f y D d 2 j f se llaman respectivamente aproximación discreta de<br />

f(x) y señal de detalle discreta de f(x) a la resolución 2 j . En la práctica, estas<br />

sumas se hacen sólo donde hay datos.<br />

En la Fig. 5.1, se muestra la función de escalado, φ(x) que se describe en [11] y<br />

el módulo de su transformada de Fourier, ˆφ(x). Se puede ver que las funciones de<br />

escalado son filtros paso bajo. Esto es lógico, pues van quitando detalle a la señal.<br />

Esta función de escalado es continuamente diferenciable y exponencialmente<br />

decreciente. En la Fig. 5.2, aparece la función wavelet ψ(x) asociada a la función<br />

de escalado de la Fig. 5.1 y el módulo de la Transformada de Fourier. Se puede<br />

ver que la función wavelet es un filtro paso banda, como es lógico, pues calcula los<br />

detalles de la señal.<br />

En lugar de calcular el producto interno de las Ecs. (5.17) y (5.18), podemos<br />

obtener los coeficientes mediante un filtrado en sub-bandas, mediante el algoritmo<br />

102


Figura 5.1: (a) Ejemplo de función de escalado φ(x), (b) Módulo de la transformada<br />

de Fourier ˆφ(x). Las funciones de escalado son filtros paso bajo.<br />

103


Figura 5.2: (a) Ejemplo de función wavelet ψ(x), (b) Módulo de la transformada de<br />

Fourier ˆψ(x). Las funciones wavelet son filtros paso banda.<br />

104


Figura 5.3: La aproximación discreta A d 2 j+1 f se descompone en A d 2 j f y D d 2 j f.<br />

Figura 5.4: Reconstrucción de la aproximación discreta A d 2 j+1 f a partir de A d 2 j f y<br />

D d 2 j f.<br />

piramidal que se muestra en la Fig. 5.3. h(n) y g(n) denotan los filtros reflejados. Por<br />

ejemplo h(n) = h(−n). A 2 j es el operador que aproxima una señal a una resolución,<br />

2 j . Aplicando recursivamente la misma operación de filtrado, A d 2<br />

f, podemos obtener<br />

j<br />

una expresión multirresolución de f(x). h(n) y g(n) denotan los filtros discretos de<br />

aproximación y detalle, definidos como<br />

h(n) =< 2 −1 φ(2 −1 u), φ(u − n) > (5.19)<br />

g(n) =< 2 −1 ψ(2 −1 u), φ(u − n) > (5.20)<br />

Como aparece en [11], a partir de las Ecs. (5.12) y (5.13), se puede obtener la relación<br />

entre g(n) y h(n)<br />

g(n) = (−1) 1−n h(1 − n) (5.21)<br />

Se puede reconstruir la aproximación A d 2 j+1 f, a partir de la aproximación a menor<br />

resolución, A d 2 j f, y el detalle, D d 2 j f, mediante una estructura de filtrado piramidal<br />

similar a la anterior, como se puede ver en la Fig. 5.4 [11]. No describo todos los<br />

detalles de la transformada wavelet directa e inversa; más información sobre las<br />

mismas se puede encontrar en la bibliografía.<br />

Nuestro objetivo es aproximar los datos volumétricos mediante funciones suavizadas,<br />

por lo que ponemos la tranformada wavelet inversa como:<br />

A 2 j+1f(x) = A 2 jf(x) + P O2 j f(x)<br />

=<br />

+∞ ∑<br />

n=−∞<br />

+∞ ∑<br />

+<br />

n=−∞<br />

(A d 2 jf) nφ(2 j x − n)<br />

105<br />

(D 2 jf) n ψ(2 j x − n) (5.22)


Figura 5.5: Ejemplo de una transformada wavelet y su reconstrucción.<br />

donde P O2 j f(x) es la proyección ortogonal de f(x) en el espacio vectorial O 2 j.<br />

En al Fig. 5.5 se muestra un ejemplo de transformada wavelet directa, de una<br />

función unidimensional, f(x) y su reconstrucción. En la Fig. 5.5(a) aparece una<br />

función continua f(x); en la Fig. 5.5(b), se muestra la aproximación discreta A d 1f(x),<br />

que se obtiene muestreando f(x) en 64 puntos. Aplicando el esquema piramidal de<br />

filtrado que aparece en la A d 1 f, la señal discreta A d 1f(x) se descompone en A d 1 f y<br />

2<br />

2<br />

D 1 f, como se muestra en las Figs. 5.5(c) y 5.5(d). La Fig. 5.5(e) es la aproximación<br />

2<br />

A 1 f, que se reconstruye con el primer término de la Ec. (5.22), y la Fig. 5.5(f) es la<br />

2<br />

aproximación continua A 1 f, obtenida sumando el segundo término de la Ec. (5.22)<br />

a A 1 f. Se puede ver claramente que los detalles de la señal A d 1f, que se pierden<br />

2<br />

después de la degradación a A 1 f, se conserva en la señal de detalle D 1 f. Estas dos<br />

2<br />

2<br />

señales no están correladas.<br />

5.3. Transformada Wavelet 3D<br />

Como lo que queremos es aplicar una transformada wavelet ortonormal sobre<br />

datos volumétricos, debemos extender la transformada wavelet, vista en el apartado<br />

anterior, a tres dimensiones [9], [10].<br />

Sea V2 1 j<br />

usamos<br />

el espacio vectorial de la aproximación multirresolución de L 2 (R), y<br />

V 2 j = V 1 2 j ⊗ V1 2 j ⊗ V1 2 j (5.23)<br />

106


como aproximación multirresolución de L 2 (R 3 ). Como V2 1 se escribe<br />

j+1<br />

V 1 2 j+1 = V1 2 j ⊗ O1 2 j (5.24)<br />

V 2 j+1<br />

se puede escribir como<br />

V 2 j+1 =<br />

= (V 1 2 j ⊗ V1 2 j) ⊗ (V1 2 j ⊗ V1 2 j) ⊗ (V1 2 j ⊗ V1 2 j)<br />

= (V 1 2 j ⊗ V1 2 j ⊗ V1 2 j)<br />

⊗(V 1 2 j ⊗ V1 2 j ⊗ O1 2 j)<br />

⊗(V 1 2 j ⊗ O1 2 j ⊗ V1 2 j)<br />

⊗(V 1 2 j ⊗ O1 2 j ⊗ O1 2 j )<br />

⊗(O 1 2 j ⊗ V1 2 j ⊗ V1 2 j)<br />

⊗(O 1 2 j ⊗ V1 2 j ⊗ O1 2 j )<br />

Esto significa que la función de escalado 3D<br />

⊗(O 1 2 ⊗ j O1 2 ⊗ j V1 2 j)<br />

⊗(O 1 2 ⊗ j O1 2 ⊗ j O1 2j) (5.25)<br />

Φ(x, y, z) = φ(x)φ(y)φ(z) (5.26)<br />

forma una base ortonormal de V 2 j, y así se puede construir una base ortonormal de<br />

L 2 (R 3 ) con las siguientes siete wavelets 3D:<br />

Ψ 1 (x, y, z) = φ(x)φ(y)ψ(z),<br />

Ψ 2 (x, y, z) = φ(x)ψ(y)φ(z),<br />

Ψ 3 (x, y, z) = φ(x)ψ(y)ψ(z),<br />

Ψ 4 (x, y, z) = ψ(x)φ(y)φ(z),<br />

Ψ 5 (x, y, z) = ψ(x)φ(y)ψ(z),<br />

Ψ 6 (x, y, z) = ψ(x)ψ(y)φ(z),<br />

Ψ 7 (x, y, z) = ψ(x)ψ(y)ψ(z) (5.27)<br />

De forma similar, a la Fig. 5.3, la descomposición wavelet 3D de un conjunto de datos<br />

volumétricos, se puede realizar mediante las operaciones de filtrado que aparecen en<br />

la Fig. 5.6, en la que se puede ver como en primer lugar se realiza una transformada<br />

de todas las filas en la dirección x (igual que la transformada 1D); a continuación,<br />

de las columnas en la dirección y; y por último en profundidad, en la dirección z.<br />

Colocando las señales de salida de las operaciones de filtrado como se muestra<br />

en la Fig. 5.7, en la que se muestran tres niveles de transformación de un volumen<br />

de datos de dimensiones 128 × 128 × 128, se puede ver que no hay pérdida de<br />

información. En otras palabras, los detalles de los datos volumétricos, A d 2 j+1 f, que se<br />

107


Figura 5.6: Una aproximación discreta, A d 2 j+1 f, se descompone en la aproximación<br />

discreta a menor resolución, A d 2 j f, y siete detalles, desde D 1 2 j f hasta D 7 2 j f.<br />

Figura 5.7: Expresión multirresolución de unos datos volumétricos de tamaño 128 ×<br />

128 × 128.<br />

108


pierden después de la degradación a A d 2 j f, se dividen en 7 direcciones y se conservan<br />

en las señales de detalle, desde D 1 2 j f hasta D 7 2 j f. Aplicando sucesivas veces estas<br />

operaciones, se obtiene la expresión multirresolución de los datos volumétricos,<br />

como se puede ver en la Fig. 5.7.<br />

A partir de las señales anteriores, la aproximación a la resolución 2 j del volumen<br />

f(x, y, z), se puede reconstruir como<br />

A 2 jf(x, y, z) = ∑<br />

(A d 2 jf) (n,m,l)Φ(2 j x − n, 2 j y − m, n2 j z − l) (5.28)<br />

n,m,l<br />

y añadiendo las funciones de detalle 3D a la resolución 2 j ,<br />

∑<br />

P O2 f(x, y, z) = [<br />

j<br />

n,m,l<br />

(D 1 2 jf) (n,m,l)Ψ 1 (2 j x − n,2 j y − m, 2 j z − l)<br />

+ (D 2 2 jf) (n,m,l)Ψ 2 (2 j x − n,2 j y − m, 2 j z − l)<br />

+ (D 3 2 jf) (n,m,l)Ψ 3 (2 j x − n,2 j y − m, 2 j z − l)<br />

+ (D 4 2 jf) (n,m,l)Ψ 4 (2 j x − n,2 j y − m, 2 j z − l)<br />

+ (D 5 2 jf) (n,m,l)Ψ 5 (2 j x − n,2 j y − m, 2 j z − l)<br />

+ (D 6 2 jf) (n,m,l)Ψ 6 (2 j x − n,2 j y − m, 2 j z − l)<br />

+ (D 7 2 jf) (n,m,l)Ψ 7 (2 j x − n,2 j y − m, 2 j z − l)] (5.29)<br />

podemos reconstruir la aproximación de 2 j+1 de f(x, y, z). Esto indica que cualquier<br />

volumen de datos se puede aproximar como una suma de funciones, que son versiones<br />

desplazadas y dilatadas de 8 tipos de funciones 3D.<br />

5.4. Transformada Wavelet <strong>para</strong> Compresión 3D<br />

Como acabamos de ver, a partir de la expresión multirresolución de los datos<br />

volumétricos, podemos obtener la aproximación continua de la función original<br />

A 1 f(x, y, z). De forma simplificada, se puede escribir<br />

A 1 f(x, y, z) =<br />

N×M×L ∑<br />

i=1<br />

c i f i (x, y, z) (5.30)<br />

donde f i (x, y, z) representan las versiones desplazadas y retrasadas, tanto de la función<br />

de escalado 3D, como de las wavelets 3D, y c i representa los coeficientes wavelet,<br />

como aparecen en el ejemplo de la Fig. 5.7. N, M y L son las dimensiones del volumen.<br />

Sin embargo, se observa que muchos (la mayoría) de los coeficientes de esta<br />

ecuación son despreciables, por lo que se puede reconstruir una buena aproximación<br />

109


de los datos originales, tomando sólo los términos significantes. Considerando el<br />

efecto de dilatación de cada función, definimos la significancia de cada término<br />

como el valor<br />

significancia = 8 −j |c i |. (5.31)<br />

A continuación obtenemos la serie c , if , i(x, y, z), ordenando la serie c i f i (x, y, z), por<br />

orden de significancia. Cogiendo sólo los P términos mayores, con P < (N ×M ×L),<br />

la Ec. (5.30) se puede aproximar por<br />

P∑<br />

A 1 f(x, y, z) ≃ c , if i(x, , y, z) (5.32)<br />

i=1<br />

Cuantas menos funciones usemos <strong>para</strong> aproximar los datos volumétricos, menos<br />

detalle tendremos (más error), y por tanto una versión a más baja resolución. Sin<br />

embargo, al eliminar funciones, obtenemos una mayor compresión. Cuantos más<br />

funciones usemos, mayor resolución obtendremos, y por tanto más se parecerá al<br />

original (menor error), pero conseguiremos menor compresión de los datos.<br />

Este método de compresión se puede englobar dentro de los métodos de compresión<br />

por transformación. Además, es un método de compresión con pérdidas. Una<br />

de las ventajas principales de este método de compresión, es que es multirresolución,<br />

como se verá en el apéndice A.<br />

110


Capítulo 6<br />

Descripción de la Aplicación<br />

En este capítulo se describe el programa realizado <strong>para</strong> comprimir datos volumétricos,<br />

así como <strong>para</strong> visualizar, renderizar, e interactuar con los datos renderizados.<br />

Para todo ello se ha desarrollado una interfaz gráfica de usuario (GUI) bastante<br />

completa, que nos permite de forma sencilla realizar todas estas tareas.<br />

6.1. Introducción<br />

La radiología es una disciplina médica que trata con imágenes y datos de la<br />

anatomía humana. Estas imágenes se obtienen, como ya se ha descrito anteriormente,<br />

de distintos dispositivos, como rayos-x, tomografía computerizada, resonancia<br />

magnética y ultrasonido. Cada una de estas técnicas tiene sus ventajas y sus<br />

inconvenientes.<br />

Normalmente los radiologistas usan imágenes bidimensionales, pero hay situaciones<br />

en las que los modelos tridimensionales pueden ayudar a los diagnósticos de<br />

estos profesionales. La razón del uso más común de las imágenes bidimensionales<br />

son obvias: están más acostumbrados y entrenados <strong>para</strong> entender las complejas relaciones<br />

anatómicas en estas representaciones 2D; además hay muchas menos unidades<br />

de exploración que permitan obtener resultados tridimensionales.<br />

Sin embargo, muchas veces, los radiologistas tienen dificultades <strong>para</strong> explicar<br />

estas relaciones a los cirujanos. Después de todo, estos últimos trabajan en tres<br />

dimensiones al planear y ejecutar las operaciones, y se sienten mucho más cómodos<br />

mirando representaciones 3D.<br />

En principio, el programa está pensado <strong>para</strong> estudiar los resultados obtenidos<br />

mediante dos métodos distintos de compresión de datos volumétricos: transformada<br />

wavelet y diezmado de isosuperficies. En estos métodos se pueden variar<br />

gran cantidad de parámetros, <strong>para</strong> llegar a obtener los valores óptimos, que nos den<br />

111


los mejores resultados, según el caso. Estos valores óptimos podrán variar dependiendo<br />

del tipo de aplicación (local, cliente-servidor en una red, . . . ).<br />

En una posterior aplicación, se fijarán estos parámetros (o se permitirá un<br />

pequeño número de variaciones, dependiendo del caso). Esta aplicación estará pensada<br />

<strong>para</strong> ser ejecutada por profesionales de la medicina, poco familiarizados, en<br />

principio, con las aplicaciones informáticas y telemáticas, y mucho menos con la<br />

compresión volumétrica.<br />

Pese a ello, la interfaz gráfica de usuario es bastante manejable, y se ha tenido<br />

cuidado en mostrar mensajes de error, deshabilitar entradas de menús que no se<br />

deben ejecutar en determinadas situaciones, por no disponer de los datos <strong>para</strong> realizar<br />

esas operaciones, etc.<br />

6.2. Tareas a Realizar<br />

Durante una ejecución normal del programa, el usuario realiza las siguientes<br />

operaciones:<br />

Selección y lectura de ficheros con los datos volumétricos. Estos ficheros son el<br />

resultado de aplicar una tomografía computerizada, una resonancia magnética,<br />

o una exploración mediante ultrasonidos a un paciente. Lo normal es que, debido<br />

a su enorme tamaño, los datos se encuentren divididos en varios ficheros,<br />

uno <strong>para</strong> cada “rodaja” de la exploración. Estos ficheros estarán todos en el<br />

mismo directorio y tendrán el mismo nombre, salvo la extensión, que será el<br />

número de rodaja al que corresponde el fichero. Para abrirlos se debe seleccionar<br />

el primer fichero de los que se quieren abrir y el último (en este orden).<br />

Otra posibilidad es que todo el conjunto de datos volumétricos se encuentre<br />

en el mimso fichero. Ambas posibilidades están contempladas en la aplicación.<br />

Procesado de los datos. Dependiendo del método de compresión usado, este<br />

procesado puede ser de dos tipos:<br />

• Compresión mediante transformada wavelet: esta transformada se realiza<br />

directamente sobre los datos en volumen. Por ello, en primer lugar<br />

se realiza la compresión y a continuación se obtienen la isosuperficie que<br />

se desea visualizar.<br />

• Compresión mediante diezmado: este tipo de compresión se realiza sobre<br />

la isosuperficie obtenida, disminuyendo el número de triángulos. Por ello<br />

se debe obtener en primer lugar la isosuperficie del valor deseado, <strong>para</strong><br />

realizar, a continuación, el diezmado de la misma.<br />

Visulización y renderización de la isosuperficie, <strong>para</strong> poder ser observada sobre<br />

la pantalla. Ahora el usuario puede interactuar con el volumen renderizado en<br />

pantalla, girándolo, desplazándolo, cambiando el zoom, . . .<br />

112


Obtención de detalle. Si se ha empleado el método de compresión basado en la<br />

transformada wavelet, se podrá obtener la imagen a la resolución seleccionada,<br />

pero con una zona de la misma en la que se muestra el detalle completo (toda<br />

la resolución).<br />

Almacenamiento de la isosuperficie o de la renderización. Si lo desea, el usuario<br />

podrá almacenar en un fichero el resultado obtenido, <strong>para</strong> una posterior visualización.<br />

Si desea hacer una visualización interactiva, deberá guardar la<br />

isosuperficie, mientras que si sólo desea ver una imagen fija, guardará la renderización.<br />

Cálculo del error de compresión. Una vez obtenida la compresión de los datos<br />

volumétricos, el usuario podrá calcular el error cometido. Hay varios métodos<br />

<strong>para</strong> calcular el error, que se explicarán más adelante.<br />

6.3. Interfaz Gráfica de Usuario (GUI)<br />

La interfaz gráfica de usuario es el elemento principal de interacción entre el<br />

usuario y la máquina, permitiendo tanto la entrada, como la salida de información.<br />

Las interfaces gráficas de usuario tienen cada vez mayor importancia en la programación<br />

de aplicaciones informáticas, pues es lo primero que el usuario final ve,<br />

y el elemento de al aplicación con el que interactúa. Así, un programa excelente<br />

con una interfaz gráfica de usuario deficiente o nula, lo más normal es que no tenga<br />

éxito, a no ser que el programa no tenga competencia.<br />

Hace algunos años, la mayor parte de las aplicaciones informáticas carecían de interfaz<br />

gráfica; sobre todo, antes de surgir los sistemas de ventanas (como X-Windows<br />

y MS-Windows). Es fácil darse cuenta de las ventajas de trabajar con un sistema<br />

de ventanas y menús, frente a un interfaz que sólo tenga elementos textuales.<br />

Por otro lado, una interfaz gráfica de usuario debe permitir un aprendizaje rápido<br />

y un uso intuitivo del funcionamiento básico del programa, incluso <strong>para</strong> usuarios<br />

sin experiencia. No debe ser muy complicada, ni recargada, evitando así producir<br />

cansancio sobre el usuario.<br />

En la Fig. 6.1 se muestra la interfaz gráfica de usuario general del programa.<br />

A continuación voy a describir los elementos principales de la interfaz gráfica de<br />

usuario de la aplicación.<br />

6.3.1. Sistema de Menús<br />

En la parte superior de la Fig. 6.1, se puede ver la barra de menús usada <strong>para</strong><br />

realizar todas las operaciones descritas anteriormente. Se ha elegido un sistema de<br />

113


Figura 6.1: GUI general del programa en el que se puede ver una renderización.<br />

114


menús y submenús desplegables, algo muy usual en los GUIs. Cada elemento del<br />

menú ejecuta un comando o muestra un menú desplegable.<br />

La mayoría de los comandos de los menús dan acceso a un panel de control, en<br />

el que se ajustan los distintos parámetros de cada operación, como se puede ver<br />

en el ejemplo de la Fig. 6.2, en el que se muestra el panel de control con todas las<br />

opciones de renderización, entre las que se encuentran los colores de la renderización<br />

y fondo, la transparencia de la renderización y el tipo de interpolación. Se puede<br />

ver en este menú que <strong>para</strong> algunas opciones se ha proporcionado varias formas de<br />

elegir los parámetros. Así, por ejemplo, <strong>para</strong> seleccionar los colores, se ofrecen tres<br />

métodos distintos: valor de R,G y B (rojo, verde y azul), selección en una rueda de<br />

color, y botones <strong>para</strong> los colores más comunes.<br />

En general, en los menús de control se pueden encontrar los siguientes elementos:<br />

Barra de título: muestra el nombre del panel de control, dando una idea de su<br />

función.<br />

Etiquetas: muestra una pequeña descripción del parámetro que se encuentra a<br />

continuación, o debajo de la etiqueta.<br />

Entradas de texto: sirven <strong>para</strong> introducir valores a los parámetros (numéricos en<br />

general) que controlan las distintas operaciones del programa.<br />

Botones: se usan <strong>para</strong> asignar valores determinados a ciertos parámetros. Además,<br />

hay dos botones que aparecen en todos los paneles de control:<br />

Botón aceptar: sirve <strong>para</strong> ejecutar el comando correspondiente al panel de<br />

control (seleccionado mediante el sistema de menús). Al pulsar sobre este<br />

botón, se oculta el panel de control y se ejecuta el menú.<br />

Botón cancelar: como su propio nombre indica, cancela la ejecución del comando<br />

correspondiente.<br />

Botones de radio: se usan <strong>para</strong> seleccionar una opción entre varias posibilidades,<br />

excluyentes entre sí. En el ejemplo de la Fig. 6.2 se muestran dos botones de<br />

este tipo, <strong>para</strong> seleccionar el tipo de interpolación.<br />

Botones de chequeo: permiten seleccionar o no una opción. Al contrario que los<br />

botones de radio, son independientes entre sí.<br />

Escalas deslizantes: permiten seleccionar un parámetro numérico, que varía entre<br />

dos extremos, simplemente mediante un movimiento del ratón. De esta forma<br />

se controla los valores del parámetro.<br />

Como puede que el usuario no sepa qué valores asignar en un principio a cada<br />

parámetro, se ha asignado a todos los parámetros de los menús un valor por defecto,<br />

115


Figura 6.2: Ejemplo de panel de control. Panel de control con las opciones de renderización.<br />

116


Figura 6.3: Panel de control <strong>para</strong> abrir los archivos con los datos volumétricos.<br />

que normalmente es adecuado <strong>para</strong> realizar un primer estudio de los datos. Así,<br />

por ejemplo, en el panel de control de renderización, mostrado en la Fig. 6.2, los<br />

parámetros por defecto son: color hueso <strong>para</strong> la renderización y azul marino <strong>para</strong><br />

el fondo, 1.0 de opacidad (totalmente opaco) e interpolación tipo Gouraud (ya explicada<br />

anteriormente). En las ventanas de abrir y cerrar archivos, también se han<br />

seleccionado directorios por defecto, en los que suelen estar los datos, o en los que<br />

se suelen guardar.<br />

Hay algunos paneles de control, los que sirven <strong>para</strong> abrir y guardar archivos, que<br />

son paneles estándar, ya implementados en los principales lenguajes de programación<br />

y permiten realizar interfaces gráficas. En la Fig. 6.3, se muestra el panel <strong>para</strong> abrir<br />

los archivos. En estos paneles de control, aparece el directorio del sistema de archivos,<br />

en el que nos encontramos actualmente, una ventana con los archivos y directorios<br />

en el directorio actual, que sirve <strong>para</strong> moverse por el sistema de ficheros, una entrada<br />

de texto <strong>para</strong> escribir el nombre del fichero, y un menú desplegable con los distintos<br />

tipos de archivos que se pueden seleccionar. Además, están los botones de Abrir<br />

(Open) o Guardar (Save) y Cancelar (Cancel).<br />

Otros elementos de los menús, no dan acceso a paneles de control, sino que<br />

sirven <strong>para</strong> seleccionar parámetros y ejecutar operaciones, como los comandos del<br />

menú vista, en el que se selecciona el punto de Vista, o la opción de salir del programa,<br />

del menú Archivo.<br />

6.3.2. Ventana de Renderización<br />

En el centro del GUI de la aplicación, y ocupando la mayor parte del mismo,<br />

se encuentra la ventana de renderización. En ella se muestra el resultado de todas<br />

las operaciones realizadas sobre los datos, una vez renderizados. Esta ventana se<br />

117


corresponde con el plano de imagen de la cámara, situado en el punto focal, perpendicular<br />

a la dirección de proyección, como vimos en el capítulo de renderización.<br />

Esta ventana sigue el sistema de coordenadas de la pantalla.<br />

Una vez realizada la renderización, se puede interactuar sobre ella de dos formas,<br />

principalmente:<br />

Mediante el sistema de menús: se puede cambiar el punto de vista, o posición<br />

de la cámara (frontal, lateral izquierda, lateral derecha, posterior, superior,<br />

inferior e isométrica). También se puede cambiar el zoom, y el color de la<br />

renderización y del fondo, . . . , como ya hemos visto.<br />

Mediante el ratón: con los distintos botones del ratón se permite realizar<br />

movimientos sobre la renderización.<br />

• Pulsando el botón izquierdo del ratón y moviéndolo por la ventana, se<br />

puede rotar el objeto renderizado, alrededor del punto focal.<br />

• Pulsando el botón central del ratón y moviéndolo por la ventana, se modifica<br />

la posición de la renderización.<br />

• Pulsando el botón derecho del ratón y moviéndolo por la ventana, se<br />

modifica el dolly (similar al zoom).<br />

Mediante el teclado: algunas teclas permiten cambiar el modo de renderización.<br />

• Pulsando la tecla ‘w‘, se realiza la renderización sólo de la malla, sin<br />

rellenar los triángulos.<br />

• Pulsando la tecla ‘s‘, vuelve a realizar la renderización en superficie, renderizando<br />

el interior de los triángulos.<br />

6.3.3. Barra de Estado<br />

Se encuentra en la parte inferior de la ventana de la aplicación, como se puede<br />

ver en la Fig.6.1. Esta barra, está dividida en tres pequeñas ventanas de texto, en<br />

las que se muestra información muy útil del estado de la aplicación. A continuación<br />

se describe la utilidad de cada una de estas ventanas de texto:<br />

Ventana superior: en ella se muestra información de los datos sobre los que<br />

se realizan operaciones. Dependiendo del estado del programa, puede mostrar<br />

distinta información:<br />

• Número de puntos y número de voxels: se muestra esta información después<br />

de leer los datos, si no se ha realizado ningún tipo de operaciones<br />

sobre los mismos, que se encuentran en forma de malla rectangular regular<br />

3D.<br />

118


• Número de puntos y número de triángulos: se muestra este tipo de información<br />

después de calcular la isosuperficie o después de realizar un<br />

diezmado de la misma.<br />

• Número de coeficientes y error cuadrático medio: aparece después de realizar<br />

la compresión mediante la transformada wavelet.<br />

Ventana inferior izquierda: en esta ventana de texto se muestra el tiempo<br />

invertido en realizar la última operación que se ha realizado sobre los datos.<br />

El tiempo aparece en segundos, con una precisión de centésimas de segundo.<br />

Ventana inferior derecha: esta es la ventana de estado, en la que se muestra<br />

información sobre el estado del programa. Sirve <strong>para</strong> informar al usuario sobre<br />

las operaciones que se están realizando o se han realizado, así como el resultado<br />

de las mismas (si se ha producido el resultado correcto, o si se ha producido<br />

algún tipo de error, indicando cuál ha sido la causa).<br />

6.3.4. Ventanas de Error y de Información<br />

En general, en la interfaz gráfica se deshabilitan las entradas de los menús que<br />

realizan operaciones no permitidas en cada estado del programa, <strong>para</strong> que el usuario<br />

no intente realizar operaciones <strong>para</strong> las que aún no están disponibles los datos.<br />

Por ejemplo, puede intentar realizar la renderización antes de leer los datos, o un<br />

diezmado de la isosuperficie antes de calcularla, etc. De esta forma se evita un<br />

comportamiento incontrolado del programa.<br />

Pero además, durante la ejecución del programa se realiza un gran número de<br />

comprobaciones de error. El usuario puede estar realizando operaciones sobre datos<br />

no válidos, intentar leer ficheros con datos erróneos, . . . . Además, por motivos desconocidos<br />

(por ejemplo, si los datos de los ficheros son defectuosos o erróneos) puede<br />

fallar alguna operación. Para informar al usuario de estos fallos y hacer al programa<br />

lo más robusto posible, se han generado una serie de ventanas con mensajes de error,<br />

<strong>para</strong> informar al usuario del tipo de error que se ha producido. En la Fig. 6.4 se<br />

muestra un ejemplo de este tipo de ventanas. Además, también se informa al usuario<br />

de los errores en la barra de estado, como se ha dicho antes.<br />

Algunos tipos de errores que se pueden producir son, por ejemplo, los siguientes:<br />

Los ficheros seleccionados <strong>para</strong> abrir no están en el mismo directorio o tienen<br />

distinto nombre, salvo la extensión. Los ficheros de datos deben estar todos en<br />

el mismo directorio (como se dijo antes, se selecciona el primer y último fichero<br />

con los datos que se quieren abrir) y tener todos el mismo nombre, salvo la<br />

extensión, que indica el número de rodaja.<br />

119


Figura 6.4: Ejemplo de ventana de error, con información del error.<br />

Figura 6.5: Ejemplo de ventana de información.<br />

El segundo archivo debe ser mayor que el primero. Este error se produce cuando<br />

no ocurre el error anterior, pero el último archivo seleccionado tiene una<br />

extensión con un número menor o igual que el primero.<br />

Error al abrir el volumen. Se produce cuando el número de puntos que teóricamente<br />

debe tener el volumen no es el mismo que el obtenido.<br />

Error al calcular la isosuperficie. Se produce cuando no se obtiene ninguna<br />

isosuperficie. Esto puede ocurrir, por ejemplo, si el valor seleccionado <strong>para</strong> la<br />

isosuperficie no está dentro del rango de variación de los datos volumétricos.<br />

Además de las ventanas de error, hay también algunas ventanas que ofrecen<br />

información al usuario sobre lo que tiene que hacer en cada momento. Este tipo<br />

de ventanas se han usado <strong>para</strong> mostrar información que el usaurio debe conocer,<br />

antes de mostrar los menús estándar de abrir y guardar archivos, ya que en estas<br />

ventanas no se pueden añadir elementos, ni información adicional a la que ya tienen<br />

por defecto.<br />

También se podría haber realizado mediante la barra de estado, pero se ha optado<br />

por mostrar este tiop de información en una ventana, porque de esta forma el<br />

usuario se ve obligado a leer su contenido (o al menos pulsar el botón Aceptar, si<br />

ya lo conoce), mientras que la barra de estado, como siempre está, puede pasar más<br />

desapercibida, o incluso quedar oculta por la ventana emergente. En la Fig. 6.5 se<br />

muestra un ejemplo de este tipo de ventana.<br />

120


6.4. Unas notas sobre la implementación<br />

En este apartado se describe, de forma breve, cómo se he realizado la aplicación.<br />

Una descripción más detallada aparece en el apéndice A.<br />

Para realizar la aplicación, se ha usado la librería gráfica de aplicación que proporciona<br />

el entorno VTK (Visualization ToolKit) [12]. En el apéndice B se<br />

describirá algo más sobre este entorno de programación, su estructura, . . . VTK,<br />

está formado por un conjunto de librerías gráficas, que permiten realizar operaciones<br />

de computer graphics, tratamiento de imagen y renderización. Estas librerías<br />

gráficas están formadas por un conjunto de clases en C++.<br />

VTK permite programar mediante dos lenguajes distintos:<br />

Directamente en C++. Se consiguen programas más rápidos y eficientes, pero<br />

más lentos de implementar y necesitan compilación. Este modo de programación<br />

es necesario <strong>para</strong> crear nuevas clases de VTK, que implementen funcionalidades<br />

no dispponibles.<br />

En el lenguaje interpretado Tcl/Tk [15]. Los programadores de VTK “empaquetaron”<br />

el lenguaje de programación Tcl/Tk, <strong>para</strong> poder usar los objetos de<br />

VTK. Tcl/tk es un lenguaje de programación parecido, por ejemplo al C shell<br />

de UNIX. Al empaquetar VTK con Tcl/Tk, se han añadido nuevos comandos,<br />

que corresponden a todos los objetos de VTK. En Tcl/Tk, al ser un lenguaje<br />

interpretado, se pueden realizar aplicaciones de forma mucho más rápida,<br />

pero su ejecución es más lenta. Sin embargo, Tk permite hacer, de forma muy<br />

sencilla interfaces gráficas de usuario, algo que no es tan sencillo en C++.<br />

Para realizar la aplicación, se ha optado por programar todo lo posible, incluyendo<br />

la interfaz gráfica de usuario mediante Tcl/Tk, debido a sus grandes ventajas<br />

sobre la programación de interfaces gráficas en C++. El hecho de que sea más lento<br />

que un lenguaje compilado no es demasiado inconveniente, pues todas las funciones<br />

de los objetos están compiladas en las librerías de C++.<br />

Algunas operaciones requeridas por la aplicación, ya estaban programadas en<br />

las librerías de VTK, por lo que no han tenido que ser programadas. Otras operaciones,<br />

necesarias <strong>para</strong> la aplicación, no estaban realizadas aún en VTK, por lo que<br />

se han tenido que crear nuevas clases en C++, <strong>para</strong> realizar estas operaciones no<br />

disponibles. Al compilar estas clases, y meterlas en las librerías gráficas de VTK,<br />

automáticamente se hace el empaquetado de las mismas y sus funciones miembro a<br />

Tcl/Tk, lo cual las hace ya accesibles desde scripts realizados en Tcl/Tk.<br />

Hay algunas operaciones que no se han programado mediante clases, pues se<br />

consigue una mejor funcionalidad mediante programas ejecutables, hechos en C++,<br />

que pueden ser llamados posteriormente desde Tcl/Tk.<br />

121


Por último, hay que señalar que las librerías de VTK se pueden compilar <strong>para</strong><br />

linux, UNIX y Windows, y de hecho soportan distinto tipo de hardware gráfico. Esto<br />

se consigue realizando las clases que tienen que interactuar con las librerías gráficas,<br />

de forma que sean independientes del hardware, y derivando de estas superclases,<br />

otras específicas <strong>para</strong> cada librería gráfica. Tcl/tk también se puede compilar y<br />

funciona <strong>para</strong> linux, UNIX y Windows.<br />

Por tanto, es muy fácil realizar el portado de una aplicación hecha <strong>para</strong> VTK de<br />

un sistema a otro. De hecho, Tcl/Tk ofrece comandos <strong>para</strong> manipular los nombres<br />

de ficheros de forma independiente de la plataforma.<br />

Para realizar la aplicación se ha elegido el sistema operativo linux, por su gran<br />

estabilidad y fiabilidad. De todas las formas sería muy fácil realizar el portado<br />

a Windows. Los únicos elementos no portables son ciertos menús de Tk que son<br />

dependientes de la plataforma (de su sistema de ventanas) [15] y el directorio casa del<br />

usuario que ejecuta la aplicación, elegido como directorio por defecto <strong>para</strong> guardar<br />

los resultados.<br />

6.5. Ejemplos de Utilización del Programa<br />

En este apartado se describen algunos ejemplos de utilización del programa, que<br />

pueden servir como manual de usuario. Mediante estos ejemplos se puede entender<br />

el funcionamiento general del programa, incluyendo algunos elementos de la implementación<br />

del programa, necesarios <strong>para</strong> explicar su funcionamiento.<br />

Para la realización de los ejemplos se usa una tomografía computerizada de la<br />

cabeza de un niño de 12 años. Las rodajas de la tomografía tienen un grosor de<br />

1.5 mm, con dimensiones de pixels de 0.8 mm <strong>para</strong> los datos a total resolución,<br />

1.6 mm a media resolución y 3.2 mm a un cuarto de la resolución. En general, se<br />

usan los datos a media resolución, <strong>para</strong> que los cálculos no sean demasiado lentos,<br />

pero los resultados obtenidos sean bastante buenos. Se puede observar que el paciente<br />

tiene un agujero en el hueso, cerca de la nariz, un tubo en la boca, <strong>para</strong> administrarle<br />

anestesia, durante el proceso de escaneado, y como dato curioso, se puede ver incluso<br />

que tiene la oreja izquierda agujereada.<br />

6.5.1. Diezmado de un Cráneo<br />

En este primer ejemplo, se desriben los pasos necesarios <strong>para</strong> la realización de<br />

una compresión por diezmado, de la isosuperficie correspondiente al valor del cráneo.<br />

También se calcula el error cometido en la compresión, mediante dos métodos distintos.<br />

Los pasos a realizar son los siguientes:<br />

122


Ejecución del programa: Para ejecutar un programa interpretado de VTK (realizado<br />

en Tcl/Tk), se usa el programa vtk (Tcl/Tk empaquetado con las<br />

clases de VTK, como comandos) seguido del nombre del script que contiene<br />

la aplicación. Por tanto, <strong>para</strong> ejecutar el script Comp3D.tcl se ejecuta en la<br />

línea de comandos:<br />

$ VTK Comp3D.tcl &<br />

Aparece en pantalla el GUI de la aplicación, como se muestra en la Fig. 6.1;<br />

pero en lugar de la renderización que aparece en esta figura, aparece el nombre<br />

de la aplicación, en la ventana de renderización (Comp3D 1.0). En las tres<br />

ventanas de la barra de estado, se muestra el texto sin datos.<br />

Antes de hacer nada, podemos probar con el texto que aparece en la ventana<br />

de renderización, los movimientos del ratón, o las teclas ‘w‘ y ‘s‘, como se<br />

describió en el apartado 6.3.2.<br />

Lectura de los datos volumétricos: Como ya se ha dicho, los datos volumétricos<br />

pueden estar, o bien en un sólo fichero (cuya extensión será .slc), o bien<br />

en varios archivos situados en el mismo directorio, con el mismo nombre, salvo<br />

la extensión, que indica el número de rodaja. En estos ejemplos, los datos<br />

están en varios archivos. Para leer estos datos, se debe seleccionar con el ratón<br />

de la barra de menús<br />

Archivo ->Abrir.<br />

Aparece una ventana de información, que indica al usuario que seleccione el<br />

primer archivo del volumen que quiere leer. Pulsando el botón OK de esta ventana<br />

aparece el panel de control <strong>para</strong> seleccionar el primer fichero, como se<br />

muestra en la Fig. 6.3. Abrimos, por ejemplo el directorio headsq, pulsando<br />

dos veces con el ratón sobre él o bien pulsando una vez y a continuación presionando<br />

el botón Open. En este directorio se encuentran los datos volumétricos<br />

de la TC de una cabeza, de dimensiones 128 × 128 × 93 (ficheros half.*) y<br />

64×64×93 (ficheros quarter.*) En estos últimos, se ha realizado un diezmado<br />

de los datos en las direcciones x e y en cada rodaja, pero tienen el mismo<br />

número de rodajas (dirección z). Abrimos el archivo half.1.<br />

A continuación aparece otra ventana de información que indica que hay que<br />

seleccionar el último archivo. Al pulsar OK, vuelve a aparecer el panel de la<br />

Fig. 6.3, pero en esta ocasión, en el directorio del que seleccionamos el primer<br />

archivo. Abrimos en este caso el archivo half.93.<br />

Para generar la malla rectangular regular, en la que se colocan los datos<br />

leídos, hacen falta tres elementos:<br />

Número de datos en cada dimensión: dimensiones de la malla en<br />

las tres coordenadas, x, y, z (128, 128 y 93, <strong>para</strong> los datos que estamos<br />

abriendo, respectivamente). El número de elementos en z no hay que<br />

asignarlo, pues se fija según los ficheros seleccionados.<br />

123


Origen de la malla: sirve <strong>para</strong> situar la esquina inferior izquierda de<br />

la malla, en coordenadas del mundo real. Este parámetro lo puedo fijar<br />

siempre a (0, 0, 0), pues no modifica los resultados.<br />

Se<strong>para</strong>ción de los datos en cada dirección: representan el tamaño de<br />

cada celda de la malla. Para los datos que estamos abriendo, los valores<br />

correctos son 1,6, 1,6 y 1,5 respectivamente<br />

Estos parámetros son los únicos necesarios <strong>para</strong> generar una malla rectangular<br />

regular (la estructura de datos más simple de VTK), en la que todos las celdas<br />

de la malla están equiespaciados. Los elementos de este tipo de estructuras<br />

de datos se pueden referenciar mediante coordeandas implícitas (i, j, k). La<br />

obtención de las coordenadas del mundo real a partir de las implícitas es<br />

trivial [12].<br />

Al seleccionar y abrir el último archivo, se nos muestra el panel de control con<br />

las Opciones de Lectura del Volumen, en el que hay que asignar valores<br />

a los parámetros descritos anteriormente (excepto al origen, que como hemos<br />

dicho, se asigna siempre (0, 0, 0)).<br />

Además, hay un botón de chequeo, que sirve <strong>para</strong> indicar si los datos tienen bit<br />

de conectividad. Se emplea en algunos datos volumétricos, que <strong>para</strong> realizar<br />

operaciones de segmentación, usan el bit más significativo de cada elemento<br />

como bit de conectividad. No es el caso de los datos que estamos abriendo.<br />

Como se puede ver en este panel de control, los valores asignados a sus parámetros<br />

por defecto son los correspondientes a los datos que estamos abriendo. Por<br />

tanto, no debemos modificarlos (algo que deberíamos hacer <strong>para</strong> otros datos<br />

con distinta estructura, si no queremos que el programa saque un mensaje de<br />

error).<br />

Al pulsar Aceptar en este panel de control, se leen los datos de todos los<br />

ficheros y se genera en memoria la malla rectangular regular –en cuyos puntos<br />

están los datos de la resonancia magnética que hemos abierto–, a partir de la<br />

cual se realizarán todas las operaciones.<br />

Aparece en la ventana de estado, Leyendo el volumen..., mientras se leen<br />

todos los archivos; y al acabar, Volumen abierto con exito. Ahora en la<br />

ventana superior de la ventana de estado aparece el número de puntos del<br />

volumen (128 × 128 × 93 = 1523712), y el número de celdas (127 × 127 × 92 =<br />

1483868) y en la ventana inferior izquierda, el tiempo en la lectura de los datos<br />

(1.61 s).<br />

De esta forma, hemos leído todos los datos volumétricos de la cabeza. Si sólo<br />

se quisiera abrir una parte de la misma (por ejemplo la zona de los dientes),<br />

seleccionaríamos un rango menor de ficheros, teniendo en cuenta que cada<br />

fichero contiene los datos de una sección axial, tal como son obtenidos mediante<br />

los métodos explicados en el capítulo 2.<br />

124


Cálculo de la Isosuperficie: El siguiente paso es calcular la isosuperficie correspondiente<br />

a cierto valor de los datos volumétricos. Se calcula mediante el<br />

algoritmo Marching Cubes, descrito en el apartado 3.4. Para ello ejecutamos<br />

el comando de la barra de menús<br />

Operaciones ->Isosuperficie<br />

Aparece un panel de control <strong>para</strong> el cálculo de la isosuperficie, en el que simplemente<br />

hay que dar como parámetro el valor de la isosuperficie que se quiere<br />

obtener. Se muestran algunos valores típicos en tomografía computerizada <strong>para</strong><br />

dos tipos de tejidos: hueso (1200) y piel (600). En este primer ejemplo, vamos<br />

a calcular la isosuperficie correspondiente al hueso, por lo que debemos introducir<br />

el valor 1200 (este valor ya está por defecto).<br />

Al pulsar el botón Aceptar, aparece en la ventana de estado Calculando<br />

Isosuperficie... y tras un breve periodo de tiempo, ya tenemos calculada<br />

la isosuperficie (Isosuperficie obtenida con exito), mostrando el tiempo<br />

invertido en el proceso, y el número de puntos y triángulos que tiene<br />

la isosuperficie calculada mediante marching cubes. En este caso, obtenemos<br />

102337 puntos y 203956 triángulos, con un tiempo de ejecución de 4.67 s.<br />

Diezmado de la Isosuperficie: Ahora se puede renderizar directamente la isosuperficie<br />

calculada, sin nigún tipo de compresión, o bien realizar un diezmado<br />

de la misma. El algoritmo de diezmado, ya se ha explicado en el apartado 4.3.7.<br />

En este caso, vamos a optar diezmar la isosuperficie. Para ello ejecutamos el<br />

comando del menú<br />

Operaciones ->Compresion ->Diezmado<br />

Aparece un menú de control con varios parámetros, que se describe a continuación:<br />

Mantener topología: Si está activado, no se produce división de la<br />

malla (splitting), ni eliminación de agujeros. Esto puede limitar la máxima<br />

reducción que se puede conseguir. Por defecto está desactivado en la<br />

aplicación.<br />

Ángulo Característico: Sirve <strong>para</strong> especificar lo que es un borde. Si la<br />

normal a la superficie entre dos triángulos adyacentes es mayor o igual que<br />

el ángulo característico, existe un borde. Puede influir sobre los resultados<br />

obtenidos. Por defecto se fija a 30 o .<br />

Hacer splitting (división de la malla): Si está activado, se puede<br />

dividir la malla donde sea necesario (en esquinas, bordes, o cualquier<br />

sitio donde sea necesario dividir la malla). Si se desactiva, preserva mejor<br />

la topología, pero puede limitar la máxima reducción que se puede lograr.<br />

Por defecto, está activado.<br />

125


Ángulo de splitting: Controla el proceso de división de la malla. Existe<br />

una línea de división cuando las normales a la superficie de dos triángulos<br />

conectados por un borde es mayor o igual que este ángulo. Por defecto,<br />

se ha fijado a 75 o , pues interesa que la división sea lo último que se haga<br />

(cuando ya no pueda eliminar puntos de otra forma, pues con divisiones,<br />

queda peor la renderización).<br />

Hacer Splitting Previo: En algunos casos interesa hacer la división de<br />

la malla antes de eliminar ningún punto, pues da resultados mejores. Si<br />

está activado, se divide la malla en “parches” semi-planos, desconectados<br />

entre sí. La división se lleva a cabo con el Ángulo de splitting especificado.<br />

Por defecto, se ha deshabilitado, pues se obtienen peores resultados.<br />

Triángulos en un vértice <strong>para</strong> hacer splitting: Si el número de<br />

triángulos conectados a un vértice excede este valor, el vértice se divide.<br />

Por defecto, lo he fijado en 25, aunque no tiene demasiada inidencia, <strong>para</strong><br />

factores de compresión que no sean excesivos.<br />

Compresión: Especifica la reducción deseada en el número de triángulos,<br />

en tanto por uno (0.0 indica sin compresión). Puede que este valor no<br />

se llegue a alcanzar. Si se quiere alcanzar cualquier valor de reducción,<br />

se debe desactivar preservar topología y activar el splitting. Por defecto,<br />

está fijada en 0,70. Este es el principal parámetro, pues fija la relación de<br />

compresión.<br />

Para este ejemplo, fijamos el valor de la compresión en 0.75, y pulsamos el<br />

botón Aceptar.<br />

Al pulsar Aceptar aparece en la ventana de estado, Diezmando isosuperficie<br />

a 0.75..., y tras algún tiempo, ya tenemos la isosuperficie diezmada,<br />

Isosuperficie diezmada con éxito a 0.75. En la ventana superior de la<br />

barra de estado se puede ver el número de puntos, 25481, y el número de<br />

triángulos, 50988, que es exactamente el 25 % de los que teníamos antes de<br />

diezmar.<br />

Renderización de la Isosuperficie: Ahora que ya tenemos la isosuperficie diezmada,<br />

la renderizamos, <strong>para</strong> poder ver los resultados obtenidos. Para ello ejecutamos<br />

el comando de la barra de menús<br />

Vista ->Inicializar<br />

Aparece el menú de la Fig. 6.2, en el que, como dijimos se puede seleccionar<br />

el color de la renderización y del fondo, de varias formas distintas, el valor<br />

de la opacidad de la superficie (0.0 superficie totalmente transparente; 1.0<br />

superficie totalmente opaca), y el método de interpolación, que ya se explicó en<br />

el apartado 3.3.10.<br />

De esta forma, tras dejar los parámetros por defecto (color hueso <strong>para</strong> la renderización,<br />

azul marino <strong>para</strong> el fondo, opacidad 1.0 e interpolación Gouraud)<br />

126


y seleccionar Aceptar, se muestra por pantalla el cráneo del niño, con una<br />

reducción del número de triángulos de la isosuperficie de 0,75.<br />

Observación de la renderización: Se puede observar que la calidad obtenida es<br />

aceptable, aunque se notan los efectos de la reducción de triángulos (aparecen<br />

zonas con una iluminación más o menos constante). Si se pulsa la tecla ‘w‘,<br />

se ve la malla triangular obtenida, que com<strong>para</strong>da con la original, tiene bastantes<br />

menos triángulos. Se puede observar como la reducción de triángulos<br />

ha sido mayor en las zonas más planas de la superficie, y menor en las zonas<br />

más curvadas. Pulsando la tecla ‘s‘, vuelve a aparecer la superficie. Se puede<br />

observar que se muestra un <strong>para</strong>lelepípedo alrededor de la renderización, <strong>para</strong><br />

dar más idea de perspectiva.<br />

Ahora podemos girar, mover, acercar la cabeza mediante el ratón. Al hacer<br />

esto, debido a que suele ser lento, si no se dispone de hardware específico, se ha<br />

optado por mostrar sólo algunos puntos del volumen, <strong>para</strong> tener una idea de<br />

la posición actual, pero de forma que el proceso sea lo más rápido e interactivo<br />

posible. También se puede rotar a distintas posiciones mediante los comandos<br />

Vista ->Frontal<br />

Vista ->Trasera<br />

Vista ->Izquierda<br />

Vista ->Superior<br />

Vista ->inferior<br />

Vista ->Isométrica<br />

Se puede ver que estos comandos, además de modificar la vista, son botones<br />

tipo radio, que muestran la vista actual. También se puede modificar el zoom,<br />

mediante el comando<br />

Vista ->Zoom<br />

y asignando el valor de zoom deseado. Valores de zoom mayores que 1.0 acercan,<br />

mientras que si son menores, alejan. El zoom es acumulativo.<br />

Calculo del error: A continuación vamos a calcular el error. Se han realizado<br />

dos algoritmos <strong>para</strong> calcular el error. El primero de ellos, calcula el error en<br />

volumen y el segundo, calcula el error en la renderización.<br />

Para calcular el error por cualquiera de los dos métodos, necesitamos tener<br />

los datos necesarios <strong>para</strong> cada uno de los dos métodos, sin reducir el número<br />

de datos, y una vez reducido el número de datos. Como tenemos ahora la<br />

renderización tras el diezmado, obtenemos en primer lugar los datos necesarios<br />

<strong>para</strong> calcular el error de estos datos diezmados.<br />

Error en Volumen: Para calcular el error en volumen, no podemos com<strong>para</strong>r<br />

las dos mallas triangulares (la malla sin diezmar y la diezmada).<br />

Por ello, lo que hacemos es transformarlas en mallas rectangulares<br />

127


egulares, que contengan unos en las celdas de la malla rectangular interiores<br />

a la superficie y ceros en las celdas que estén fuera. Esto se realiza<br />

mediante el algoritmo flood filling que se explica en el apartado A.4.<br />

Esta malla se guarda en el disco duro del ordenador, <strong>para</strong> ser com<strong>para</strong>da,<br />

posteriormente, con la malla sin compresión. Esto se realiza mediante el<br />

comando<br />

Error ->FloodFilling<br />

Se muestra un panel de control con las opciones de este algoritmo. Estas<br />

opciones son, el tamaño de la malla rectangular regular, en cada una de las<br />

tres dimensiones x, y, y z. Dejamos los valores por defecto (256, 256, 93),<br />

es decir, el tamaño en x, e y es el doble que el de los datos originales,<br />

<strong>para</strong> conseguir la mayor precisión; en z, es el mismo que el número de<br />

rodajas, <strong>para</strong> que el algoritmo no sea demasiado lento.<br />

Tras pulsar el botón Aceptar, aparece el panel de control <strong>para</strong> guardar el<br />

archivo con la malla rectangular regular. Lo guardamos, por ejemplo, con<br />

el nombre hueso75.vtk (vtk es la extensión de los archivos con datos de<br />

VTK).<br />

Error de la imagen renderizada: Para calcular el error de la imagen<br />

renderizada, simplemente tenemos que guardar la imagen que aparece en<br />

pantalla, mediante el comando<br />

Archivo ->Guardar Renderizacion<br />

Lo guardamos, por ejemplo, con el nombre hueso75.ppm (el archivo se<br />

guarda con el formato gráfico PPM –pixmap–). Se puede observar que al<br />

guardar la imagen desaparece el borde exterior, que no debe influir en<br />

el cálculo del error, y se acerca la cámara al actor, <strong>para</strong> que tenga más<br />

importancia en el cálculo del error, que el fondo, que no influye.<br />

Ahora necesitamos realizar las dos operaciones anteriores sobre la renderización<br />

de los datos volumétricos sin diezmar. Para ello, volvemos a ejecutar el<br />

diezmado, pero en esta ocasión ponemos como valor de compresión 0,0. Esto<br />

pasa simplemente los datos por el filtro, sin realizar compresión (es inmediato,<br />

pues no tiene que realizar operación alón alguna, sino simplemente poner los<br />

datos de la entrada a la salida).<br />

Una vez realizado el flood filling y guardada la renderizacón de los datos sin<br />

comprimir, por ejemplo en los archivos hueso.vtk y hueso.ppm, calculamos<br />

ambos errores.<br />

Error en volumen: Para calcular el error en volumen, ejecutamos el<br />

comando<br />

Error ->Error en Volumen<br />

El programa nos pide que seleccionemos el archivo con formato vtk de los<br />

datos sin copresión y a continuación el archivo con los datos comprimidos.<br />

128


Seleccionamos respectivamente los archivos hueso.vtk y hueso75.vtk y<br />

el error se calcula (tarda 37.04 s). Obtenemos un error absoluto medio<br />

por rodaja de 290.19. este valor se muestra, tanto en una ventana de<br />

información, como en la ventana de estado.<br />

Error de la imagen renderizada: Para calcular el error entre las<br />

imágenes renderizadas, ejecutamos el comando<br />

Error ->Error en imagen<br />

Aparece un panel de control, en el que nos ofrece la posibilidad de, además<br />

de calcular el error cuadrático medio entre las dos imágenes, almacenar la<br />

diferencia entre las mismas. Pulsando el botón guardar imagen diferencia,<br />

aparece el panel de control de guardar archivo. Guardamos la diferencia<br />

como difer75.ppm.<br />

Después, al pulsar el botón Aceptar, hay que seleccionar los dos ficheros<br />

con la imagen sin compresión (hueso.ppm) y la imagen resultado del diezmado<br />

(hueso75.ppm). Se calcula el error cuadrático medio en 16.45 s,<br />

y se obtiene un resultado de 218.332.<br />

En las Figs. 6.6, 6.7 y 6.8 se muestra la imagen renderizada sin diezmado<br />

(archivo hueso.ppm), con diezmado a 0.75 (hueso75.ppm), y la diferencia entre<br />

ambas (difer75.ppm).<br />

6.5.2. Compresión Wavelet de una Cabeza y Obtención de<br />

Detalle<br />

En este segundo ejemplo vamos a realizar una compresión del volumen, usando<br />

la tranformada wavelet, <strong>para</strong> obtener a continuación la isosuperficie correspondiente<br />

a la piel. Después, se calcula el error, de varias formas, igual que anteriormente y<br />

por último, aprovechando la localidad de la tranformada wavelet, obtenemos una<br />

zona de la cara con más detalle que el resto.<br />

Muchos de los pasos a realizar, son iguales o muy parecidos que los del ejemplo<br />

anterior, por lo que simplemente se enuncian. Las operaciones que sean distintas, se<br />

describen más a fondo. Los pasos a realizar son los siguientes:<br />

Ejecución del programa: Si ya estamos dentro del programa, después de acabar<br />

el ejemplo anterior, no hace falta que volvamos a ejecutarlo.<br />

Lectura de los datos volumétricos: Si acabamos de ejecutar el ejemplo anterior,<br />

tampoco hace falta, pues ya tenemos los datos en memoria.<br />

Compresión Wavelet: El siguiente paso es obtener la compresión mediante la<br />

transformada wavelet, según se explicó en el apartado 5.4. Al contrario que en<br />

129


Figura 6.6: Renderización de la isosuperficie correspondiente al hueso, sin comprimir<br />

(archivo hueso.ppm).<br />

130


Figura 6.7: Renderización de la isosuperficie diezmada un 75 % (archivo<br />

hueso75.ppm).<br />

131


Figura 6.8: Diferencia entre la renderización de la isosuperficie sin diezmado y diezmada<br />

un 75 % (archivo dif75.ppm).<br />

132


el ejemplo de diezmado, aquí se obtiene la compresión directamente a partir<br />

de los datos en volumen, no de la isosuperficie.<br />

Para realizar la compresión mediante la transformada wavelet, ejecutamos el<br />

comando de la barra de menú<br />

Operaciones ->Compresión ->Wavelets ->Compresión<br />

Aparece un panel de control en el que, simplemente, podemos establecer el<br />

valor de la tasa de compresión. Este valor se puede establecer de dos formas,<br />

mediante una escala, o bien, mediante una entrada de texto.<br />

Para obtener mayor precisión en el valor de la compresión establecido, se puede<br />

escribir sobre la entrada de texto, o bien, mover la escala pinchando con el<br />

ratón sobre el elemento móvil y a continuación ajustando el valor deseado<br />

pinchando en el carril, a un lado u otro del elemento móvil (hasta obtener el<br />

valor deseado).<br />

La tasa de compresión que se consigue al introducir un valor N en este panel de<br />

control, es N:1. Se ha establecido una forma de asignar la tasa de compresión<br />

distinta de la usada <strong>para</strong> el diezmado, debido a que con wavelets podemos<br />

conseguir mayores tasas de compresión, permitiendo que el valor N varíe desde<br />

1 hasta 10000.<br />

En general, el valor de compresión que se obtiene, es menor que el establecido<br />

en este panel de control, debido a que la tranformada wavelet necesita que el<br />

tamaño de todas las dimensiones de los datos sean potencia de dos, con lo que<br />

si no lo son, deben ser padeadas.<br />

Establecemos un valor de compresión de 100:1, y pulsamos Aceptar. Tras<br />

50.47 s, obtenemos los datos comprimidos (compresión, distorsión y descompresión).<br />

En la barra de estado aparece el número de coeficientes wavelet,<br />

20971, y el error cuadrático medio, 12833.1.<br />

Una de las ventajas de esta compresión, es que una vez obtenida la transformada<br />

wavelet directa, y ordenados los coeficientes wavelet, no tenemos que<br />

hacerlo de nuevo, si queremos obtener otra tasa de compresión o un detalle en<br />

los datos.<br />

Cálculo de la Isosuperficie: Se realiza igual que se hizo en el ejemplo anterior,<br />

pero en esta ocasión seleccionamos el valor de densidad correspondiente a la<br />

piel, es decir, 600.<br />

Renderización de la Isosuperficie: Se realiza igual que antes, pero en este caso,<br />

elegimos el color piel <strong>para</strong> obtener un resultado más real. El resto lo dejamos<br />

igual.<br />

Observación de la Isosuperficie: El resultado obtenido aparece en la Fig. 6.10.<br />

Se puede ver que la superficie aparece distorsionada, pero aún se pueden ver<br />

133


las estructuras más “gruesas”. Al igual que en el ejemplo anterior, podemos<br />

observar distintas vistas de la renderización, girarla, moverla, etc.<br />

Cálculo del Error: En el caso de la transfromada wavelet, ya hemos obtenido una<br />

primera medida del error, el error cuadrático medio cometido al hacer<br />

la compresión, antes de obtener la isosuperficie. Esta medida del error<br />

es independiente de la isosuperficie obtenida. Sólo depende de los datos y del<br />

nivel de compresión.<br />

Las otras dos medidas, error en volumen a partir de la isosuperficie y<br />

error de la imagen renderizada, se obtienen exactamente igual que antes.<br />

Los resultados obtenidos son los siguientes:<br />

Error en Volumen. Error absoluto medio por rodaja: 428.821<br />

Error en Superficie. Error cuadrático medio: 363.607<br />

En la Fig.6.9, se muestra la imagen renderizada de la piel, sin ningún tipo de<br />

compresión. Por último, en la Fig.6.11, se muestra la diferencia entre las dos,<br />

al igual que en el ejemplo anterior.<br />

Obtención de una zona con detalle: Por último, en este ejemplo, vamos a obtener<br />

una compresión mutirresolución dentro del mismo volumen. Como ya se ha<br />

dicho, una de las ventajas de la compresión mediante la transformada wavelet,<br />

frente a otras transformadas, es su localidad espacial.<br />

Podemos obtener un nivel de compresión distinto <strong>para</strong> cada punto. Para la realización<br />

del proyecto, nos hemos conformado con obtener una zona de detalle,<br />

en la que cogemos todos los coeficientes de la transformada, y por tanto, no<br />

realizamos compresión. Esa zona se verá con todo el detalle, y el resto a la baja<br />

resolución que hayamos especificado anteriormente (100:1 en este ejemplo).<br />

Esto es, normalmente, suficiente <strong>para</strong> las aplicaciones médicas, pues el especialista<br />

quiere ver la zona sobre la que tiene que diagnosticar con buena resolución,<br />

pero sin perder una relación espacial de esa zona respecto al resto de la imagen.<br />

Para tener esa idea espacial, no importa, que esté a baja resolución.<br />

Para obtener el detalle, una vez realizada la compresión wavelet 100:1, ejecutamos<br />

el comando del menú<br />

Operaciones ->Compresion ->Wavelet ->Detalle<br />

Al ejecutar este comando, aparece un panel de control con seis escalas deslizantes<br />

y dos botones (además de los siempre presentes, Aceptar y Cancelar). En<br />

la ventana de renderización, desaparece la isosuperfiice. Se muestra la línea<br />

externa negra (con forma prismática), que nos sirve como referencia de la renderización;<br />

unos ejes coordenados x, y y z, y un prisma con las aristas de color<br />

blanco. Este prisma será el que nos sirva <strong>para</strong> seleccionar el detalle, moviéndolo<br />

por la escena.<br />

134


Figura 6.9: Renderización de la isosuperficie correspondiente a la piel, sin comprimir<br />

(archivo piel.ppm).<br />

135


Figura 6.10: Renderización de la isosuperficie comprimida mediante la transformada<br />

wavelet 100:1 (archivo piel100.ppm).<br />

136


Figura 6.11: Diferencia entre la renderización de la isosuperficie sin compresión<br />

wavelet y con una compresión 100:1 (archivo dif100.ppm).<br />

137


Las seis escalas del panel de control, nos sirven <strong>para</strong> colocar el centro del prisma<br />

blanco, así como su tamaño en cada dimensión. Los valores que aparecen<br />

en estas escalas son valores relativos con respecto a la posición y tamaño<br />

del prisma negro. Por ejemplo, una posición (0,5, 0,5, 0,5) indica el centro del<br />

prisma negro, y un tamaño de (0,5, 0,5, 0,5) indica las dimensiones del prisma<br />

blanco son la mitad de las respectivas del negro. Estos son los valores iniciales.<br />

Moviendo las escalas del panel de control, podemos cambiar la posición y el<br />

tamaño del detalle (prisma blanco).<br />

El movimiento del prisma blanco se ha limitado al interior del prisma negro,<br />

<strong>para</strong> que el detalle se limite a la zona de interés, en la que tenemos isosuperficie.<br />

Por ello, el centro y el tamaño del prisma blanco en cada dimensión están<br />

relacionados. Así, por ejemplo, podemos ver que si movemos el centro en x del<br />

prisma, hacia la izquierda, el prisma se mueve en esa dirección; pero al llegar<br />

a 0.25, si seguimos moviéndolo, el tamaño en esta dimensión empieza a disminuir,<br />

no permitiendo que el detalle salga fuera del borde de la isosuperficie.<br />

Si pretendemos aumentar ahora el tamaño del detalle en x, no se nos permite.<br />

Para conseguirlo, tenemos que mover el prisma hacia el centro.<br />

Después de esta pequeña explicación del posicionamiento y tamaño del prisma<br />

de detalle, ya podemos seleccionar la zona en la que deseamos mayor resolución.<br />

Se ha eliminado la renderización de la ventana de imagen <strong>para</strong> que el<br />

movimiento del prisma sea más rápido y no haya que renderizar la isosuperficie<br />

cada vez que movemos el prisma. Sin embargo, lo más probable es que<br />

con la sola referencia del prisma negro no podamos fijar la posición exacta del<br />

detalle que queremos ver. Por ello, en la parte inferior del panel de control, se<br />

han colocado dos botones. Sirven <strong>para</strong> mostrar u ocultar la renderización de la<br />

isosuperficie. Si se pulsa el botón mostrar, aparece la renderización, pero con<br />

un valor de transparencia de 0.2 (1.0 es opaco y 0.0 transparente), <strong>para</strong> que<br />

se vea el prisma blanco.<br />

Por último indicar que <strong>para</strong> fijar mejor la posición del prisma blanco de detalle,<br />

puede que sea conveniente girar la cámara mediante el ratón.<br />

Para la realización del ejemplo, tratamos de mostrar el detalle de la oreja<br />

izquierda (la derecha, desde nuestra posición). Para ello, los valores correctos<br />

<strong>para</strong> el panel de control del detalle son los siguientes:<br />

Centro del Prisma en X: 0.93<br />

Centro del Prisma en Y: 0.45<br />

Centro del Prisma en Z: 0.26<br />

Longitud del Prisma en X: 0.14<br />

Longitud del Prisma en Y: 0.19<br />

Longitud del Prisma en T: 0.42<br />

138


Figura 6.12: Obtención del detalle. Mediante el prisma blanco se selecciona la zona<br />

que se desea ver con detalle.<br />

En la Fig.6.12 se muestra la ventana de renderización durante el establecimiento<br />

del detalle, mostrando la renderización semitransparente.<br />

Tras pulsar Aceptar, se calcula el detalle y se muestra en la barra de estado<br />

el número de coeficientes wavelet: 38637, y el error cuadrático medio<br />

en volumen: 12591.2. A partir de estos resultados se puede observar, como el<br />

número de coeficientes ha aumentado de 20971 a 38637. Vemos la zona que nos<br />

interesa con toda la resolución y un número muy bajo de coeficientes. El error<br />

ha disminuido de 12833.1 a 12591.2. No ha disminuido mucho relativamente,<br />

debido a que la zona del detalle es muy pequeña con respecto a todo el volumen,<br />

pero esto nos da igual. En realidad, en el caso de obtener detalle, el error<br />

no importa, pues corresponde al resto, de baja resolución. Por eso no se ha<br />

139


ealizado un estudio sobre los errores con detalle.<br />

Tras obtener la isosuperficie correspondiente al hueso, (600) y mostrar la renderización<br />

en pantalla, podemos observar nítidamente la oreja izquierda del<br />

niño, en la que incluso se puede ver el agujero del pendiente. En la Fig. 6.13<br />

se muestra esta imagen; se ha girado ligeramente <strong>para</strong> que se aprecie mejor.<br />

Una de las ventajas de obtener el detalle mediante la transformada wavelet<br />

es que, como transforma todo el volumen –y no sólo la isosuperficie–, una vez<br />

obtenido el detalle, se puede ver cualquier tejido de esa zona (mediante el cálculo<br />

de la isosuperficie correspondiente). En el caso del ejemplo, evidentemente<br />

sólo se puede ver la piel, pues la oreja carece de estructura ósea)<br />

140


Figura 6.13: Detalle de la oreja izquierda (derecha desde nuestra posición).<br />

141


142


Capítulo 7<br />

Resultados<br />

En este capítulo se mostrarán y comentarán los resultados obtenidos. Todos los<br />

resultados comentados son relativos al error, que se ha calculado de varias formas<br />

distintas, <strong>para</strong> llegar a ser lo más objetivo posible. Se evalúan y com<strong>para</strong>n los dos<br />

métodos de compresión implementados: diezmado y transformada wavelet.<br />

Aunque en la aplicación, como se dijo en el capítulo 6, hay una ventana en la barra<br />

de estado en la que se muestra el tiempo empleado <strong>para</strong> realizar cada operación, no<br />

se ha realizado un estudio del tiempo de ejecución. La razón de esto es que como se<br />

tratan volúmenes enormes de datos, el funcionamiento depende mucho de elementos<br />

del ordenador como la memoria caché o hardware gráfico.<br />

7.1. Medidas de Error Empleadas<br />

Desde el primer momento en que se intenta calcular el error cometido al comprimir<br />

un volumen, se encuentran ciertas dificultades, debido, principalmente a que<br />

lo que estamos viendo no es el volumen, sino una isosuperficie obtenida a partir de<br />

él.<br />

Las formas de calcular el error que se han empleado <strong>para</strong> evaluar los dos métodos<br />

de compresión, son las siguientes:<br />

Error Cuadrático Medio de la Imagen Renderizada. Es lógico calcular<br />

este error, pues la imagen renderizada es lo que estamos viendo en realidad.<br />

Sin embargo, este método sólo evalúa el error desde un punto de vista de la<br />

escena.<br />

Para que todos los valores de error sean coherentes, se deben realizar la renderización,<br />

exactamente de la misma forma. Es decir, colocando la cámara en la<br />

misma posición y con los mismos parámetros, las mismas luces y propiedades<br />

del actor, etc.<br />

143


Error en Volumen. Como tenemos un volumen comprimido, también parece<br />

lógico realizar un cálculo del error en volumen.<br />

Este tipo de cálculo es trivial <strong>para</strong> el caso de compresión mediante la transformada<br />

wavelet; basta con calcular el error cuadrático medio entre los datos<br />

volumétricos originales y los distorsionados por la compresión. Sin embargo,<br />

si se emplea el método de diezmado, no podemos hacer esto, pues no tenemos<br />

los datos en volumen, sino sólo una malla triangular en el espacio. Por ello, el<br />

error en volumen se calcula de dos formas, según el caso:<br />

• Error Cuadrático Medio en Volumen. Sólo se puede calcular <strong>para</strong> la<br />

transformada wavelet. Es una buena medida del error, pero es independiente<br />

de la isosuperficie que se quiera renderizar, con lo que tiene en cuenta<br />

todos los errores del volumen, independientemente de que finalmente se<br />

muestren en la renderización, o no.<br />

• Número Medio de Errores por “Rodaja”. Esta es la forma en la<br />

que se calcula el error en volumen de la isosuperficie.<br />

El problema de com<strong>para</strong>r dos isosuperficies es que están formadas por<br />

triángulos en el espacio –muy difíciles de com<strong>para</strong>r globalmente–. Por ello,<br />

lo que se hace <strong>para</strong> evaluar su error, es convertir la superficie en una<br />

malla rectangular regular, formada por celdas prismáticas, evaluando las<br />

celdas por las que pasa la isosuperficie. De esta forma, se transforma la<br />

isosuperficie en una estructura regular (con unos en las celdas por las<br />

que pasa la isosuperficie y ceros en las celdas por las que no pasa). Sin<br />

embargo, seguimos con el mismo problema (por ejemplo, si las dos superficies<br />

com<strong>para</strong>das no pasan exactamente por las mismas celdas, daría el<br />

mismo error que si están muy alejadas, a no ser que se emplee un cálculo<br />

de distancias).<br />

La forma empleada <strong>para</strong> la com<strong>para</strong>ción de estas dos estructuras, es mediante<br />

un preprocesado de las mismas, que consiste en rellenar el interior<br />

de las isosuperficies (ahora en la estructura regular), respetando las posibles<br />

cavidades que puedan tener en su interior, como se explica en los<br />

apartados A.4 y A.5.<br />

De esta forma, se puede evaluar el error en volumen cometido al diezmar<br />

una malla triangular. En concreto, la medida obtenida es el número de<br />

errores medio por rodaja, pues en la estructura regular tenemos datos<br />

binarios (unos dentro de la isosuperficie o en su frontera, y ceros fuera, o<br />

en las cavidades interiores). Por tanto el número de errores es equivalente<br />

al error cuadrático.<br />

Este cálculo del error se puede emplear <strong>para</strong> ambos métodos de compresión,<br />

pues en ambos se obtiene, antes o después, isosuperficies.<br />

144


7.2. Cálculos Realizados<br />

En el apartado 7.3 se muestran todos los resultados obtenidos <strong>para</strong> ambos métodos<br />

y distintos niveles de compresión. Para cada uno, se han realizado todos los<br />

cálculos de error posibles, según el método:<br />

Compresión mediante Wavelets:<br />

• Error cuadrático medio en volumen (MSEV).<br />

• Número medio de errores por “rodaja” (NMER).<br />

• Error cuadrático medio de la imagen renderizada (MSER).<br />

Compresión Mediante Diezmado:<br />

• Número medio de errores por “rodaja” (NMER).<br />

• Error cuadrático medio de la imagen renderizada (MSER).<br />

Todos estos cálculos se realizan <strong>para</strong> dos isosuperficies distintas de la misma<br />

Tomografía Computerizada, de dimensiones 128 × 128 × 93. El estudio se puede<br />

considerar bastante válido <strong>para</strong> cualquier conjunto de datos volumétricos, obtenidos<br />

a partir de dispositivos de radiología médica (como es nuestro objetivo), pues en<br />

todos ellos obtenemos datos a partir de propiedades de los tejidos del cuerpo, aunque<br />

las partes del cuerpo estudiadas sean distintas.<br />

La obtención de todos los datos de error, se ha llevado a cabo con gran cuidado,<br />

intentando realizar todas las pruebas bajo las mismas condiciones y parámetros.<br />

Ello nos ha llevado a tener que repetirlas varias veces, a causa de algún cambio en el<br />

programa. Como nota anecdótica cabe señalar que la obtención de todas las medidas<br />

de error ha llevado un tiempo aproximado de 20 horas (sin contar las repeticiones<br />

que se han realizado de los experimentos). Lo que más tiempo ha llevado, ha sido el<br />

algoritmo de flood filling. La razón de esto es que hemos muestreado la isosuperficie<br />

sobre una malla de dimensiones 256 × 256 × 93, cuando los datos originales tenían<br />

un tamaño de 128 × 128 × 93. Esto se ha hecho así <strong>para</strong> que el muestreo de la<br />

isosuperficie tuviera una incidencia desperciable sobre el cálculo del error.<br />

Los parámetros usados <strong>para</strong> realizar las pruebas han sido los siguientes:<br />

Datos:<br />

• Tipo de datos: tomografía computerizada de la cabeza de un niño.<br />

• Dimensiones originales: 128 × 128 × 93.<br />

• Dimensiones padeadas: 128 × 128 × 93.<br />

• Origen en coordenadas del mundo: (0, 0, 0).<br />

145


• Espaciado entre los datos: (1.6, 1.6, 1.5) (milímetros).<br />

• Densidad correspondiente a la piel: 600.<br />

• Densidad correspondiente al hueso: 1200.<br />

Transformada wavelet:<br />

• Filtro wavelet usado: Daubechies de 6 coeficientes.<br />

Diezmado:<br />

• Mantener topología: desactivado.<br />

• Ángulo característico: 75o .<br />

• Hacer splitting: activado.<br />

• Hacer splitting previo: desactivado.<br />

• Permitir distorsiones de bordes: activado.<br />

• Triángulos en un vértice <strong>para</strong> hacer splitting: 25.<br />

Cámara:<br />

• Posición: (99.85, 724.6, 69).<br />

• Punto Focal: (99.85, 93.63, 69).<br />

• Vista Superior: (0, 0, -1).<br />

• Distancia a los planos de corte: 63.10, 3154.82.<br />

• Ángulo de vista: 20o .<br />

• Ángulo del ojo: 2o .<br />

• Centro de la ventana: 0, 0.<br />

7.3. Resultados obtenidos<br />

En este apartado voy a mostrar los resultados numéricos obtenidos <strong>para</strong> las<br />

distintas magnitudes de error calculadas. También se muestran algunas gráficas,<br />

mediante las que será más sencillo interpretar los resultados.<br />

En la tabla 7.1, se muestra el error cuadrático medio en volumen <strong>para</strong> la compresión<br />

wavelet, <strong>para</strong> distintas tasas de compresión, expresadas de la forma N:1.<br />

También aparece el número de coeficientes wavelet que se ha cogido <strong>para</strong> realizar la<br />

compresión.<br />

En las tablas 7.2 y 7.3, se muestran los resultados de error <strong>para</strong> las isosuperficies<br />

correspondientes al hueso y a la piel, respectivamente. Las medidas de error que<br />

146


10 x 104 Número de Coeficientes de la transformada wavelet<br />

9<br />

8<br />

MSE entre los datos volumétricos<br />

7<br />

6<br />

5<br />

4<br />

3<br />

2<br />

1<br />

0<br />

0 2 4 6 8 10 12<br />

Figura 7.1: Compresión mediante transformada wavelet. Variación en el error<br />

cuadrático medio en el volumen reconstruido, en función del número de coeficientes<br />

de la transformada wavelet. Escala lineal <strong>para</strong> ambos ejes.<br />

x 10 5<br />

aparecen son, el número medio de errores por rodaja y el error cuadrático medio de<br />

la imagen renderizada.<br />

Por último, en las tablas 7.4 y 7.5, aparecen las medidas de error, <strong>para</strong> las dos<br />

mismas isosuperficies, <strong>para</strong> el caso de compresión mediante diezmado. Las medidas<br />

obtenidas son también, el número medio de errores por rodaja y el error cuadrático<br />

medio de la imagen renderizada.<br />

Se han obtenido varias gráficas a partir de las medidas contenidas en las tablas<br />

mostradas. Se ha intentado no mostrar demasiadas gráficas, pero que las que aparecen<br />

tengan el mayor interés posible. Estas gráficas se comentarán en el apartado 7.4.<br />

147


Cuadro 7.1: Compresión mediante transformada wavelet. Error cuadrático medio<br />

en volumen (MSEV) y número de coeficientes wavelet, en función de la tasa de<br />

compresión.<br />

N:1 # Coeficientes MSEV<br />

2:1 1048576 0.6<br />

3:1 699050 5.8<br />

4:1 524288 16.5<br />

5:1 419430 37.0<br />

6:1 349525 73.2<br />

7:1 299593 130.3<br />

8:1 262144 210.2<br />

9:1 233016 311.3<br />

10:1 209715 434.6<br />

12:1 174762 766.4<br />

14:1 149796 1195.2<br />

16:1 131072 1677.6<br />

18:1 116508 2259.1<br />

20:1 104857 2894.6<br />

23:1 91180 3949.7<br />

26:1 80659 4877.0<br />

30:1 69905 6135.7<br />

34:1 61680 6999.2<br />

39:1 53773 7577.5<br />

44:1 47662 7899.1<br />

50:1 41943 8257.5<br />

58:1 36157 8781.6<br />

70:1 29959 9698.9<br />

84:1 24966 11067.0<br />

100:1 20971 12833.1<br />

130:1 16131 16871.6<br />

200:1 10485 23874.7<br />

300:1 6990 26536.1<br />

450:1 4660 30147.0<br />

700:1 2995 38025.0<br />

1000:1 2097 47021.6<br />

2000:1 1048 54019.8<br />

5000:1 419 79952.7<br />

10000:1 214 91969.2<br />

148


Cuadro 7.2: Compresión mediante transformada wavelet. Isosuperficie correspondiente<br />

al hueso (densidad = 1200). Número medio de errores por rodaja (NMER) y<br />

error cuadrático medio de la imagen renderizada (MSER).<br />

N:1 # Coeficientes NMER MSER<br />

2:1 1048576 0.2 0.3<br />

3:1 699050 5.3 2.3<br />

4:1 524288 14.3 6.8<br />

5:1 419430 31.1 14.0<br />

6:1 349525 54.7 26.4<br />

7:1 299593 79.9 44.1<br />

8:1 262144 109.7 62.5<br />

9:1 233016 139.7 80.5<br />

10:1 209715 170.2 106.5<br />

12:1 174762 247.3 166.3<br />

14:1 149796 323.6 227.9<br />

16:1 131072 400.0 292.8<br />

18:1 116508 489.3 363.1<br />

20:1 104857 561.9 440.6<br />

23:1 91180 642.8 533.8<br />

26:1 80659 755.9 626.7<br />

30:1 69905 865.5 711.4<br />

34:1 61680 912.3 777.9<br />

39:1 53773 943.0 807.4<br />

44:1 47662 995.1 839.2<br />

50:1 41943 1077.7 884.5<br />

58:1 36157 1231.0 986.2<br />

70:1 29959 1416.5 1186.4<br />

84:1 24966 1633.4 1303.4<br />

100:1 20971 1858.1 1379.0<br />

130:1 16131 2131.8 1585.0<br />

200:1 10485 2658.6 1669.4<br />

300:1 6990 2928.1 1705.8<br />

450:1 4660 3348.2 1761.0<br />

700:1 2995 4273.5 1769.1<br />

1000:1 2097 4707.9 2118.1<br />

2000:1 1048 5285.8 2216.7<br />

5000:1 419 7021.2 2654.1<br />

10000:1 214 7733.8 2740.5<br />

149


Cuadro 7.3: Compresión mediante transformada wavelet. Isosuperficie correspondiente<br />

a la piel (densidad = 600). Número medio de errores por rodaja (NMER) y<br />

error cuadrático medio de la imagen renderizada (MSER).<br />

N:1 # Coeficientes NMER MSER<br />

2:1 1048576 0 0.3<br />

3:1 699050 1.9 0.5<br />

4:1 524288 5.4 1.3<br />

5:1 419430 11.1 2.7<br />

6:1 349525 18.2 4.3<br />

7:1 299593 26.3 7.0<br />

8:1 262144 34.1 10.3<br />

9:1 233016 41.9 14.5<br />

10:1 209715 51.6 19.1<br />

12:1 174762 70.2 31.9<br />

14:1 149796 92.9 52.4<br />

16:1 131072 110.0 72.3<br />

18:1 116508 136.2 99.5<br />

20:1 104857 153.3 129.6<br />

23:1 91180 189.4 172.7<br />

26:1 80659 204.5 189.2<br />

30:1 69905 210.3 196.1<br />

34:1 61680 223.8 200.9<br />

39:1 53773 236.6 209.9<br />

44:1 47662 250.4 217.4<br />

50:1 41943 253.1 227.2<br />

58:1 36157 287.4 246.9<br />

70:1 29959 318.5 269.3<br />

84:1 24966 366.5 301.4<br />

100:1 20971 428.8 363.6<br />

130:1 16131 556.9 447.2<br />

200:1 10485 663.4 507.3<br />

300:1 6990 729.6 540.1<br />

450:1 4660 866.7 599.7<br />

700:1 2995 1168.9 694.2<br />

1000:1 2097 1332.4 763.2<br />

2000:1 1048 1526.2 725.8<br />

5000:1 419 2245.1 811.5<br />

10000:1 214 2281.1 884.0<br />

150


Cuadro 7.4: Compresión mediante diezmado. Isosuperficie correspondiente al hueso<br />

(densidad = 1200). Número medio de errores por rodaja (NMER) y error cuadrático<br />

medio de la imagen renderizada (MSER).<br />

% Diezmado # Puntos # Triángulos NMER MSER<br />

0 102337 203856 0 0<br />

5 97206 193757 5.5 2.1<br />

10 92080 183560 12.0 4.1<br />

15 86963 173362 20.9 6.8<br />

20 81841 163164 30.2 10.7<br />

25 76719 152967 39.2 14.7<br />

30 71601 142769 49.7 19.9<br />

35 66485 132570 61.2 24.9<br />

40 61669 122372 79.9 31.0<br />

45 56254 112175 97.8 40.0<br />

50 51139 101978 113.9 50.8<br />

55 46023 91779 140.0 64.8<br />

60 40902 81581 165.8 80.5<br />

65 35777 71384 196.1 99.7<br />

70 30643 61185 239.6 133.2<br />

75 25481 50988 290.2 218.3<br />

80 20404 40790 350.4 312.4<br />

85 15269 30592 477.7 417.3<br />

90 10130 20395 745.7 697.9<br />

95 4956 10196 2330.8 1965.9<br />

99 3728 2039 4767.1 6878.3<br />

151


Cuadro 7.5: Compresión mediante diezmado. Isosuperficie correspondiente a la piel<br />

(densidad = 600). Número medio de errores por rodaja (NMER) y error cuadrático<br />

medio de la imagen renderizada (MSER).<br />

% Diezmado # Puntos # Triángulos NMER MSER<br />

0 72508 144104 0 0<br />

5 68877 136897 2.4 0.3<br />

10 65260 129693 5.4 0.9<br />

15 61636 122488 9.5 1.5<br />

20 58011 115283 13.3 2.1<br />

25 54391 108078 17.7 2.8<br />

30 50767 100871 23.1 3.7<br />

35 47142 93667 28.9 5.3<br />

40 43519 86461 35.1 6.9<br />

45 39897 79257 41.4 8.6<br />

50 36278 72052 49.1 10.7<br />

55 32659 64846 57.7 13.1<br />

60 29041 57640 68.2 16.2<br />

65 25423 50436 80.8 20.1<br />

70 21803 43231 98.4 26.6<br />

75 18179 36026 120.9 36.5<br />

80 14548 28820 134.0 51.7<br />

85 10913 21615 229.4 78.0<br />

90 7285 14409 356.4 126.9<br />

95 3656 7205 677.8 292.4<br />

99 2593 1441 7822.8 5721.3<br />

152


10 5 Número de Coeficientes de la transformada wavelet<br />

10 4<br />

MSE entre los datos volumétricos<br />

10 3<br />

10 2<br />

10 1<br />

10 0<br />

10 −1<br />

0 2 4 6 8 10 12<br />

Figura 7.2: Compresión mediante transformada wavelet. Variación en el error<br />

cuadrático medio en el volumen reconstruido, en función del número de coeficientes<br />

de la transformada wavelet. Escala logarítmica <strong>para</strong> el eje de ordenadas.<br />

x 10 5<br />

153


10 4 Número de Coeficientes de la transformada wavelet<br />

10 3<br />

MSE entre imágenes renderizadas<br />

10 2<br />

10 1<br />

10 0<br />

10 −1<br />

0 0.05 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5<br />

Figura 7.3: Compresión mediante transformada wavelet. Variación en el error<br />

cuadrático medio de la imagen renderizada, en función del número de coeficientes<br />

de la transformada wavelet. Escala logarítmica <strong>para</strong> el eje de ordenadas. La curva<br />

roja corresponde a la isosuperficie de la piel (1200) y la negra a la del hueso (600).<br />

154


10 4 Número de Coeficientes de la transformada wavelet<br />

10 3<br />

Número de errores por rodaja del volumen<br />

10 2<br />

10 1<br />

10 0<br />

10 −1<br />

0 0.05 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5<br />

Figura 7.4: Compresión mediante transformada wavelet. Variación en el número<br />

medio de errores por rodaja, en función del número de coeficientes de la transformada<br />

wavelet. Escala logarítmica <strong>para</strong> el eje de ordenadas. La curva roja corresponde a la<br />

isosuperficie de la piel (1200) y la negra a la del hueso (600).<br />

155


10 4 Número de puntos diezmado / Número de puntos original<br />

10 3<br />

MSE entre imágenes renderizadas<br />

10 2<br />

10 1<br />

10 0<br />

10 −1<br />

0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1<br />

Figura 7.5: Compresión mediante diezmado. Variación en el error cuadrático medio<br />

de la imagen renderizada, en función del tanto por uno de puntos considerados el<br />

diezmado. Escala logarítmica <strong>para</strong> el eje de ordenadas. La curva roja corresponde a<br />

la isosuperficie de la piel (1200) y la negra a la del hueso (600).<br />

156


10 4 Número de puntos diezmado / Número de puntos original<br />

Número de errores por rodaja del volumen<br />

10 3<br />

10 2<br />

10 1<br />

10 0<br />

0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1<br />

Figura 7.6: Compresión mediante diezmado. Variación en el número medio de errores<br />

por rodaja, en función del tanto por uno de puntos considerados el diezmado. Escala<br />

logarítmica <strong>para</strong> el eje de ordenadas. La curva roja corresponde a la isosuperficie de<br />

la piel (1200) y la negra a la del hueso (600).<br />

157


10 4 Número de elementos comprimido / Número de elementos original<br />

Número de errores por rodaja del volumen<br />

10 3<br />

10 2<br />

10 1<br />

10 0<br />

10 −4 10 −3 10 −2 10 −1 10 0<br />

Figura 7.7: Com<strong>para</strong>ción de la compresión mediante la transformada wavelet (rojo)<br />

y la compresión mediante diezmado (negro). Variación en el número medio de errores<br />

por rodaja, en función del tanto por uno de elementos considerados <strong>para</strong> realizar la<br />

compresión. Escala logarítmica <strong>para</strong> ambos ejes. Isosuperficie correspondiente a la<br />

piel (600).<br />

158


7.4. Interpretación de los resultados obtenidos<br />

En este apartado se comentan las gráficas mostradas en el apartado anterior.<br />

Aunque pueda resultar algo obvio, se puede observar en todas las tablas, cómo<br />

al disminuir el porcentaje de los datos que se considera (al aumentar la tasa de<br />

compresión), el error aumenta. Este es el comportamiento esperado de todo método<br />

de compresión con pérdidas.<br />

7.4.1. Compresión mediante la Transformada Wavelet<br />

La Fig. 7.1 corresponde a la representación, en escala lineal <strong>para</strong> ambos ejes, de<br />

la tabla 7.1. En ella se muestra el error cuadrático medio en volumen, respecto a la<br />

disminución del número de coeficientes wavelet considerados.<br />

Se observa como, cuando el número de coeficientes es grande (poca compresión),<br />

el aumento del error cuadrático medio es pequeño. Esto es así porque los primeros<br />

coeficientes eliminados corresponden a los detalles más pequeños de mayor resolución,<br />

que tienen muy poca importancia relativa en el conjunto. En la tabla se observa<br />

que al disminuir los coeficientes a la mitad, el MSE sólo es de 0.58. Sin embargo,<br />

según se va disminuyendo más el número de coeficientes, el error aumenta cada vez<br />

con mayor pendiente. Esto se debe a que los coeficientes eliminados, son cada vez<br />

más importantes (detalles de resoluciones menores).<br />

Si com<strong>para</strong>mos esta tabla con la que aparece en [10], Pág. 55, se puede observar<br />

que es muy similar cualitativamente. Sin embargo, no se pueden realizar com<strong>para</strong>ciones<br />

cuantitativas del error, pues en este artículo se evalúa el error en datos<br />

procedentes de una resonancia magnética, y aquí se está evaluando el error en una<br />

tomografía computerizada.<br />

Si bien en la Fig. 7.1, se puede ver la variación del error en escala lineal, también<br />

puede resultar interesante representarlo en escala logarítmica, debido a que los<br />

errores son muy pequeños <strong>para</strong> tasas de compresión bajas y muy grandes <strong>para</strong> tasas<br />

de compresión altas.<br />

En la Fig. 7.2 se representa el mismo error en escala logarítmica, manteniendo<br />

lineal el eje de abscisas (número de coeficientes). En ella, se puede observar cómo<br />

el aumento relativo del error (pendiente de la curva) crece según se va aumentando<br />

la tasa de compresión. Sin embargo, se puede ver que hay una primera zona, en<br />

la que la pendiente es aproximadamente constante, indicando que los coeficientes<br />

eliminados tienen todos la misma importancia. Esto es muy fácil de entender, pues<br />

hasta una compresión de 8:1 (en la que la gráfica tiene una pendiente aproximadamente<br />

constante), estamos eliminando coeficientes de detalle del mismo nivel (el<br />

primero). La razón de que no sea totalmente lineal es porque se eliminan primero<br />

los coeficientes menores y luego los mayores. Además, mediante la obtención de la<br />

significancia, explicada en el apartado 5.4, se consigue que el error aumente menos<br />

159


al principio, pues no tiene la misma importancia eliminar dos coeficientes de niveles<br />

de resolución distintos, aunque tengan el mismo valor absoluto. A continuación se<br />

puede observar otra zona de pendiente aproximadamente constante, que corresponde<br />

a la eliminación del siguiente nivel de detalle (hasta compresión 64:1). Por la misma<br />

razón que antes, su pendiente no es totalmente constante, sino que va aumentando<br />

poco a poco.<br />

Hay que señalar que, si bien el comportaminiento explicado aquí puede servir<br />

<strong>para</strong> entender los resultados, el orden de eliminación de los coeficientes de la transformada<br />

wavelet no es, estrictamente, por niveles de resolución (aunque la significancia<br />

ayuda a que esto sea así), pues si un coeficiente de un nivel de resolución j, es mucho<br />

menor (más de ocho veces menor) que uno del nivel (j + 1), se elimina antes el del<br />

nivel de resolución menor. Esto puede explicar, en parte, el comportamiento observado<br />

<strong>para</strong> niveles muy grandes de compresión, en los que se eliminan coeficientes<br />

muy importantes, lo que hace aumentar mucho el error.<br />

Ahora voy a considerar los valores de error que salen <strong>para</strong> las dos isosuperficies<br />

estudiadas de este volumen de datos, la de la piel (de densidad 1200) y la del hueso<br />

(de densidad 600). Las gráficas se han realizado con el eje de abscisas con escala<br />

logarítmica, pues en las gráficas lineales simplemente se observa que el error crece<br />

de forma “exponencial” con el aumento de la tasa de compresión. (Todas las gráficas<br />

lineales tienen una forma muy similar, siendo difícil extraer más conclusiones a partir<br />

de ellas.)<br />

En la Fig. 7.3 se representa el error cuadrático medio en las imágenes renderizadas.<br />

La línea roja corresponde a la piel, y la línea negra al hueso. Se han representado<br />

juntas, <strong>para</strong> poder ser com<strong>para</strong>das mejor. Lo primero que se puede observar,<br />

es que el comportamiento de ambas curvas es muy similar al del la Fig. 7.2, con lo<br />

cual ambos métodos de calcular el error se pueden considerar válidos. Sin embargo,<br />

ahora se distingue el error correspondiente a ambas isosuperficies, algo que antes no<br />

se podía hacer.<br />

Se observa que el error en la piel es menor que el error en el hueso. Ambas<br />

curvas aparecen más o menos <strong>para</strong>lelas, lo cual indica que el error en el hueso es,<br />

aproximadamente igual al error en la piel, multiplicado por una cierta constante (de<br />

valor entre 5 y 6, aproximadamente). La razón de esta disparidad en el error de<br />

ambas isosuperficies se debe a la variación en la densidad de los datos volumétricos<br />

en el entorno a la isosuperficie considerada, en su dirección normal. En el caso de la<br />

isosuperficie correspondiente a la piel, por fuera de la misma, hay aire, de densidad<br />

despreciable con respecto a la densidad de la piel. Por el contrario, rodeando al<br />

hueso, hay tejidos de una cierta densidad, menor que la del hueso, pero considerable.<br />

Por ello, según se va aumentando la tasa de compresión, van apareciendo pequeños<br />

salientes en el hueso, y algunas islas fuera del mismo, debido a los errores en los<br />

datos de los tejidos que rodean al hueso.<br />

Estos errores se podrían disminuir realizando un preprocesado de los datos<br />

160


volumétricos, en el que se eliminen los datos que no superen un umbral de densidad.<br />

Ajustando este valor de densidad a la del tejido a estudiar (el hueso), se<br />

disminuye mucho el error. Sin embargo, este procedimiento impide obtener otras<br />

isosuperficies de densidad menor. Esto no es un inconveniente, en general, pues al<br />

especialista sólo le interesa un determinado tejido del paciente (huesos, músculos,<br />

ligamentos, . . . ).<br />

La razón de que <strong>para</strong> una compresión 2:1 ambos valores de error sean muy<br />

similares, puede ser porque los coeficientes eliminados son despreciables <strong>para</strong> la<br />

resolución de la renderización mostrada en pantalla, en ambos casos.<br />

La Fig. 7.4 muestra el número medio de errores por rodaja del volumen. El<br />

resultado obtenido es también muy similar al de las figuras anteriores. Por ello<br />

podemos considerar los tres métodos de cálculo de error equivalentes y válidos. Sin<br />

embargo, los métodos que evalúan el error de la isosuperficie, tienen la ventaja de<br />

que permiten com<strong>para</strong>r los errores cometidos por el método de compresión <strong>para</strong> cada<br />

isosuperficie.<br />

Se puede observar en esta gráfica, que ambas curvas de error también aparecen<br />

<strong>para</strong>lelas (aunque el factor en este caso está entre 3 y 4, aproximadamente). Para una<br />

tasa de compresión de 2:1, en el caso del hueso sale menor de lo esperado y en el caso<br />

de la piel sale 0. La razón de esto es que eliminamos coeficientes irrelevantes. Algunos<br />

de ellos corresponden al padeo, y otros no son observados debido a la resolución de<br />

la matriz que hemos usado <strong>para</strong> muestrear la isosuperficie. Las dimensiones de esta<br />

matriz son, <strong>para</strong> los cálculos realizados, 256 × 256 en cada rodaja, cuando los datos<br />

originales tenían 128 × 128 datos por rodaja. La razón de haber usado una matriz<br />

cuatro veces más grande es <strong>para</strong> disminuir, en lo posible, los errores introducidos por<br />

el método del cálculo del error (aunque ello conlleve un tiempo de cálculo bastante<br />

mayor).<br />

Los datos volumétricos originales tienen valores de densidad. A partir de ellos se<br />

obtiene la isosuperficie, mediante el método de marching cubes, en el que se interpolan<br />

los valores de cada punto, <strong>para</strong> obtener la posición exacta de los triángulos de<br />

la isosuperficie, según se explicó en el apartado 3.4. Sin embargo, el pasar de nuevo,<br />

de isosuperficie a malla rectangular, obtenemos sólo los puntos más cercanos a la<br />

isosuperficie, sin ningún tipo de interpolación. En este proceso, se pierde resolución<br />

e información; por ello se ha usado una matriz de muestreo con mayor resolución.<br />

No compensa coger mayor resolución aún, pues aumenta mucho el tiempo de procesamiento.<br />

Para tasas de compresión pequeñas, el error introducido por el algoritmo de<br />

cálculo del error puede ser considerable con respecto al de compresión; sin embargo,<br />

<strong>para</strong> tasas de compresión mayores, se hace despreciable.<br />

En este punto hay que señalar que <strong>para</strong> isosuperficies interiores (como el hueso),<br />

tiene mucha importancia el valor de densidad elegido. Valores de densidad distintos,<br />

que sin compresión wavelet, producen isosuperficies prácticamente idénticas, pueden<br />

161


llevar, cuando se realiza compresión, a valores de error muy distintos. Por ejemplo,<br />

si se elige un valor de densidad de 1150, en lugar de 1200, las isosuperificies sin<br />

compresión, son indistinguibles a simple vista. Sin mebargo, a medida que los datos<br />

se van comprimiendo, van apareciendo muchos más salientes e islas en el hueso, con<br />

lo que el error es mucho mayor (del orden de un 50 %-60 % mayor en NMER, y<br />

un 30 %-90 % mayor <strong>para</strong> MSER). Por ello, tiene gran importancia la exactitud en<br />

el valor de densidad elegida <strong>para</strong> isosuperficies interiores. Esto no es cierto <strong>para</strong> la<br />

isosuperficie externa (piel), en la que el error prácticamente no varía, aunque varíe<br />

algo el valor de densidad.<br />

7.4.2. Compresión mediante Diezmado<br />

En la Fig. 7.5 se muestra el error cuadrático medio en las imágenes renderizadas<br />

correspondientes al diezmado de la isosuperficie. Los valores del eje de abscisas<br />

corresponden al tanto por uno de puntos que se mantienen en la malla que representa<br />

a la isosuperficie. En rojo se muestra el error correspondiente a la piel, y en negro<br />

el correspondiente al hueso. Al igual que en la compresión wavelet, en el método de<br />

compresión por diezmado, el error <strong>para</strong> la piel es menor que el error <strong>para</strong> el hueso.<br />

Sin embargo, en este caso, la razón de esta disparidad es otra distinta. La piel es<br />

bastante más plana y lisa que el hueso, con lo que se pueden eliminar puntos de<br />

la isosuperficie introduciendo menor error. Se puede observar que ambas curvas son<br />

muy <strong>para</strong>lelas, con un factor entre ambas que varía entre 4.5 y 5.5, aproximadamente.<br />

En este caso, la variación relativa del error aumenta más al principio, habiendo<br />

luego una zona de menor pendiente. Por último, <strong>para</strong> tasas de compresión muy altas,<br />

hay varios efectos que hacen aumentar el error:<br />

Los puntos eliminados tienen cada vez mayor importancia <strong>para</strong> el error.<br />

Se empieza a producir splitting o división en la malla, con lo cual, el sombreado<br />

de la renderización aparece más abrupta en los triangulos adyacentes a las<br />

aristas que se han dividido, pues no tiene en cuenta las normales en estos<br />

triángulos adyacentes, <strong>para</strong> realizar el sombreado de la isosuperficie. Para que<br />

este efecto se produzca <strong>para</strong> tasas de compresión muy altas, se ha dado al<br />

ángulo característico un valor grande, de 75 o (ver apartado 4.3.7).<br />

La renderización de pocos triángulos aparece bastante mal, pues los triángulos<br />

que no enfocan a la fuente de luz, aparecen más oscuros <strong>para</strong> la parte de la luz<br />

correspondiente a iluminación epecular. Este efecto es mayor cuanto mayores<br />

son los triángulos (por tanto, cuantos menos triángulos haya).<br />

En la Fig. 7.6 se muestra el número medio de errores en cada rodaja del volumen,<br />

<strong>para</strong> ambas isosuperficies. Es muy parecida a la anterior, lo cual nos lleva a decir de<br />

162


nuevo que los métodos empleados <strong>para</strong> el cálculo del error son equivalentes. En este<br />

caso, el factor que relaciona ambas curvas varía entre 2 y 2.5, aproximadamente.<br />

Lo único que cabe destacar, respecto del caso anterior, es la razón del mayor<br />

aumento relativo del error <strong>para</strong> tasas de compresión muy altas.<br />

En parte, es debido a que el error de eliminar un punto es cada vez mayor;<br />

pero otra parte es debido a que, cuando se empieza a dividir la malla, el método<br />

de flood filling puede fallar, ya que no tenemos superficies cerradas. Este efecto<br />

es el que se observa <strong>para</strong> los dos valores mayores de compresión (95 % y 99 %),<br />

llegando el error <strong>para</strong> la piel, incluso, a superar al del hueso.<br />

7.4.3. Com<strong>para</strong>ción entre Ambos Métodos de Compresión<br />

Por último, en la Fig. 7.7, se muestra en escala logarítmica <strong>para</strong> ambos ejes, el<br />

número medio de errores por rodaja <strong>para</strong> la piel. En rojo aparece la curva correspondiente<br />

a la transformada wavelet y en negro la de diezmado. Se muestra esta<br />

gráfica como ejemplo, pues se podrían mostrar otras gráficas com<strong>para</strong>tivas, aunque<br />

de todas se obtienen los mimos resultados. En el eje de abscisas de la gráfica aparece<br />

la relación entre el número medio de elementos comprimidos (coeficientes wavelet,<br />

o puntos) y el número de elementos originales. Para ello, se ha considerado que el<br />

volumen de datos tiene 128 × 128 × 128 puntos.<br />

Se observa que es mucho mejor el resultado obtenido <strong>para</strong> compresión mediante<br />

la transformada wavelet, que mediante diezmado, especialmente <strong>para</strong> niveles de<br />

compresión relativamente bajos. Para compresión wavelet, también ocurre esto <strong>para</strong><br />

niveles de compresiones muy altos, <strong>para</strong> los que el aumento relativo del error, es<br />

pequeño con respecto al aumento relativo del porcentaje de compresión. Esto no<br />

ocurre <strong>para</strong> diezmado. Así, por ejemplo, el error de una compresión mediante wavelet<br />

de 10000:1 es del orden de 3.5 veces mejor que una compresión 100:1 mediante<br />

diezmado.<br />

Sin embargo, estos dos métodos de compresión, no pueden ser totalmente com<strong>para</strong>bles,<br />

pues la información que se comprime es distinta. En diezmado se comprime<br />

sólo la isosuperficie, mientras que con la transformada wavelet se comprime todo el<br />

volumen.<br />

7.5. Algunas Imágenes Renderizadas de Ejemplo<br />

Todos los resultados de error calculados, pueden servir <strong>para</strong> evaluar los métodos<br />

de compresión; sin embargo, siempre hay que ver el resultado final obtenido. A<br />

continuación se muestran varias renderizaciones <strong>para</strong> distintos niveles de compresión.<br />

163


En ellas se puede ver la calidad de los resultados obtenidos con cada método de<br />

compresión.<br />

En las Figs. 7.12 y 7.13, se muestran ejemplos de compresión de otros datos<br />

volumétricos distintos a los usados <strong>para</strong> evaluar los métodos de compresión.<br />

164


Figura 7.8: Ejemplos de renderizaciones. Compresión mediante transformada<br />

wavelet.<br />

165


Figura 7.9: Ejemplos de renderizaciones. Compresión mediante transformada<br />

wavelet. 166


167<br />

Figura 7.10: Ejemplos de renderizaciones. Compresión mediante diezmado.


168<br />

Figura 7.11: Ejemplos de renderizaciones. Compresión mediante diezmado.


Figura 7.12: Ejemplos de renderizaciones. Compresión mediante transformada<br />

wavelet, con dos niveles de compresión.<br />

169


Figura 7.13: Ejemplos de renderizaciones. Compresión mediante diezmado a una<br />

tasa de compresión de 75 %, de dos isuperficies.<br />

170


Capítulo 8<br />

Conclusiones y Líneas Futuras<br />

8.1. Conclusiones<br />

El sistema visual humano está mucho mejor adaptado a las imágenes tridimensionales,<br />

que a las bidimensionales; por ello, desde hace algún tiempo, se está desarrollando<br />

hardware y software que permite procesar y visualizar datos volumétricos,<br />

de forma rápida e interactiva.<br />

En medicina moderna, se captán datos volumétricos del cuerpo humano, mediante<br />

dispositivos como la tomografía computerizada, la resonancia magnética y los<br />

ultrasonidos. Estos datos pueden ser mucho mejor interpretados que las imágenes<br />

bidimensionales. Para ello, deben ser renderizados, y permitir interactividad por<br />

parte del profesional médico.<br />

El mayor problema de los datos volumétricos, es su enorme tamaño. Por ello<br />

cobran especial importancia los esquemas de compresión. Son especialmente interesantes<br />

los esquemas de compresión multirresolución, que permiten mostrar los datos<br />

a distintos niveles de resolución, según la compresión realizada.<br />

Se han com<strong>para</strong>do dos esquemas de compresión de datos volumétricos: compresión<br />

mediante transformada wavelet y compresión mediante diezmado de mallas<br />

triangulares.<br />

La compresión mediante la transformada wavelet, reorganiza los datos originales<br />

a un conjunto de detalles, y aproximaciones a distintos niveles de resolución. Tiene la<br />

ventaja de que posee localidad en el espacio, lo que permite obtener distintos niveles<br />

de compresión (y por tamto resolución) dentro de la misma imagen. Hemos obtenido<br />

una zona prismnática de detalle en los datos, de forma que en esta zona no se realiza<br />

compresión sobre los datos, permitiendo visualizarla a la máxima resolución, pero<br />

sin pérdida del contexto espacial global.<br />

La compresión por diezmado, elimina puntos de la malla triangular que forma<br />

la isosuperficie, de forma que el error de eliminación de cada punto sea el mínimo<br />

171


posible.<br />

En el proyecto no se ha realizado un estudio de tiempos de ejecución, debido a las<br />

grandes variaciones, debido a elementos como la memoria caché. Sin embargo, sí se<br />

ha comprobado que el tiempo de ejecución del esquema de compresión wavelet, no<br />

depende del nivel de compresión, mientras que en el diezmado, sí depende. Esto es<br />

totalmente lógico, pues <strong>para</strong> wavelet, hace las mismas operaciones, independientemente<br />

del nivel de compresión; sin embargo, <strong>para</strong> diezmado, como se van eliminando<br />

los puntos de la malla de uno en uno, el tiempo de procesamiento depende del nivel<br />

de compresión.<br />

Se ha comprobado cuantitativa y cualitativamente que la compresión mediante<br />

la transformada wavelet es mucho mejor que el diezmado. Para ello se han evaluado<br />

distintas magnitudes de error <strong>para</strong> los datos de una tomografía computerizada. Se<br />

ha calculado el error cuadrático medio en volumen, <strong>para</strong> la compresión mediante la<br />

transformada wavelet. Para ambos métodos de compresión, se ha calculado el error<br />

cuadrático medio en la imagen renderizada, y el número medio de errores por rodaja<br />

del volumen.<br />

Se ha visto en todos los resultados obtenidos que todos los métodos de compresión<br />

empleados dan resultados equivalentes, pero el número medio de errores por rodaja<br />

y el error cuadrático medio en la imagen renderizada, tienen la ventaja de que son<br />

función de la isosuperficie de interés, y no de todo el volumen.<br />

Se ha desarrollado una aplicación en la que se ha incluido una interfaz gráfica de<br />

usuario, que permite realizar ambos tipos de compresión sobre datos volumétricos,<br />

evaluar todos los tipos de error comentados sobre los datos comprimidos, y renderizar<br />

las imágenes resultantes. Además, aunque el propósito de la aplicación no es el de ser<br />

usada por profesionales médicos, permite interactuar sobre los datos renderizados,<br />

realizando rotaciones, traslaciones, cambios de zoom, semitransparencias, . . .<br />

Algunas de las aportaciones más interesantes del proyecto, de las que no tenemos<br />

conocimiento hayan sido realizadas previamente son:<br />

Com<strong>para</strong>cion de compresión mediante wavelets y mediante diezmado de mallado<br />

triangular.<br />

Obtención y estudio de los errores de compresión en distintas isosuperfies, las<br />

que están en contacto con el aire, en las que el error es menor, y las internas,<br />

en las que el error es mayor.<br />

Especificación clara de criterios <strong>para</strong> el cálculo del error en datos volumétricos.<br />

Desarrollo e implementación de un algoritmo <strong>para</strong> el acondicionamiento de<br />

volúmenes <strong>para</strong> el cálculo del error (flood filling generalizado).<br />

Realización de dos clases en VTK, <strong>para</strong> implementar el esquema de compresión<br />

mediante wavelet (vtkWaveletFilter), y <strong>para</strong> realizar el flood filling generalizado<br />

(vtkFloodFillFilter).<br />

172


Además, se ha visto la importancia de elegir de forma muy exacta el valor de<br />

la isosuperficie <strong>para</strong> realizar la compresión wavelet. Variaciones de densidad relativamente<br />

pequeñas, que apenas se ven en la renderización sin compresión, provocan<br />

aumentos muy considerables del error, cuando se comprimen los datos.<br />

8.2. Líneas Futuras<br />

Este proyecto fin de carrera forma parte del proyecto financiado por la Junta de<br />

Castilla y León, con referencia VA78/99, cuyo título es “Desarrollo de una Aplicación<br />

de Compresión y Visualización de Datos Volumétricos <strong>para</strong> los Hospitales de Castilla<br />

y León”.<br />

Por ello, este proyecto se va a continuar y ampliar de forma muy importante en<br />

un futuro inmediato. Las líneas de desarrollo futuro que se pueden seguir son muy<br />

amplias.<br />

Se puede desarrollar una aplicación distribuida, con arquitectura cliente–servidor,<br />

en la red de área amplia (WAN), formada por todos los centros hospitalarios de<br />

Castilla y León, o bien en la red de área local (LAN) de cada centro hospitalario.<br />

En estas redes, habrá servidores, que posean los datos comprimidos (con todas las<br />

versiones multirresolución), enviando a los sistemas clientes (localizados en cada<br />

hospital), los datos a la resolución requerida. Los datos se mandarán comprimidos,<br />

en orden de menor a mayor resolución, hasta el nivel de resolución requerido. De<br />

esta forma, desde que el cliente empieza a recibir datos, puede obtener una versión<br />

a baja resolución de los mismos, e ir aumentándola a medida que va recibiendo más<br />

datos.<br />

Además, en los centros hospitalarios, es donde se obtienen los datos volumétricos,<br />

que son enviados al servidor del sistema. En el servidor, los datos se almacenan en<br />

su versión multirresolución, <strong>para</strong> que si un cliente los solicita, simplemente haya que<br />

seleccionar los coeficientes según el nivel de compresión, y enviarlos por la red.<br />

En el sistema cliente sólo hay que hacer la descompresión de los datos (transformada<br />

inversa), y el proceso de visualización de los mismos.<br />

En el cliente se puede seleccionar una zona de interés, en la que se quiere mayor<br />

detalle. El servidor mandaría los detalles de esta zona, en orden creciente de<br />

resolución; de esta forma, se obtiene cada vez más detalle en la zona de interés.<br />

Uno de los problemas de la transformada wavelet sobre datos volumétricos (y<br />

de la mayor parte de las transformadas), es que opera sobre datos con un número<br />

de elementos en cada dimensión, múltiplo de dos. Esto provoca que si los datos<br />

originales no son múltiplos de dos en cada dimensión, su tamaño aumente (aunque<br />

luego pueda disminuir con la compresión). Por ello puede ser interesante dividir<br />

los datos en subvolúmenes más pequeños, en los que realizar la compresión; de esta<br />

forma el aumento de tamaño se reduce. De todas las formas, la solución más sencilla,<br />

173


y también la mejor, es que los datos tengan tamaños en cada dimensión múltiplos<br />

de dos. Esto es lo más común, pues los dispositivos de captación, obtienen matrices<br />

<strong>para</strong> cada rodaja de 256 × 256 o 512 × 512.<br />

Cuando se selecciona una zona de los datos con mayor nivel de detalle, en principio,<br />

hay que realizar la transformada inversa sobre todo el volumen de datos (operación<br />

muy ineficiente). interesa mucho desarrollar una transformada wavelet local,<br />

aprovechando la propiedad de localidad de la transformada wavelet. De esta forma,<br />

sólo se obtiene la transformada inversa de la zona de interés (que es la única que<br />

cambia). Aunque no se ha incluido en el proyecto, por no haberse implementado en<br />

la práctica, no es complicado realizar esta transformada wavelet inversa de una zona<br />

de los datos. Para ello, simplemente hay que formar un subvolumen de coeficientes<br />

wavelet formado por todos los correspondientes a la zona de interés; a continuación<br />

se realiza la transformada wavelet inversa de este subvolumen, y se coloca en el<br />

lugar adecuado del volumen total a baja resolución. El subvolumen deberá tener un<br />

tamaño en cada dimensión potencia de dos.<br />

Puede ser muy interesante realizar un estudio del error <strong>para</strong> el método de compresión<br />

wavelet en función del filtro wavelet usado. Para la realización del proyecto<br />

se ha usado el filtro Daubechies de seis coeficientes. Se ha visto que los resultados<br />

obtenidos son mucho mejores que con el filtro de Haar (no es continuo, ni derivable);<br />

sin embargo, no se ha com<strong>para</strong>do con otros filtros. Como la transformada wavelet<br />

de un volumen de datos es un proceso relativamente lento, hay que llegar a un<br />

compromiso entre calidad obtenida y número de coeficientes del filtro. Sin embargo,<br />

distintos tipos de filtros, del mismo número de coeficientes, pueden dar errores<br />

distintos.<br />

Se puede intentar combinar ambos métodos de compresión, ya que operan en<br />

dominios distintos (la transformada wavelet, sobre los datos volumétricos, y el diezmado,<br />

sobre la malla triangular que representa a la isosuperficie). Para obtener una<br />

buena interacción entre el usuario y la escena renderizada, interesa que la isosuperficie<br />

tenga el mínimo número de triángulos que permita una visualización con la<br />

calidad deseada. En principio, mediante la compresión wavelet, no podemos asegurar<br />

que el número de triángulos obtenidos al calcular la superficie, disminuya con el<br />

aumento de la tasa de compresión, pues no hay ninguna relación.<br />

Por ello se puede, en primer lugar, realizar una compresión mediante la transformada<br />

wavelet, con la arquitectura cliente–servidor explicada. A continuación, una<br />

vez descomprimidos los datos en el cliente y obtenida la isosuperficie, se puede realizar<br />

un diezmado de la misma. De esta forma se consigue una mejor interacción<br />

por parte del usuario, especialmente si el sistema cliente no tiene hardware gráfico<br />

específico <strong>para</strong> renderización.<br />

Habrá que estudiar también el tiempo de procesamiento de cada algoritmo, en<br />

función del nivel de compresión. Habrá que conseguir que los tiempos de ejecución<br />

de un algoritmo, con todos sus parámetros iguales, se mantenga aproximadamente<br />

174


constante, independientemente de otros elementos. Para ello, habrá que usar, entre<br />

otras cosas, ordenadores con memorias mayores (<strong>para</strong> evitar swapping).<br />

Una vez estudiados los tiempos de ejecución, habrá que llegar a un compromiso<br />

entre nivel de compresión, error y tiempo de ejecución. Este compromiso, será distinto,<br />

según el caso. En una apliciación ejecutada en un solo ordenador (como la del<br />

proyecto), puede tener más importancia el tiempo de procesamiento. Sin embargo,<br />

en un sistema distribuido (especialmente en una WAN), tiene mucha más importancia<br />

el nivel de compresión, pues el cuello de botella del sistema está en el ancho de<br />

banda de la red.<br />

175


176


Capítulo 9<br />

Pliego de Condiciones<br />

En este capítulo se enumeran los medios materiales usados <strong>para</strong> la realización del<br />

proyecto, así como su precio. No se ha pretendido realizar un presupuesto detallado<br />

del proyecto. Sin embargo, se puede ver que la mayor parte de los medios usados<br />

<strong>para</strong> la realización del mismo son de distribución gratuita. No se muestran entre<br />

los medios materiales los libros y artículos empleados, pues no ha sido necesario<br />

comprarlos <strong>para</strong> la realización del proyecto.<br />

Ordenador Pentium II 266 MHz:<br />

Monitor 19 pulgadas:<br />

Escáner color Primax 4800 × 4800:<br />

Impresora HP DeskJet 890 C:<br />

Impresora láser HP LaserJet 4000 N:<br />

Material de papelería y fotocopias:<br />

Linux Slackware 2.0.30:<br />

Windows ’95:<br />

VTK 2.0:<br />

Tcl/Tk 8.0:<br />

Picture Publisher 6.0:<br />

L A TEX V. 3.14159 (C V. 6.1):<br />

220.000 pts.<br />

140.000 pts.<br />

25.000 pts.<br />

70.000 pts.<br />

100.000 pts.<br />

5.500 pts.<br />

0 pts.<br />

17.500 pts.<br />

0 pts.<br />

0 pts.<br />

0 pts.<br />

0 pts.<br />

177


178


Bibliografía<br />

[1] J. D. Bronzino, Editor in Chief, The Biomedical Engineering. Handbook, CRC<br />

Press – IEE Press, Boca Ratón, Florida, USA, 1995.<br />

[2] A. S. Tenenbaum, Redes de Ordenadores, (Segunda Edición), Prentice Hall<br />

Hispanoamericana, México D. F., México, 1991.<br />

[3] F. J. Owens, Signal Processing of Speech, Macmillan New Electronics. Introduction<br />

to Advanced Topics, Londres, G.B., 1993.<br />

[4] J. Watkinson, The Art of Digital Audio, (Segunda Edición), Focal Press, Oxford,<br />

G.B., 1994.<br />

[5] G. Plenge, DAB – A New Broadcasting System – Status of the Development –<br />

Routes to its Introduction, EBU Review – Technical, Número 246, Abril 1991.<br />

[6] C. W. Brown and B. J. Shepherd, Graphics File Formats. Reference and Guide,<br />

Manning, Greenwich, G.B., 1995.<br />

[7] B. V. Dasarathy, Image Data Compression. Block Truncation Coding, IEEE<br />

Computer Society Press, Págs. 1-54, 1995.<br />

[8] R. J. Clarke, Digital Compression of Still Images and Video. Signal Processing<br />

and its Applications, Academic Press Inc., San Diego, California, USA, 1995.<br />

[9] S. Muraki, Approximation and Rendering of Volume Data Using Wavelet Transforms,<br />

Proc. of IEEE Visualization 1992, Pgs. 21-28.<br />

[10] S. Muraki, Volume Data end Wavelet Transforms, IEEE Computer Graphics<br />

and Applications, Vol. 13, Número 4, Págs. 50-56, Julio 1993.<br />

[11] S. G. Mallat A Theory for Multiresolution Signal Decomposition: The Wavelet<br />

Representation, IEEE Transactions on Pattern Analysis and Machine Inteligence,<br />

Vol. 11, Número. 7, Págs. 674-693, Julio 1989.<br />

[12] W. Schroeder, K. Martin and B. Lorensen, The Visualization Toolkit. An<br />

Object-Oriented Approach to 3D Graphics, (Segunda Edición), Prentice Hall<br />

PTR, New Jersey, USA, 1997.<br />

179


[13] W. E. Lorensen and H. E. Cline, Marching Cubes: a High Resolution 3D Surface<br />

Construction Algorithm, Coputer Graphics, Vol. 21, Número 4, Págs. 163-169,<br />

Julio 1987.<br />

[14] P. Cignoni, C. Montani, E. Puppo, and R. Scopigno, Multiresolution Representation<br />

and Visualization of Volume Data, IEEE Transactions on Visualization<br />

and Computer Graphics, Vol. 3, Número 4, Págs. 352-369, Octubre-Diciembre<br />

1997.<br />

[15] B. B. Welch, Practical Programming in Tcl and Tk (Segunda Edición), Prentice<br />

Hall PTR, New Jersey, USA, 1997.<br />

[16] K. D. Cohen, Feature Extraction and Pattern Analysis of Three-Dimensional<br />

Objects, Master Thesis, Thayer School of Engeneering, Darmouth College, 1996<br />

180


Apéndice A<br />

Manual de Referencia<br />

A.1.<br />

Introducción<br />

En este capítulo se van a comentar algunos aspectos de la implementación de la<br />

aplicación. En el apéndice C se muestran parte de los listados de la aplicación. No<br />

se han mostrado todos, debido a su gran tamaño; sin embargo, espero que sirvan<br />

<strong>para</strong> entender la estructura del programa. A continuación s describe la estructura<br />

general del programa, así como los objetos de VTK usados <strong>para</strong> su realizaciónm.<br />

así como las clases de VTK que se han tenido que realizar, por no estar disponibles<br />

entre las ofrecidas por este sistema de programación gráfica.<br />

A.2.<br />

Estructura general del Programa<br />

En el apéndice B se describen algunos aspectos concretos de VTK; además se<br />

com<strong>para</strong> la realización de programas compilados en C++, frente a programas interpretados<br />

en Tcl/Tk.<br />

Para la realización general de la aplicación se ha elegido el lenguaje de programación<br />

interpretado Tcl/Tk, debido a sus ventajas sobre C++.<br />

Permite una programación y modificación del programa más rápida, pues no<br />

requiere compilación.<br />

La realización de interfaces gráficas en, al mismo tiempo, mediante Tk es muy<br />

sencilla y potente.<br />

Desde el script Tcl/Tk, además de hacer el GUI, básicamente se crean instancias<br />

de las clases de VTK. Como estas clases son compiladas, a pesar<br />

de usar un lenguaje interpretado, las aplicaiones son relativamente rápidas,<br />

181


aprovechando, de esta forma, las ventajas de los lenguajes interpretados y las<br />

de los lenguajes compilados.<br />

Es un lenguaje sencillo, y a mi entender muy interesante, pues permite hacer<br />

muchas de las cosas que se pueden hacer en lenguajes tan completos como C.<br />

De hecho, hay muchos comandos que se parecen a las funciones de C. (No en<br />

vano, Tcl/Tk está programado en C).<br />

Sin embargo, debido a que no todas las operaciones que se han tenido que realizar<br />

sobre los datos, se encontraban programadas en VTK, se ha tenido que realizar un<br />

par de clases nuevas, descritas en apartados posteriores.<br />

Además, el cálculo de errores también se ha programado como aplicaciones ejecutables<br />

programadas en C++. Esto se ha hecho así debido al enorme número de<br />

operaciones que hay que realizar <strong>para</strong> calcular el error, haciendo imposible su programación<br />

en Tcl. Los cálculos de error no han sido programado como clases, debido a<br />

que no se han considerado suficientemente generales como <strong>para</strong> intentar aprovechar<br />

la modularidad de una clase. Además las clases abstractas de proceso que ofrece<br />

VTK (ver apéndice B), están más adaptadas <strong>para</strong> realizar filtros, y no <strong>para</strong> obtener<br />

resultados numéricos a partir de clases de entrada.<br />

En el apéndice C se muestra parte del código general de la aplicación. Para<br />

entenderlo mejor, voy a describir brevemente las instancias de las clases usadas. Se<br />

puede encontrar más información sobre algunas de estas clases en el apéndice B (se<br />

recomienda leer antes este apéndice, <strong>para</strong> entender los objetos usados en VTK), y<br />

muchas más en [12]. Se muestra el nombre de la clase, seguido por el nombre de la<br />

instancia creada:<br />

Objetos usados <strong>para</strong> la renderización<br />

• vtkTkRenderWidget .Render: ventana de renderización. Es un Widget<br />

de Tk que permite la integración de la ventana de renderización, con el<br />

GUI realizado en Tk.<br />

• vtkRenderer render: objeto renderizador. Sirve <strong>para</strong> gestionar una ventana<br />

en la pantalla.<br />

• vtkLight luz: fuente de luz <strong>para</strong> iluminar la escena.<br />

• vtkCamera camara: cámara virtual <strong>para</strong> la renderización de escenas 3D.<br />

• vtkLODActor isoActor: actor <strong>para</strong> la isosuperficie obtenida. Esta clase<br />

permite varios niveles de resolución, lo cual permite mejor interactividad<br />

del usuario con la escena. Los actores representan objetos (geometría y<br />

propiedades) en una escena renderizada.<br />

• vtkActor lineaExternaActor: actor <strong>para</strong> el prisma de color negro que<br />

representa el borde externo de la isosuperficie.<br />

182


• vtkActor mensajeActor: actor <strong>para</strong> el objeto de texto que aparece al<br />

ejecutar la aplicación.<br />

• vtkActor cuboActor: actor <strong>para</strong> el prisma de color blanco, usado <strong>para</strong><br />

posicioar el detalle en compresión mediante la transformada wavelet.<br />

• vtkActor ejesActor: actor usado <strong>para</strong> representar los ejes que se muestran<br />

en la pantalla de selección de detalle (wavelet).<br />

• vtkActor XActor, YActor, ZActor: actores <strong>para</strong> el texto que etiqueta<br />

cada uno de los ejes de ejesActor.<br />

• vtkProperty: objeto <strong>para</strong> definir las propiedades de la superficie del actor<br />

isoActor.<br />

Objetos de datos<br />

• vtkStructuredPoints Volumen: objeto de datos con topología y geometría<br />

regular. Se usa <strong>para</strong> almacenar los datos de la tomografía computerizada,<br />

leídos del disco. También se usa como salida del filtro que<br />

realiza la transformada wavelet.<br />

• vtkPolyData PolyData: objeto de datos que representa vértices, líneas,<br />

polígonos y tiras de triángulos. Este objeto representa la isosuperficie<br />

obtenida.<br />

Objetos de proceso fuente<br />

• vtkVectorText mensaje: objeto que crea texto como datos poligonales.<br />

Sirve <strong>para</strong> crear el texto que aparece en la ventana de renderización al<br />

iniciar la aplicación.<br />

• vtkCubeSource cubo: objeto <strong>para</strong> crear un cubo como datos poligonales.<br />

Se usa <strong>para</strong> seleccionar el detalle <strong>para</strong> la transformada wavelet.<br />

• vtkAxes ejes: objeto <strong>para</strong> crear los ejes usados en la ventana de selección<br />

del detalle.<br />

• vtkTextSource textoX, textoY, textoZ: crea texto como datos poligonales.<br />

Sirve <strong>para</strong> crear las etiquetas de los ejes.<br />

• vtkVolumen16Reader lectorVol: objeto lector, usado <strong>para</strong> leer los archivos<br />

con los datos de la tomografía Estos datos están en varios archivos, con<br />

el mismo nombre , salvo la extensión, que indica el número de rodaja.<br />

Los archivos tienen datos de 16 bits. La salida de este objeto tiene datos<br />

del tipo unsigned short, que pueden valer entre 0 y 2 16 − 1 = 65535.<br />

• vtkSLCReader lectorVol: objeto lector <strong>para</strong> leer datos volumétricos en<br />

un solo archivo, con extensión slc.<br />

Objetos de proceso filtro<br />

183


• vtkMarchingCubes iso: objeto usado <strong>para</strong> obtener la isosuperficie, mediante<br />

el método marching cubes, descrito en el apartado 3.4.<br />

• vtkDecimatePro deciPro: objeto <strong>para</strong> realizar el diezmado de la isosuperficie.<br />

De las técnicas avanzadas explicadas en el apartado 4.3.7, este<br />

filtro implementa el algoritmo que modifica la estructura de la malla,<br />

descrito en ese apartado, pero no la reducción progresiva.<br />

• vtkVoxelModeller borde: filtro que transforma la isosuperficie en un<br />

objeto del tipo vtkStructuredPoints binario, en el que hay unos en los<br />

puntos por los que pasa la isosuperficie y ceros en el resto. Se usa como<br />

procesado previo al objeto flood, <strong>para</strong> transformar la malla poligonal en<br />

una estructura rectangular regular.<br />

• vtkFloodFillFilter flood: filtro que sirve <strong>para</strong> rellenar el volumen interior<br />

a una superficie cerrada (que se encuentra en forma de vtkStructuredPoints,<br />

respetando las cavidades interiores que haya en el volumen).<br />

Esta clase se ha tenido que crear <strong>para</strong> realizar la aplicación, por lo que<br />

se describe en el apartado A.4.<br />

• vtkWaveletFilter wavelet: objeto que realiza la compresión de un conjunto<br />

de datos con estructura vtkStructuredPoints. Permite obtener,<br />

además un detalle en el que no se realiza compresión. Esta clase también<br />

se ha creado <strong>para</strong> el proyecto, por lo que se describe en el apartado A.3.<br />

• vtkOutlineFilter lineaExterna: filtro usado <strong>para</strong> obtener un prisma<br />

que contiene exactamente a la isosuperficie.<br />

objetos de proceso mapeadores<br />

• vtkPolyDataMapper isoMapper: objeto usado <strong>para</strong> convertir los datos<br />

poligonales de la isosuperficie en primitivas gráficas (mapeador).<br />

• vtkPolyDataMapper lineaExternaMapper: objeto que realiza el mapeado<br />

<strong>para</strong> el prisma que rodea a la isosuperficie.<br />

• vtkPolyDataMapper mensajeMapper: objeto mapeador usado <strong>para</strong> el texto<br />

que aparece al ejecutar la aplicación.<br />

• vtkPolyDataMapper cuboMapper: objeto mapeador <strong>para</strong> el prisma usado<br />

<strong>para</strong> seleccionar un detalle, en compresión mediante la transformada<br />

wavelet.<br />

• vtkPolyDataMapper ejesMapper: objeto mapeador usado <strong>para</strong> el objeto<br />

ejes.<br />

• vtkPolyDataMapper XMapper, YMapper, ZMapper: objetos mapeadores<br />

<strong>para</strong> las etiquetas de los ejes.<br />

• vtkStructuredPointsWriter: objeto escritor, usado <strong>para</strong> escribir en un<br />

fichero el objeto vtkStructuredPoints salida del filtro vtkFloodFill-<br />

Filter.<br />

184


• vtkMCubesWriter writer: objeto usado <strong>para</strong> guardar en un archivo la<br />

isosuperficie obtenida.<br />

El mayor problema <strong>para</strong> realizar la red de visualización de la aplicación es su<br />

interactividad. En ella, el usuario es el que indica, mediante los comandos de la<br />

barra de menús, las operaciones que se van realizando sobre los datos. Por ello no<br />

se puede usar una arquitectura fija <strong>para</strong> la red de visualización de la aplicación.<br />

Para afrontar este problema, lo que se ha hecho es mantener los datos siempre<br />

en dos objetos de datos fijos, que son la entrada y la salida de la mayoría de los<br />

filtros ejecutados. De esta forma, sea cual sea el orden de ejecuación del programa,<br />

siempre sabemos dónde están los datos. Si no se hiciera así, tendríamos el problema<br />

de que la arquitectura de la red de visualización cambiaría, dependiendo del orden<br />

de ejecución de los filtros, por parte del usuario de la aplicación.<br />

Los dos objetos en los que se guardan los datos, son los siguientes:<br />

vtkStructuredPoints Volumen: éste es el objeto de datos en el que se guardan<br />

los datos leídos de los ficheros. Tienen una estructura en malla rectangular<br />

regular (topológica y geométricamente). Este objeto sirve de entrada al filtro<br />

que implementa el algoritmo de marching cubes (vtkMarchingCubes iso),<br />

y es la salida, tanto del objeto lector de los ficheros (vtkVolumen16Reader<br />

lectorVol), como del objeto de compresión wavelet (vtkWaveletFilter wavelet).<br />

vtkPolyData PolyData: este objeto contiene la malla triangular que representa<br />

a la isosuperficie. Es la entrada al objeto que realiza la conversión de<br />

los datos poligonales a primitivas gráficas (vtkPolyDataMapper isoMapper)<br />

y al objeto que guarda en un fichero la malla triangular (vtkMCubesWriter<br />

writer). Es la salida del filtro de marching cubes (vtkMarchingCubes iso)<br />

y del filtro que realiza el diezmado de la malla triangular (vtkDecimatePro<br />

deciPro).<br />

La red de visualización, o pipeline de visualización resultante, se muestra en la<br />

Fig. A.1. En ella se han puesto solamente los objetos más importantes, <strong>para</strong> que sea<br />

de fácil comprensión.<br />

Los filtros que realizan la compresión, no toman su entrada de los dos objetos<br />

que guardan los datos, sino del objeto anterior en el pipeline. La razón de hacer<br />

esto así, es <strong>para</strong> que si se realiza varias veces la compresión de los datos, las tasas<br />

de compresión sean absolutas, y no acumulativas. Si se tomaran las entradas de los<br />

filtros de compresión de estos dos objetos (respectivamente), y se realiza varias veces<br />

un tipo de compresión, cada vez se tomaría la entrada del resultado de la compresión<br />

anterior, lo cual no es deseable.<br />

185


fichero.93<br />

fichero.1<br />

vtkVolumen16Reader<br />

lectorVol<br />

vtkWaveletFilter<br />

wavelet<br />

vtkStructuredPoints<br />

Volumen<br />

vtkMarchingCubes<br />

iso<br />

vtkDecimatePro<br />

deciPro<br />

vtkPolyData<br />

PolyData<br />

vtkMCubesWriter<br />

writer<br />

vtkPolyDataMapper<br />

isoMapper<br />

fichero.vtk<br />

Figura A.1: Red de visualización de la aplicación.<br />

186


Se puede ver en la figura, la estructura general de todas las redes de visualización.<br />

Siempre empiezan por objetos fuente, y terminan en objetos mapeadores (o<br />

escritores), estando en medio los filtros que sean necesarios.<br />

Para mantener los datos en los objetos PolyData y Volumen, se usan dos procedimientos<br />

de vtk muy sencillos, que se muestran a continuación<br />

# Procedimiento <strong>para</strong> actualizar el structured points<br />

# despues de la compresion mediante la tranformada wavelet<br />

proc ActualizarVolumen {filtro} {<br />

Volumen CopyStructure [$filtro GetOutput]<br />

# Solucion al bug de que sale 0 en el numero<br />

# de dimensiones<br />

set dims [Volumen GetDimensions]<br />

# Da igual las dimensiones que se pongan<br />

Volumen SetDimensions 1 2 1<br />

eval Volumen SetDimensions [split $dims]<br />

}<br />

[Volumen GetPointData] PassData [[$filtro GetOutput] \<br />

GetPointData]<br />

Volumen Modified<br />

# Procedimiento <strong>para</strong> actualizar la renderizacion despues de<br />

# alguna operacion<br />

proc ActualizarPolyData {filtro} {<br />

PolyData CopyStructure [$filtro GetOutput]<br />

[PolyData GetPointData] PassData [[$filtro GetOutput] \<br />

GetPointData]<br />

PolyData Modified<br />

}<br />

Estos procedimientos copian la salida del filtro, que toma como argumento al objeto<br />

de datos correspondiente. El método vtkStructuredPoints::CopyStructure()<br />

tiene un bug, que consiste en que al copiar la estructura de un objeto a otro, pone el<br />

número de dimensiones a 0. Se puede solucionar de la forma que se muestra arriba.<br />

Para llamar a estos procedimientos, se pone el nombre del procedimiento y el<br />

del filtro. Con esto basta <strong>para</strong> copiar la salida del filtro al objeto correspondiente.<br />

A continuación se muestran dos sentencias de ejemplo, <strong>para</strong> ambos filtros.<br />

# Actualizo los valores del objeto Volumen con la<br />

187


# salida del filtro wavelet<br />

ActualizarVolumen wavelet<br />

# Actualizo el valor del objeto PolyData con la<br />

# isosuperficie decimada<br />

ActualizarPolyData deciPro<br />

Los funciones realizadas por los filtros, se encuentran en procedimientos, que son<br />

llamados desde los botones del menú. Al ejecutarse estos procedimientos, lo primero<br />

que se suele hacer es mostrar el panel de control correspondiente. Los paneles de<br />

control se encuentran disponibles desde el principio de la ejecución de la aplicación;<br />

sin embargo no se muestran en patalla. A continuación se muestra un pequeño esbozo<br />

de estas operaciones<br />

## Creo la barra de menu (segun la forma de Tk 8.0)<br />

menu .menubar<br />

# Lo fijo en la ventana principal<br />

. config -menu .menubar<br />

# Creo menus en cascada<br />

foreach m {Archivo Operaciones Vista Error Ayuda} {<br />

set $m [menu .menubar.m$m -tearoff FALSE]<br />

.menubar add cascade -label $m -menu .menubar.m$m<br />

}<br />

# Annado los elementos del menu Operaciones<br />

$Operaciones add command -label Isosuperficie -command \<br />

menuMarchingCubes -state disabled<br />

... -> Resto de elementos del menu<br />

## procedimiento <strong>para</strong> obtener la isosuperficie<br />

proc menuMarchingCubes {} {<br />

global isoValor resultadoMenu<br />

# Muestro el menu de leer el volumen<br />

wm deiconify .marching<br />

# Pongo un valor que no puede tener la variable<br />

set resultadoMenu 2<br />

# Muestro el menu con las opciones de Marching Cubes<br />

# y espero a que se cierre<br />

# Espero a que cambie el valor (a 1 o 0),<br />

# con lo que se habra cerrado la ventana .volumen<br />

vwait resultadoMenu<br />

188


# Si se ha pulsado el boton aceptar<br />

if {$resultadoMenu == 1} {<br />

# Extraigo la isosuperficie a partir del objeto Volumen<br />

iso SetInput Volumen<br />

... -> Llamada al resto de metodos de la instancia iso<br />

}<br />

## Creacion del panel de control<br />

toplevel .marching<br />

# Oculto esta ventana hasta que sea necesaria<br />

wm withdraw .marching<br />

wm title .marching "Obtencion de isosuperficie"<br />

# Cuando se cierra la ventana no se elimina,<br />

# sino que se quita de pantalla<br />

wm protocol .marching WM_<strong>DE</strong>LETE_WINDOW {wm withdraw .marching}<br />

# Creo un frame<br />

frame .marching.f1<br />

... -> Otros elementos del panel de control<br />

# Frame <strong>para</strong> botones de aceptar y cancelar<br />

frame .marching.f3<br />

# Muestro el frame<br />

pack .marching.f3 -side top -expand true -fill x -pady 20<br />

button .marching.f3.aceptar -text Aceptar -command \<br />

{botonPulsado 1 .marching}<br />

button .marching.f3.cancelar -text Cancelar -command \<br />

{botonPulsado 0 .marching}<br />

pack .marching.f3.aceptar .marching.f3.cancelar -side left \<br />

-expand true<br />

proc botonPulsado {valor vent} {<br />

# Variable global usada <strong>para</strong> guardar el resultado del menu<br />

global resultadoMenu<br />

# Resultado del menu: Aceptar (1), Cancelar (0)<br />

set resultadoMenu $valor<br />

# Quito de pantalla la ventana del menu<br />

189


}<br />

wm withdraw $vent<br />

Al final del ejemplo, se pueden ver los botones Aceptar y Cancelar, que aparecen<br />

en todos los paneles de control. Al pulsar estos botones, se llama al procedimiento<br />

botonPulsado, que tiene dos argumentos, el botón pulsado (1 <strong>para</strong> Aceptar y 0 <strong>para</strong><br />

Cancelar) y la ventana del panel de control en la que se ha pulsado el botón. (De<br />

esta forma, se puede ocultar la ventana dentro del procedimiento botonPulsado).<br />

En el procedimiento menuMarchingCubes, se puede ver la forma por la que conoce<br />

cuándo se ha cerrado el menú y qué botón se ha pulsado. Se usa la variable global<br />

resultadoMenu. Al mostrar el panel de control, se asigna a esta variable un valor<br />

que no puede tener, 2, y a continuación se espera a que cambie de valor, algo que<br />

sucede en el procedimiento botonPulsado. Según el valor devuelto (1 ó 0), se conoce<br />

el botón pulsado y se actúa en consecuencia, como se ve en el fragmento de código<br />

mostrado.<br />

A.3.<br />

vtkWaveletFilter<br />

Esta clase realizar al compresión mediante la transformada wavelet. El nombre<br />

elegido <strong>para</strong> esta clase es vtkWaveletFilter, bastante descriptivo de su funcionalidad.<br />

En VTK hay varios filtros abstractos, a partir de los que se pueden derivar nuevos<br />

filtros concretos. Según el filtro del que se derive, dependerán los objetos de entrada<br />

y salida del filtro obtenido. Hay que llegar a un compromiso entre generalidad y<br />

funcionalidad. Cuanto más generales se permitan los objetos de entrada y salida del<br />

filtro, menos eficiente será. En el caso de este filtro, la transformada wavelet sólo se<br />

puede realizar <strong>para</strong> estructuras de datos vtkStructuredPoints, por lo que usamos<br />

este tipo de datos como entrada y salida del filtro. De esta forma, no perdemos en<br />

generalidad, pues la transformada wavelet está adaptada sólo a este tipo de datos,<br />

pero ganamos en funcionalidad, respecto a estructuras de datos más generales. En<br />

la Fig. A.2 se muestra el diagrama OMT de herencia de esta clase.<br />

Casi todas las clases de VTK se derivan a partir de la clase vtkObject, que define<br />

algunos elementos, como los tiempos de modificación, o las opciones de debugging.<br />

Las clases derivadas de vtkSource generan datos de salida, y deben tener como<br />

funciones miembro de la clase Update() y Execute(). En vtkFilter se definen las<br />

funciones miembro Update() y Execute(), que hacen que el filtro se ejecute si la<br />

entrada o el filtro se ha modificado desde la última vez que se ejecutó, como se<br />

describe en el apartado B.1.2.<br />

Todas las clases derivadas de vtkStructuredPointsFilter, toman una estructura<br />

de datos vtkStructuredPoints como entrada. En esta clase se definen los<br />

190


vtkObject<br />

vtkSource<br />

vtkFilter<br />

vtkStructuredPointsFilter<br />

vtkStructuredPointsToStructuredPointsFilter<br />

vtkWaveletFilter<br />

Figura A.2: Diagrama OMT de herencia de la clase vtkWaveletFilter.<br />

191


métodos SetInput(), <strong>para</strong> establecer la entrada del filtro; y GetInput(), que devuelve<br />

la entrada asignada al filtro. También se define la variable de la clase Input,<br />

que contiene una referencia al objeto de entrada al filtro.<br />

Por último, las clases derivadas de vtkStructuredPointsToStructuredPoints-<br />

Filter tienen también vtkStructuredPoints como datos de salida del filtro. Se<br />

define la función miembro GetOutput(), y el objeto de salida Output.<br />

Derivando nuestro filtro de esta forma, ahorramos parte del trabajo de creación<br />

del mismo, pues hay funciones miembro que ya están implementadas, o al menos<br />

definidas de forma abstracta (dejando la implementación concreta <strong>para</strong> las clases<br />

derivadas).<br />

Para implementar la clase, hay que realizar la definición de la clase, en el archivo<br />

de cabecera vtkWaveletFilter.h, e implementar las funciones miembro en el<br />

archivo vtkWaveletFilter.cxx.<br />

A continuación se va a describir brevemente los elementos miembros de la clase,<br />

definidos en ella:<br />

Miembros protegidos: son los elementos de la clase a los que sólo se puede<br />

acceder desde las funciones miembros de la clase y sus subclases derivadas.<br />

• int WaveCompresion. Variable usada <strong>para</strong> establecer la relación de compresión,<br />

de la forma N:1.<br />

• int NumCoeficientes. Variable <strong>para</strong> calcular y guardar el número de<br />

coeficientes wavelet que se cogen.<br />

• float Error. Variable <strong>para</strong> calcular el error cuadrático medio de la compresión.<br />

• int DimsReales[3]. Dimensiones del objeto de entrada.<br />

• int Dims[3]. Dimensiones del objeto de entrada, una vez padeado <strong>para</strong><br />

que todas las dimensiones sean potencias de 2. La transformada wavelet<br />

se hace sobre datos cuyas dimensiones sean potencias de 2.<br />

• int Detalle[6]. Array usado <strong>para</strong> seleccionar la zona en la que se obtiene<br />

el detalle sin compresión.<br />

• int FlagYaHecho. Variable que vale 1 si el filtro se ha ejecutado alguna<br />

vez, en cuyo caso ya no hay que hacer la transformada inversa, por tener<br />

ya los coeficientes almacenados.<br />

• int FlagDetalle. Variable que vale 1 si se ha de comprimir respetando<br />

una zona de detalle, en la que no se realiza distorsión.<br />

• vtkFloatScalars *WaveCoefs. Array de escalares en punto flotante usado<br />

<strong>para</strong> almacenar los coeficientes obtenidos de la transformada wavelet<br />

directa.<br />

192


• vtkIntScalars *IndicesOrdenados. Array de escalares enteros usado<br />

<strong>para</strong> almacenar los coeficientes wavelet ordenados. Sirve de índice <strong>para</strong><br />

acceder a los coeficientes ordenados.<br />

• vtkBitScalars *Filtro. Array binario usado <strong>para</strong> realizar el mapeado<br />

de la zona de detalle en el espacio, a la zona de detalle en el dominio de<br />

la transformada wavelet. Se calcula en la función CalcularDetalle().<br />

• void Execute(). Función principal de la clase, que se encarga de obtener<br />

la salida del filtro, que es el resultado de la compresión mediante la<br />

transformada wavelet del objeto de entrada. Además, sirve <strong>para</strong> obtener<br />

el detalle, en caso de ser solicitado. Si ya se ha ejecutado alguna vez,<br />

no hay que repetir la transformada directa, ni la ordenación de los coeficientes,<br />

por ya estar almacenados en objetos miembros de la clase.<br />

• void QuickSortStart(float *datos),<br />

void QuickSortRecursive(float *datos,int prim,int ult),<br />

int PartitionData(float *datos, int prim, int ult).<br />

Funciones usadas <strong>para</strong> implementar el método de ordenación Quick Sort,<br />

considerado como el método más rápido de ordenación de datos. Es importante<br />

que sea rápido debido al gran número de datos. Sirven <strong>para</strong><br />

ordenar los coeficientes de significancia (ver apratado 5.4.)<br />

• static void wfltr convolve (...),<br />

static void wxfrm 1d varstep (...),<br />

static void wxfrm nd nonstd (...),<br />

void wxfrm fand (...).<br />

Funciones usadas <strong>para</strong> realizar la transformada wavelet directa e inversa.<br />

Estas funciones se han obtenido del UBC Imager Wavelet Package –<br />

Release 3.0 beta, que se ha portado a C++, <strong>para</strong> hacerlas miembros de<br />

la clase.<br />

Se ha elegido como filtro <strong>para</strong> realizar la transformada wavelet, el de<br />

Daubechies de 6 coeficientes. Sólo se han com<strong>para</strong>do los resultados<br />

obtenidos con el de Haar, que evidentemente son mucho peores (el filtro de<br />

Haar no es continuo, ni derivable). No se ha p‘tado por un filtro de muchos<br />

coeficientes, debido al enorme número de datos, lo cual ralentizaría mucho<br />

la ejecución. En un futuro, se podrá com<strong>para</strong>r con otros filtros.<br />

• void Significancia(float *puntWavelet). Función que sirve <strong>para</strong> calcular<br />

la significancia de los coeficientes wavelet.<br />

Miembros públicos: son los elementos de la clase a los que se puede acceder<br />

desde todas las funciones, pertenezcan o no a la clase.<br />

• vtkSetMacro(WaveCompresion,int),<br />

vtkGetMacro(WaveCompresion,int).<br />

Macros de VTK <strong>para</strong> crear las funciones SetWaveCompresion(int) y<br />

193


GetWaveCompresion(int). Estas funciones son las que permiten establecer<br />

el valor de las variables protegidas de la clase. Así se tiene control total<br />

sobre los miembros protegidos (o privados). Sirven <strong>para</strong> leer y asignar la<br />

tasa de compresión.<br />

• vtkGetMacro(NumCoeficientes,int). Macro <strong>para</strong> leer el número de coeficientes<br />

empleados <strong>para</strong> obterner la aproximación.<br />

• vtkGetMacro(Error,float). Macro <strong>para</strong> leer el valor del error cuadrático<br />

medio obtenido.<br />

• void CalcularDetalle(float borde[6]). Función que se utiliza <strong>para</strong><br />

calcular el mapeado del detalle seleccionado, en el dominio de la transformada<br />

wavelet.<br />

• vtkGetVectorMacro(Detalle, int, 6). Macro <strong>para</strong> leer el detalle actualmente<br />

calculado.<br />

• vtkSetMacro(FlagDetalle, int),<br />

vtkGetMacro(FlagDetalle, int).<br />

Macros <strong>para</strong> indicar o leer si se ha de calcular la compresión con detalle.<br />

• vtkBooleanMacro(FlagDetalle, int). Este macro se convierte en las<br />

funciones FlagDetalleOn() y FlagDetalleOff()<br />

Algunas funciones interesantes de la clase (no mostradas anteriormente), son el<br />

contructor de la clase, vtkWaveletFilter(), y el destructor, vtkWaveletFilter().<br />

En estas dos funciones se inicializan las variables protegidas de la clase y se eliminan<br />

los objetos de la clase, mediante la función Delete() de cada clase, respectivamente.<br />

De todas funciones miembro de la clase, la más importante es Execute(), pues<br />

es ésta la función en la que se realiza el algoritmo de compresión. Otras funciones<br />

muy interesante son CalcularDetalle(...), en la que se calculan los coeficientes<br />

wavelet correspondientes a la zona de detalle seleccionada, y Significancia(...),<br />

en la que se calcula la significancia de los coeficientes de la transformada wavelet.<br />

En el apéndice C se muestra el código de esta clase, suficientemente comentado<br />

<strong>para</strong> entender su funcionamiento. Sin embargo voy a comentar brevemente las<br />

operaciones realizadas en estas dos funciones.<br />

A.3.1.<br />

Función vtkWaveletFilter::Execute()<br />

Esta función, en general siempre tiene la misma estructura, formada por cinco<br />

pasos:<br />

1. Declaración de variables. Se declaran las variables locales de la clase,<br />

incluyendo los objetos VTK necesarios, punteros y referencias. Se declaran<br />

194


e inicializan, además punteros a los objetos de entrada (Input) y de salida<br />

(Output). Estas variables son del tipo general de estructura de datos<br />

vtkDataSet, por lo que se tiene que hacer el casting a objetos del tipo del<br />

filtro (vtkStructuredPoints). Además se obtienen los puntos de estos objetos,<br />

y de los puntos se obtienen los valores escalares que forman el objeto. Esto<br />

se realiza con las siguientes líneas:<br />

// Estos son punteros, por lo que no hay que borrarlos<br />

vtkStructuredPoints *input=(vtkStructuredPoints *)this->Input;<br />

vtkPointData *pd = input->GetPointData();<br />

vtkScalars *escalares = (vtkScalars *)pd->GetScalars();<br />

vtkStructuredPoints *output=(vtkStructuredPoints *)this->Output;<br />

vtkPointData *outPd = output->GetPointData();<br />

de esta forma se pueden leer ya los datos de entrada, sus dimensiones, . . . Los<br />

escalares de salida no se leen aquí, pues se crean en esta clase y se asignan al<br />

final de la función al objeto de salida de la clase, como veremos más adelante.<br />

Se crean dos objetos de escalares, usados respectivamente <strong>para</strong> almacenar los<br />

datos padeados (con cero) <strong>para</strong> que sean potencia de dos en cada dimensión,<br />

y <strong>para</strong> almacenar los datos de salida, resultado del filtro:<br />

vtkFloatScalars *escalaresPadeados;<br />

vtkUnsignedShortScalars *outEscalares;<br />

2. Inicialización. Me aseguro de la validez y consistencia de los datos de entrada.<br />

Se puede incluir información de debugging <strong>para</strong> proporcionar información al<br />

usuario sobre la entrada al filtro y otras características importantes.<br />

Leemos las caracterísitcas de los datos de entrada (dimensiones, espaciado y<br />

origen), y se calculan las dimensiones padeadas, que deben ser potencia de 2<br />

(Dims[3]) y las dimensiones en orden inverso, nA, con el índice de variación<br />

más rápida al final (como lo requiere la función wxfrm fand(), que realiza la<br />

transformada wavelet). También se calcula el número total de elementos del<br />

objeto de entrada, así como de los datos padeados.<br />

3. Reserva de Memoria. Se reserva memoria <strong>para</strong> todos los objetos miembros<br />

de la clase, pero en este caso, solamente la primera vez que se ejecuta esta<br />

función. Por ello, se ha de comprobar antes de asignarlos memoria, si tienen<br />

un valor distinto de NULL. Estos objetos se eliminan en el destructor de la clase,<br />

comprobando también si se ha reservado memoria <strong>para</strong> ellos (puede que no se<br />

haya ejecutado nunca el filtro, con lo cual los objetos de la clase no tienen<br />

memoria reservada). Un ejemplo de esta reserva de memoria es el siguiente:<br />

195


if (! this->WaveCoefs)<br />

{<br />

this->WaveCoefs = vtkFloatScalars::New();<br />

this->WaveCoefs->Allocate(tamDensidadPad);<br />

this->WaveCoefs->SetNumberOfScalars(tamDensidadPad);<br />

}<br />

También se ha de reservar memoria <strong>para</strong> los objetos creados localmente en<br />

esta función, escalaresPadeados y outEscalares.<br />

4. Cuerpo de la función. Se realiza el algoritmo de compresión.<br />

Lo primero que se debe hacer es obtener los datos padeados (si es necesario) con<br />

tamaños en cada dimensión, que seab potencia de dos. Esto se hace recorriendo<br />

todas las dimensiones del array de datos y asignando el valor correspondiente<br />

al array de datos padeados (el del original, o cero, según el caso). Aunque<br />

tengamos datos tridimensionales, los arrays que tenemos son unidimensionales.<br />

Por ello, se deben anidar tres bucles que recorren las coordenadas implícitas<br />

en cada dimensión (i, j, k) y calculan el elemento que corresponde en cada uno<br />

de los dos arrays (padeado y sin padear). Esto se debe hacer sólo en el caso de<br />

que sea la primera vez que se ejecute el filtro.<br />

A continuación, sólo si es la primera vez que se ejecuta el fitro, se realiza la<br />

transformada wavelet directa de los datos padeados, se calcula la significancia<br />

de los coeficientes wavelet obtenidos y se ordenan los coeficientes de significancia.<br />

Tanto los coeficientes wavelet, como el array con los índices de los<br />

coeficientes de significancia ordenados son miembros de la clase, por lo que<br />

sólo hay que calcularlos una vez (si no cambia el objeto de entrada al filtro).<br />

El siguiente paso que se realiza es el bucle de ditorsión, que pone parte de los<br />

coeficientes wavelet a cero, y de esta forma comprimir los datos. Para ello, lo<br />

primero que se debe hacer es calcular el número de coeficientes que se cogen,<br />

según el valor de la variable miembro WaveCompresion introducido por el<br />

usuario. A continuación, los coeficientes mayores se respetan, no poniéndolos<br />

a cero. Para el resto, se ponen a cero en el caso de no haber seleccionado<br />

la obtención de detalle. Si FlagDetalle está activado, entonces, en vez de<br />

ponerlos a cero directamente, se multiplican por la matriz filtro, que contiene<br />

unos en los coeficientes wavelet correspondientes a la zona de detalle, y ceros en<br />

el resto. Se puede decir que éste es el compresor del algoritmo de compresión.<br />

El siguiente paso es obtener la transformada wavelet inversa de los coficientes<br />

wavelet distorsionados de la forma que se ha explicado en el párrafo anterior.<br />

Este paso es el descompresor del algoritmo de compresión.<br />

Lo que tenemos ahora son los datos reconstruidos, pero aún están padeados.<br />

Por ello, el siguiente paso consiste en realizar el despadeo. Esto se hace de una<br />

forma muy similar a como se hizo el padeo; pero en este caso, leyendo de los<br />

196


datos padeados y escribiendo en los despadeados. Además, los datos de salida<br />

se limitan a unsignet short (entre 0 y 65535), pues los datos de entrada del<br />

filtro también lo eran. Ahora ya tenemos los datos resultantes del filtro en el<br />

objeto de escalares de salida, outEscalares.<br />

El último paso es obtener el error cuadrático medio entre los escalares de<br />

entrada, escalares, y los de salida, outEscalares.<br />

5. Salida. Se asigna al objeto de salida del filtro, el objeto de escalares outEscalares.<br />

Para ello se copia a la salida la estructura de los datos de entrada<br />

(pero no los datos), y se asignan los datos, como se muestra a continuación:<br />

outPd->CopyScalarsOff();<br />

outPd->PassData(pd);<br />

outPd->SetScalars(outEscalares);<br />

También se han de borrar los objetos creados localmente, <strong>para</strong> los que se<br />

ha reservado memoria, mediante sus funciones miembro Delete(). Aunque<br />

se sigue usando en el objeto de salida, también se debe borrar el objeto<br />

outEscalares. La razón de esto es que en VTK, algunos clases de datos se<br />

derivan de la clase abstracta vtkReferenceCount. Los objetos con “cuenta de<br />

referencias”, llevan la cuenta del número de objetos que los hacen referencia.<br />

Este valor se incrementa en una unidad, cuando un nuevo objeto lo referencia,<br />

y se decrementa cuando un objeto lo borra (si llega la cuenta a cero, se elimina<br />

realmente).<br />

A.3.2.<br />

Función vtkWaveletFilter::CalcularDetalle(...)<br />

Esta función miembro se usa <strong>para</strong> calcular una matriz que transforma una zona<br />

prismática en el volumen original (el detalle seleccionado), a los coeficientes wavelet<br />

correspondientes. Por tanto, se encarga de hacer el mapeado del dominio espacial al<br />

dominio transformado.<br />

Las propiedades de localidad de la transformada wavelet, expresadas mediante<br />

las Ecs. (5.3), (5.5) y (5.6), nos inidican que los coeficientes correspondientes a una<br />

zona espacial de la transformada wavelet, tienen también una relación espacial en el<br />

dominio transformado. La razón de esto es porque la respuesta de los filtros usados<br />

<strong>para</strong> realizar la transformada wavelet tienen una respuesta exponencial decreciente,<br />

como ya se dijo entonces..<br />

Seleccionar los coeficientes wavelet correspondientes a una zona espacial en una<br />

dimensión, es bastante sencillo, pues a cada nivel de resolución, sólo tenemos un<br />

detalle (y una aproximación a baja resolución, que se descompone recursivamente);<br />

sin embargo, en 2D, tenemos 3 detalles y una aproximación a baja resolución, y en<br />

197


3D, tenemos 7 detalles y una aproximación a baja resolución, lo cual complica las<br />

cosas.<br />

En todos los artículos leídos, habla sobre las propiedades de localidad, e incluso<br />

las usa <strong>para</strong> obtener una zona con más detalle [9]; pero sin embargo, no muestra<br />

el algoritmo <strong>para</strong> realizar el mapeado. Sin embargo, si se empieza por realizar el<br />

algoritmo en 1D, luego se pasa a 2D, y finalmente en 3D, no es complicado conseguir<br />

un algoritmo bastante compacto.<br />

Además, hay que señalar que las funciones usadas <strong>para</strong> realizar la trasformada<br />

wavelet, llegan hasta la transformada wavelet de dos elementos, lo cual complica<br />

un poco este algoritmo, debido a que el número de niveles de aproximación en<br />

cada dimensión puede variar, si los datos no tienen el mismo tamaño en todas las<br />

dimensiones.<br />

Al algoritmo diseñado obtiene, recursivamente el borde de la zona seleccionada<br />

(que son los seis valores de entrada a la función), en cada uno de los siete detalles<br />

de cada nivel de resolución. La función calcula el array Filtro, que contiene unos<br />

en los coeficientes correspondientes a la zona deseada y ceros en el resto. Una vez<br />

obtenidos los 6 valores del borde en cada detalle, es fácil rellenar su interior con unos,<br />

mediante tres bucles anidados que recorren las tres dimensiones (i, j, k). El array<br />

Filtro es unidimensional, pero representa a una matriz tridimensional (siempre se<br />

hace así), por eso hay que calcular el índice del elemento que corresponde a un valor<br />

en coordenadas implícitas (i, j, k).<br />

La forma de obtener los 7 detalles de cada nivel de resolución, es mediante un<br />

array binario de 21 elementos, seleccion[21]. Este array contiene tres elementos<br />

(correspondientes a (i, j, k)) por cada uno de los 7 detalles. Cada elemento de este<br />

array vale uno o cero, dependiendo de si la coordenada y el detalle correspondientes<br />

a ese coeficiente es el más alejado del origen, o el más cercano. De esta forma,<br />

simplemente mediante un bucle for que recorre cada triada de elementos de este<br />

array, podemos obtener los coeficientes correspondientes a la zona seleccionada en<br />

el nivel de detalle correspondiente.<br />

Para seleccionar el nivel de resolución, se utiliza un bucle while, que se ejecuta<br />

mientras en alguna dimensión se siga haciendo la transformada wavelet (el número<br />

de elementos sea mayor o igual a 2). Como el volumen no tiene por qué tener el<br />

mismo tamaño en todas las dimensiones, hay que comprobar en cada paso del bucle,<br />

si en alguna dimensión, se ha llegado al mínimo, en cuyo caso, ya no se sigue iterando<br />

en esa dimensión, y tampoco se seleccionan los niveles de detalle correspondientes<br />

a la misma (la transformada wavelet, cuando se llega al mínimo en una de las tres<br />

dimensiones, ya no es 3D, sino 2D ó 1D, con lo cual ya no tenemos 7 detalles en cada<br />

nivel de resolución, sino sólo 3, en 2D, ó 1, en 1D). Cuando se llega al mínimo en<br />

alguna dimensión, se activa un flag, que indica que ya no debemos coger los detalles<br />

de esa dimensión. Esto se consigue mediante tres sentencias if de comprobación<br />

(una <strong>para</strong> cada dimensión), que evalúan el flag y el elemento correspondientes del<br />

198


array seleccion (si es cero, sí se debe coger aunque esté activado el flag; no se<br />

deben coger los detalles más alejados del origen (uno), ya que en esa dimensión ya<br />

no existen). Esto es lo más complicado de este algoritmo, y a la vez, lo más difícil de<br />

explicar. Sin embargo es la forma más sencilla que se ha encontrado <strong>para</strong> hacerlo.<br />

Por último, sólo queda obtener los seis coeficientes que representan el borde de<br />

la zona seleccionada, en cada uno de los detalles de cada nivel de resolución. Su<br />

obtención es bastante sencilla, teniendo en cuenta que en cada nivel de detalle, el<br />

tamaño de cada zona se divide por dos. Así, por ejemplo, <strong>para</strong> obtener la coordenada<br />

i superior de la zona selecionada, en cada uno de los datalles de cada nivel de<br />

aproximación, se usa la siguiente sentencia:<br />

donde<br />

i1=(int) (float) (I * seleccion[3 * numDetalle] + factores[0]);<br />

factores[0] = (float) this->Detalle[0]/this->Dims[0];<br />

numDetalle es el detalle actual de los 7 detalles de cada nivel de aproximación;<br />

seleccion[3 * numDetalle] es el primer elemento de la triada correspondiente al<br />

detalle actual, Detalle[0] y Dims[0], son las variables miembro que representan<br />

a la coordenada i superior, de la zona seleccionada y el tamaño en la dimensión i,<br />

respectivamente.<br />

Al final del bucle, se debe seleccionar también el elemento (0, 0, 0), que corresponde<br />

a la aproximación a baja resolución del nivel de mínimo.<br />

En la Fig. A.3 se muestra el resultado obtenido de trasnformar una zona en el<br />

espacio al dominio transformado en 2D. No se muestra en 3D, debido a su mayor<br />

dificultad de representación y compresión. Se puede observar que el tamaño de los<br />

datos es (32 × 16), por lo que se hace un nivel más de transformación en horizontal<br />

que en vertical.<br />

Por la forma de estar realizado el algoritmo, se puede extrapolar fácilmente a un<br />

mayor número de dimensiones. Ese ha sido también uno de los objetivos al realizarlo,<br />

ya que como he dicho, se empezó realizándolo <strong>para</strong> 1D, luego 2D, y finalmente 3D.<br />

Para entender mejor el algoritmo, lo mejor es ver el código que se muestra en el<br />

apéndice C, que se encuentra suficientemente comentado.<br />

A.3.3.<br />

Función vtkWaveletFilter::Significancia(...)<br />

Esta función se encarga de obtener la significancia correspondiente a cada coeficiente<br />

de la transformada wavelet, según el nivel de resolución a que se encuentra.<br />

La significancia se define como se muestra en la Ec. 5.31. Este algoritmo tampoco<br />

ha sido encontrado en ninguna referencia.<br />

199


0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

10<br />

11<br />

12<br />

13<br />

14<br />

15<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

10<br />

11<br />

12<br />

13<br />

14<br />

15<br />

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31<br />

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31<br />

Figura A.3: Mapeado de una zona de detalle en el espacio a los coeficientes de la<br />

transformada wavelet (2D).<br />

200


La función tiene como entrada al array con los coeficientes wavelet, después de<br />

realizar la distorsión <strong>para</strong> el nivel de comrpresión seleccionado. Se trasnforma este<br />

array, multiplicando cada coeficiente por el factor correspondiente. De esta forma<br />

se da más importancia a los niveles de resolución más bajos, frente a los más altos,<br />

que representan el detalle, y tienen menos importancia.<br />

Para realizar este algoritmo, obtengo tres array. La longitud de cada uno de ellos<br />

es el tamaño de los datos volumétricos (padeados, como todo lo que está en el<br />

dominio transformado), en la dimensión correspondiente. Para cada uno de ellos, se<br />

obtiene el factor por el que hay que multiplicar cada coeficiente <strong>para</strong> una transformada<br />

multirresolución de un vector de datos 1D de ese tamaño. Este factor es 1<br />

<strong>para</strong> el detalle de mayor resolución (la última mitad del vector), 8 <strong>para</strong> el siguiente,<br />

64, etc.<br />

Para pasar a 3D, simplemente se coge <strong>para</strong> un coeficiente (i, j, k) el menor de los<br />

tres factores. Multiplicando el valor obtenido por el coeficiente wavelet correspondiente,<br />

obtenemos su significancia.<br />

Al igual que en el caso de la función anterior, el algoritmo se complica por el<br />

hecho de que no tenemos el mismo número de niveles de resoluciones en todas las<br />

dimensiones. Por ello, no se debe seguir iterando en cada dimensión, cuando se llega<br />

la mínimo detalle. Sin embargo, aún queda el problema del valor del elemento 0<br />

de cada uno de los tres arrays (aproximación a baja resolución del nivel de detalle<br />

mínimo), que debe ser el del nivel inferior en cualquiera de las tres dimensiones, es<br />

decir, el mayor factor, y no el menor como en el resto de casos. Para solucionar esto,<br />

se mantienen un puntero, cuyo valor en cada momento es el del puntero al primer<br />

elemento del array que se ha modificado en último lugar. De esta forma, al finalizar<br />

el algoritmo, se asigna al elemento 0 de cada array, el valor del elemento 1 del array<br />

al que apunta el puntero (el de mayor longitud).<br />

Este algoritmo, al igual que el anterior, se puede fácilmente extrapolar a un<br />

número cualquiera de dimensiones.<br />

En la Fig. A.4, se puede ver el factor de significancia del ejemplo de la Fig. A.3.<br />

Se puede observar la forma de utilizar el puntero <strong>para</strong> asignar el elemento 0 de cada<br />

array.<br />

A.4.<br />

vtkFloodFillFilter<br />

Esta clase se encarga de rellenar el interior de una superficie, respetando las cavidades<br />

interiores. Recibe como entrada un objeto vtkStructuredPoints, que contiene<br />

unos en los puntos por los que pasa la isosuperficie y ceros en el resto. La salida<br />

es también un objeto del tipo vtkStructuresPoints, en el que el volumen interior a<br />

la superficie se ha rellenado. El objeto de entrada, se obtiene a partir de la isosuperficie,<br />

que es una malla triangular, mediante un filtro de la clase vtkVoxelModeller,<br />

201


0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

10<br />

11<br />

12<br />

13<br />

14<br />

15<br />

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31<br />

4096<br />

512<br />

64 512 64<br />

64<br />

8 1<br />

8 8<br />

1<br />

1<br />

512<br />

4096<br />

Horizontal<br />

512<br />

64<br />

8<br />

1<br />

Vertical<br />

64<br />

8<br />

1<br />

4096<br />

512<br />

Figura A.4: Factor de significancia de los coeficientes de la transformada wavelet<br />

(2D).<br />

202


que convierte esta malla a un objeto de la clase vtkStructuredPoints. Esto no se<br />

realiza en esta clase, sino en el script Tcl.<br />

Además se deben respetar las posibles cavidades interiores que tenga el volumen,<br />

no rellenándolas. En principio, esto es una tarea bastante complicada, pues un objeto<br />

natural obtenido, por ejemplo de una tomografía, puede tener estructuras muy<br />

complejas, llenas de cavidades.<br />

La derivación de esta clase, como su entrada y salida es del mismo tipo que<br />

<strong>para</strong> la calse vtkWaveletFilter, es la misma que se hizo <strong>para</strong> esa clase. No se va a<br />

describir detalles del código, más sencillo que el de la otra clase, pues la mayor parte<br />

de ellos ya han quedado descritos en la clase vtkWaveletFilter. Simplemente se<br />

explica el algoritmo.<br />

En [16] se describe un método de rellenar el interior de una superficie representada<br />

como voxels, llamado Flood Filling, o relleno por inmersión. Su nombre se debe<br />

a que el resultado obtenido es equivalente a meter el objeto en un recipiente con<br />

agua, con lo que todo el entorno del objeto se llena de agua, pero su interior y su<br />

superficie, no. A continuación, se obtiene el complementario, y ya tenemos el objeto<br />

rellenado. El problema de este método es que las cavidades internas del objeto no<br />

se rellenan de agua, con lo cual no se detectan.<br />

El algoritmo de flood filling “tradicional” 1 , se representa en el diagrama de flujo<br />

de la Fig. A.5.<br />

Es un algoritmo recursivo. Cada “rodaja” del objeto se rellena por se<strong>para</strong>do,<br />

<strong>para</strong> que la pila usadam, no se haga demasiado grande. Lo primero que tenemos que<br />

hacer es asegurarnos de que el objeto no toque el borde exterior, pues si ocurriera<br />

esto, el algoritmo no podría rellenar todo el exterior del objeto. Esto se consigue con<br />

padeo de ceros alrededor de la estructura.<br />

Se empieza en una esquina del plano a rellenar (por ejemplo la inferior izquierda<br />

(0,0)). Se intenta rellenar el punto situado al norte de ese punto, si es posible. Es<br />

posible rellenar un punto cuando aún no está lleno, está dentro del espacio considerado<br />

(no se sale de los bordes), y no es un punto frontera del objeto (los puntos<br />

frontera, por donde pasa la isosuperficie, valen 1). Al rellenar un punto, se le da un<br />

valor distinto de 0 (no pertenece a la frontera) y de 1 (pertenece a la frontera). Por<br />

ejemplo se le da el valor 3 (esto es totalmente convencional).<br />

Si un punto se rellena, es el nuevo punto de partida, empujando al antiguo punto<br />

a la parte superior de la pila (creada <strong>para</strong> almacenar los puntos). Si el punto al norte<br />

del punto de partida, está fuera del espacio, es fronterizo, o ya está relleno, entonces<br />

se mira al punto situado al oeste. Si ninguno de los dos se puede rellenar, se intenta<br />

con el este, y si no es posible, se mira al punto situado al sur, rellenándolo si es<br />

posible. Si ninguno de los puntos que lo rodean se puede rellenar, entonces el punto<br />

anterior en la pila se convierte en el nuevo punto de partida.<br />

1 Llamo tradicional al método explicado en [16], que no tiene en cuenta las cavidades internas.<br />

203


Cuando se vacía la pila y no hay dirección libre <strong>para</strong> rellenar, el plano está relleno.<br />

Una vez que el objeto está completamente rodeado de puntos rellenos, se obtiene el<br />

complementario, poniendo todos los 3’s (en el caso de elegir este valor como relleno)<br />

a 0 y el resto a 1. De esta forma, tenemos ya el volumen interior a la superficie<br />

relleno.<br />

Como se puede comprender fácilmente, este método no encuentra las cavidades<br />

internas. Estas cavidades aparecen, por ejemplo, <strong>para</strong> el caso de obtener la isosupercie<br />

correspondiente al hueso, en la que todo el interior de la cabeza es hueco.<br />

Si se obtiene la isosuperficie correspondiente a la piel, no aparecen cavidades. Para<br />

conseguir mi objetivo, se he diseñado un algoritmo, al que hemos llamado Flood<br />

Filling generalizado, que encuentra las cavidades internas mediante los siguientes<br />

pasos:<br />

1. Realizo el flood filling del exterior del objeto, según el método tradicional,<br />

rellenando el exterior de 3’s. Aún no obtengo el complementario.<br />

2. Los valores que se pueden encontrar en los puntos de la rodaja son: 3, si el<br />

punto es exterior al objeto; 1, si el punto está en la frontera del objeto; y 0,<br />

si el punto está dentro del objeto. Entre los puntos que valen 0, algunos son<br />

del interior del objeto, y otros de cavidades que no corresponden al objeto. Se<br />

deben distinguir estos dos casos.<br />

Lo que se hace es ir recorriendo cada línea de la rodaja, como se muestra en<br />

la Fig. A.6, en la que aparece el recorrido de dos líneas significativas de una<br />

rodaja con dos cavidades.<br />

Si en el recorrido de una línea, antes de la frontera de 1’s que precede a los<br />

0’s, hay 3’s, estamos dentro del objeto. Estos puntos interiores al objeto se<br />

rellenan con 2’s, mediantre el método flood fill tradicional (que no se sale de<br />

la cavidad). Si antes de la frontera que precede a los 0’s hay 2’s, estamos ante<br />

una cavidad, que relleno con 3’s (mediante el método flood fill tradicional),<br />

pues estamos fuera del objeto.<br />

Como se puede ver en la Fig. A.6, el estado se sigue con un flag, que cambia<br />

cuando el valor leído en la línea es distinto del anterior. En el apéndice C, se<br />

muestra el código, en el que aparece comentado el flujo de estados.<br />

3. Finalmente, sólo queda poner a 0 todos los valores de la rodaja que valgan 3, y<br />

a 1 los que valgan menos de 3, obteniendo de esta forma el resultado deseado.<br />

Este método funciona en general, sólo fallando en un caso patológico. Este caso<br />

ocurre cuando al leer una línea nos encontramos con dos cavidades que no han sido<br />

rellenadas anteriormente, se<strong>para</strong>das por una frontera continua, como se puede ver<br />

en la Fig. A.7. En ella se muestra un caso en el que falla, y otro en el que no, por<br />

no estar las fronteras unidas en la línea que rellena las cavidades. Si alguna de las<br />

204


Empieza en<br />

una esquina<br />

Miro al norte<br />

Lleno<br />

Miro al oeste<br />

Lleno<br />

Miro al este<br />

Meto el antiguo punto<br />

a la pila (push).<br />

Relleno el nuevo punto<br />

y lo uso como nuevo<br />

punto de partida<br />

Lleno<br />

Miro al sur<br />

Lleno<br />

Vacia<br />

Pila<br />

No vacia<br />

Fin<br />

Ir al punto superior<br />

de la pila (top).<br />

Figura A.5: Algoritmo del método Flood Filling “tradicional”.<br />

205


3 flag=0<br />

flag=1<br />

relleno con 2<br />

333333333333333111000000000000000000001333333333333<br />

0<br />

0<br />

0<br />

Orden de lectura<br />

flag=2<br />

flag=3 flag=0<br />

333333333333333111222222222222222222221333333333333<br />

3<br />

flag=0<br />

flag=2 flag=3<br />

2<br />

333333333333312222210011222210001122222211333333333<br />

0 0<br />

relleno con 3<br />

flag=0<br />

flag=1 flag=2 flag=3<br />

333333333333312222213311222210001122222211333333333<br />

relleno con 3<br />

flag=0 flag=1<br />

flag=2<br />

flag=3<br />

flag=0<br />

3<br />

333333333333312222213311222213331122222211333333333<br />

2<br />

3 3<br />

0<br />

1<br />

0 0<br />

Figura A.6: Representación del algoritmo de Flood Filling generalizado, <strong>para</strong> encontrar<br />

cavidades.<br />

206


(a) No funciona el Flood Fill mejorado<br />

(b) Si funciona el Flood Fill mejorado<br />

Figura A.7: (a) Caso patológico en el que no funciona el algoritmo Flood Filling<br />

generalizado. (b) Caso muy similar en el que sí funciona.<br />

cavidades empieza en una línea rellenada con anterioridad, no hay problema. Para<br />

disminuir la ocurrencia de este caso patológico, ejecutamos la parte del algoritmo<br />

que lee cada línea <strong>para</strong> todas las líneas de la rodaja. Esto ralentiza su ejecución<br />

(podríamos hacerlo cada varias líneas si no hay cavidades muy pequeñas), pero<br />

mejora considerablemente su funcionamiento. Pese a ello, hay casos en los que sigue<br />

fallando. En el apartado A.5, explicaré cómo se puede evitar la incidentcia de estos<br />

“fallos” 2 .<br />

Se puede comprobar que el algoritmo funciona incluso cuando tenemos varias<br />

cavidades anidadas unas dentro de otras, o pequeños objetos dentro de las cavidades.<br />

Sin embargo no funciona, evidentemente, cuando la isosuperficie en una rodaja no<br />

es continua (está partida), algo que ocurre cuando se realiza diezmado a tasas muy<br />

grandes de compresión. En ese caso es imposible evaluar el error en volumen.<br />

2 Por la experiencia que he tenido, <strong>para</strong> el caso del cráneo estudiado, este método falla en menos<br />

del 2-3 % de los casos. Para el caso de la piel, no falla, pues no hay cavidades<br />

207


A.5.<br />

ErrorVol.cxx<br />

Para calcular el error en volumen, como es una operación que requiere tratar<br />

enormes cantidades de datos, (ficheros de nu tamaño en torno a 13 MBytes <strong>para</strong><br />

una malla rectangular de dimensiones 256 × 256 × 93). Por ello, se ha optado, como<br />

ya se ha dicho en alguna ocasión, por crear un programa en C++ <strong>para</strong> VTK, en vez<br />

de hacerlo mediante Tcl (sería demasiado lento), o en una clase (es más apropiado<br />

crear un ejecutable, pues no hay ninguna clase apropiada en VTK <strong>para</strong> derivar este<br />

tipo de objeto).<br />

Este ejecutable recibe como entrada, los nombres de dos archivos, que contienen<br />

la malla vtkStructuredPoints que ha resultado de aplicar el filtro vtkFloodFill-<br />

Filter al objeto sin comprimir y comprimido.<br />

El código es bastante claro y está suficientemente comentado, por lo que después<br />

de explicar todo lo anterior, no voy a comentarlo aquí. Simplemente voy a explicar<br />

la forma en que se evitan los casos en los que falla el algoritmo (como se dijo, en el<br />

peor caso, en un 2-3 % de los casos).<br />

Después de comprobar que los dos ficheros de entrada son coherentes (tienen las<br />

mismas dimensiones y formato correcto), lo primero que se hace es calcular el error<br />

relativo que hay en cada rodaja de la malla rectangular.<br />

La clave es realizar un filtrado de mediana sobre estos errores relativos; <strong>para</strong><br />

ello se obtiene la mediana de los mismos, eliminando aquellos que sean mayores que<br />

una cierta constante, multiplicada por la mediana. Por último, se calcula el error<br />

absoluto medio por rodaja a partir de los errores absolutos de todas las rodajas<br />

que no se han eliminado.<br />

Para calcular el error, no hace falta considerar todas las rodajas; por ello no pasa<br />

nada si se elimina un pequeño porcentaje de las mismas. Además, se ha visto que<br />

los errores relativos de todas las rodajas tienen una distribución aproximadamente<br />

guassiana con una varianza pequeña, salvo unos pocos, que se encuentran bastante<br />

desviados de la mediana. A partir de estos experimentos, se ha comprobado que<br />

el filtro elimina casi todos los errores si se elige un valor <strong>para</strong> la constante que<br />

multiplica a la mediana de 1.5.<br />

Para hacer estos experimentos, se ha observado gráficamente el funcionamiento<br />

del filtro vtkFloodFillFilter <strong>para</strong> cada una de las rodajas de la cabeza. Aplicando<br />

este valor de la constante, el filtro de mediana elimina prácticamente todos los errores<br />

importantes (hay algún pequeño error que no elimina, pero como el valor obtenido<br />

está muy cerca de la mediana, no tiene importancia). Este filtrado también elimina<br />

los errores que se producen si en alguna rodaja la frontera del objeto aparece cortada,<br />

rellenándose su interior.<br />

208


A.6.<br />

ErrorRender.cxx<br />

De la misma forma que en el caso anterior, <strong>para</strong> calcular el error en la imagen<br />

renderizada, se ha creado un ejecutable, en el que se calcula el error cuadrático<br />

medio entre dos archivos, que contienen las imágenes. El programa recibe como<br />

argumento el nombre de estos dos archivos. Además, opcionalmente, puede tener un<br />

tercer argumento, con el nombre del fichero en el que se quiere guardar la imagen<br />

diferencia entre las dos.<br />

Las imágenes gráficas deben estar en formato PPM (Pixmap), que es el formato<br />

en que VTK guarda las renderizaciones. Los ficheros se leen como imagen, mediante<br />

objetos de la clase vtkPNMReader y posteriormente se convierten a vtkStructuredPoints.<br />

Esto no es necesario, pero lo he hecho porque estoy más acostumbrado, en VTK, a<br />

tratar con estructuras geométricas que con imágenes.<br />

El error que se calcula es el error cuadrático medio entre la intensidad de<br />

las dos imágenes.<br />

209


210


Apéndice B<br />

VTK y TCL<br />

En este apéndice se describe, brevemente, algunos detalles sobre la implementación<br />

del sistema VTK, enumerando las principales clases de VTK. No se han mostrado<br />

las clases que realizan el procesado de los datos (objetos de proceso). La razón de<br />

esto es que hay muchísimas y son muy variadas, por lo que es imposible describirlas<br />

todas, y absurdo elegir unas cuantas. También se describe la interacción entre VTK<br />

y el lenguaje interpretado Tcl/Tk, y se muestra algún ejemplo <strong>para</strong> ver algunas de<br />

las características de Tcl/Tk. Abarcar todo lo que describe este apéndice es imposible<br />

aquí, por ello, <strong>para</strong> obtener más información, se recomienda consultar [12] y<br />

[15].<br />

B.1.<br />

VTK<br />

VTK Visualization Toolkit 2.0[12] es un avanzado sistema de creación y<br />

visualización de datos volumétricos, así como de tratamiento de imagen. Permite<br />

tratar y visualizar todo tipo de datos volumétricos; desde datos meteorológiocs,<br />

pasando por resonancias magnéticas, tomografías computerizadas y ultrasonidos,<br />

hasta datos financieros y matemáticos.<br />

Una de las principales ventajas del sistema es la posibilidad de aumentar la funcionalidad<br />

del mismo y crear nuevos algoritmos. Para ello se aprovecha la principal<br />

característica de VTK, su diseño orientado a objeto. Se puede crear, por ejemplo,<br />

una nueva clase que implemente un filtro, derivándola de alguna de las clases abstractas<br />

que ofrece el sistema. La forma de hacer esto se ha explicado con ejemplos<br />

concretos de clases creadas <strong>para</strong> la aplicación realizada (apéndice A).<br />

VTK está programado en C++ y posee un gran número de clases (más de 500).<br />

Se aprovecha el diseño orientado a objetos <strong>para</strong> realizar tareas complejas, de una<br />

forma sencilla y muy modular.<br />

A continuación, se describe de forma básica el sistema; <strong>para</strong> mayor información<br />

211


sobre el mismo, se puede consultar [12] o la información que se encuentra en cada<br />

clase de las librerías que forman el sistma VTK.<br />

B.1.1.<br />

Renderizacion<br />

El Modelo Gráfico<br />

En este apartado se describen, brevemente, los objetos gráficos implementados<br />

en VTK y la forma de usarlos. En el capítulo 3 se explicaron todos los elementos<br />

que intervienen en la renderización de una escena. Ahora se enumeran los objetos<br />

usados en VTK <strong>para</strong> implementar estos elementos.<br />

En VTK hay siete objetos básicos usados <strong>para</strong> renderizar una escena. Hay muchos<br />

más objetos, pero estos siete son los más frecuentes.<br />

1. vtkRenderWindow: sirve <strong>para</strong> gestionar una ventana en la pantalla; en una<br />

instancia de vtkRenderWindow pueden dibujar varios renderizadores.<br />

2. vtkRenderer: coordina el proceso de renderización, en el que hay luces, cámaras<br />

y actores.<br />

3. vtkLight: fuente de luz <strong>para</strong> iluminar la escena.<br />

4. vtkCamera: define el punto de vista, el punto focal y otras propiedades de la<br />

visualización de la escena.<br />

5. vtkActor: representa un objeto renderizado en la escena. Sus propiedades y<br />

posición están dados en coordenadas del mundo real.<br />

6. vtkProperty: define las propiedades de apariencia de un actor, incluyendo su<br />

color, transparencia, y propiedades de iluminación, como la iluminación difusa<br />

y especular. También tiene propiedades <strong>para</strong> el modo de representación, como<br />

superificie sólida o malla.<br />

7. vtkMapper: es la representación geométrica de un actor. Más de un actor se<br />

pueden referir a el mismo mapeador.<br />

La clase vtkRenderWindow junta todo el proceso de renderización. Es la responsable<br />

de gestionar una ventana en la pantalla. Para PCs con Windows ’95 o NT,<br />

será una ventana de Microsoft, y <strong>para</strong> los sistemas UNIX será una ventana X. En<br />

VTK, las instancias de la clase vtkRenderWindow son independientes del hardware.<br />

Esto significa que al programar, no hay que tener en cuenta el hardware gráfico o<br />

el software que se está usando. El software de VTK automáticamente se adapta al<br />

ordenador en el que se ejecuta, cuando se crean instancias de esta clase, como se<br />

describirá después.<br />

212


Además de la gestión de la ventana, los objetos de la clase vtkRenderWindow se<br />

usan <strong>para</strong> gestionar los renderizadores y almacenar las características específicas de<br />

los gráficos, como el tamaño, posición, título de la ventana, profundidad de la misma,<br />

etc. La profundidad de la ventana indica cuantos bits se usan <strong>para</strong> representar cada<br />

pixel.<br />

La clase vtkRender es la responsable de coordinar las luces, cámaras y actores<br />

<strong>para</strong> producir una imagen. Cada instancia de la clase mantiene una lista de actores,<br />

luces, y una cámara activa en una escena. Al menos se debe definir un actor, pero si<br />

no se definen cámara y luces, el renderizador las crea automáticamente. Además, las<br />

instancias de esta clase, proporcionan también métodos <strong>para</strong> especificar los colores de<br />

iluminación de fondo y ambiente. También hay funciones en esta clase <strong>para</strong> convertir<br />

entre los sistemas de coordenadas del mundo, de vista y de la pantalla.<br />

Un aspecto importante de un renderizador es que debe estar asociado con una<br />

instancia de la clase vtkRenderWindow en la que dibujar, y el área de la ventana<br />

de renderización en la que dibuja, se debe definir mediante un puerto de vista<br />

rectangular. El puerto de vista se define en coordenadas normalizadas (0, 1), en<br />

los ejes de coordenadas de la imagen x e y. Por defecto, si no se especifica nada, el<br />

renderizador dibuja en toda la extensión de la ventana de renderización (coordenadas<br />

del puerto de vista (0, 0, 1, 1)). Se puede especificar un puerto de vista más pequeño,<br />

y tener más de un renderizador dibujando en la misma ventana de renderización.<br />

Las instancias de la clase vtkLight, iluminan la escena. Se han definido múltiples<br />

variables en esta clase, <strong>para</strong> orientar y posicionar la luz. También es posible encender<br />

y apagar la luz y asignarle un color. Normalmente, al menos una luz está encendida,<br />

<strong>para</strong> iluminar la escena. Si no hay luces definidas y encendidas, el renderizador crea<br />

una automáticamente. Las luces en VTK pueden ser, tanto posicionales, como<br />

infinitas. Las luces posicionales tienen asociado un ángulo sólido y unos factores de<br />

iluminación. Las luces infinitas proyectan la luz con rayos <strong>para</strong>lelos entre sí.<br />

Las cámaras se construyen mediante la clase vtkCamera. Sus parámetros más<br />

importantes son: la posición de la cámara, el punto focal, la localización de los<br />

planos de corte delantero y trasero, el vector de vista superior, y el campo de vista.<br />

Las cámaras tienen funciones especiales <strong>para</strong> facilitar su manipulación. Entre ellas<br />

se incluyen la de elevación, azimuth, zoom, y giro. De forma similar a vtkLight, si<br />

no hay definida ninguna instancia de la clase vtkCamera, el renderizador crea una<br />

automáticamente.<br />

Las instancias de la clase vtkActor representan objetos en la escena. En particular,<br />

vtkActor combina propiedades de los objetos (color, propiedades de sombreado,<br />

etc.), definición geométrica del objeto, y orientación en el sistema de coordenadas del<br />

mundo. Esto se implementa manteniendo variables en la instancia, que referencian<br />

a instancias de las clases vtkProperty, vtkMapper, y vtkTransform. Normalmente<br />

no hay que crear estas propiedades y transformaciones de forma explícita, ya que se<br />

crean automáticamente, y se modifican mediante las funciones de la clase vtkActor.<br />

213


Sin embargo, sí es necesario crear una instancia de la clase vtkMapper (o una de sus<br />

subclases). El mapeador une el pipeline (flujo de datos) de visualización al dispositivo<br />

gráfico.<br />

Hay otras clases de actores con un comportamiento específico, implementados<br />

como subclases de vtkActor. Por ejemplo vtkFollower, cuyas instancias siempre<br />

siguen a la cámara activa. Esto es útil cuando se diseñan textos que deben ser legibles<br />

desde cualquier posición de la cámara en la escena.<br />

Otro ejemplo, usado <strong>para</strong> la realización de la aplicación, es vtkLODActor. Este<br />

actor soporta varios niveles de detalle. Esto quiere decir que cuando se muestra en<br />

pantalla, en lugar de la imagen renderizada, muestra una nube de puntos. Si se<br />

deja la imagen fija durante unos instantes, el renderizador muestra el actor. De esta<br />

forma, se consigue mejor interactividad con la escena, pues el movimiento del punto<br />

de vista se puede hacer de forma más rápida y suave.<br />

Las instancias de la clase vtkProperty, afectan al aspecto de un actor renderizado.<br />

Cuando se crean los actores, se crea automáticamente una instancia <strong>para</strong> sus<br />

propiedades. También es posible crear objetos de propiedades y después asociarlos<br />

con uno o más actores. De esta forma, los actores pueden compartir propiedades<br />

comunes.<br />

Finalmente, vtkMapper, (y sus subclases) definen la geometría del objeto, y<br />

opcionalmente, los colores de los vértices. vtkMapper se relaciona con una tabla de<br />

colores (vtkLookupTable), <strong>para</strong> establecer los colores que se usan <strong>para</strong> los vértices<br />

de la geometría.<br />

Además, hay otro objeto importante, vtkRenderWindowInteractor, que captura<br />

eventos <strong>para</strong> un renderizador en la ventana de renderización. Esta clase captura estos<br />

eventos y a continuación realiza operaciones como rotación, dolly y acercamiento o<br />

alejamiento de la cámara, etc. Las instancas de esta clase se asocian con la ventana<br />

de renderización mediante el método SetRenderWindow().<br />

Consiguiendo Independencia del Dispositivo<br />

Una propiedad deseable de las aplicaciones creadas con VTK es la independencia<br />

del dispositivo. Esto significa que el código que funciona en un sistema operativo, con<br />

una configuración dada de software/hardware, funciona sin cambios en un sistema<br />

operativo diferente, con una configuración de software/hardware distinta. La ventaja<br />

de esto, es que el programador no tiene que gastar tiempo portando una aplicación<br />

entre diferentes sistemas. Además, las aplicaciones existentes no necesitan ser reescritas<br />

<strong>para</strong> aprovechar las características de las nuevas tecnologías de hardware<br />

o software. En su lugar, VTK maneja esto de forma tansparente, mediante una<br />

combinación de herencia y una técnica llamada fábricas de objetos.<br />

En la Fig. B.1 se muestra el uso de la herencia, <strong>para</strong> conseguir independencia del<br />

dispositivo. Algunas clases, como vtkActor se dividen en dos partes: una superclase<br />

214


vtkActor<br />

Superclase<br />

independiente del dispositivo<br />

vtkOpenGLActor<br />

vtkXGLActor<br />

vtkStarbaseActor<br />

Subclases<br />

dependientes<br />

del dispositivo<br />

Figura B.1: Consiguiendo independencia del dispositivo mediante herencia.<br />

independiente del dispositivo y subclases que dependen del mismo. El truco consiste<br />

en que el usuario crea una subclase, invocando el constructor especial New(), que se<br />

usa <strong>para</strong> todas las clases de VTK, en lugar del constructor de C++ (new()). Se usa<br />

el constructor New() de la superclase independiente del dispositivo. Por ejemplo, se<br />

puede usar (en C++) la línea<br />

vtkActor *unActor = vtkActor::New()<br />

<strong>para</strong> crear una instancia dependiente del dispositivo de la clase vtkActor. El usuario<br />

no ve código dependiente del dispositivo, pero en realidad unActor es un puntero<br />

a una de las subclases de vtkActor. A continuación se muestra un fragmento del<br />

código del constructor New() (a esto es a lo que se llama fábrica de objetos.)<br />

vtkActor *vtkActor::New()<br />

{<br />

char *temp = vtkRenderWindow::GetRenderLibrary();<br />

...<br />

if (!strcmp("OpenGL",temp)) return vtkOpenGLActor::New();<br />

...<br />

}<br />

En este ejemplo, vtkOpenGLActor::New() es un constructor sencillo, que devuelve<br />

una instancia de su clase, usando ({return new vtkOpenGLActor;}). (Es<br />

posible, incluso, que en esta función se elija una implementación específica de la<br />

librería gráfica OpenGL.)<br />

El uso de fábricas de objetos, mediante la función New(), nos permiten crear<br />

código independiente del dispositivo, que se puede portar fácilmente de un ordenador<br />

a otro, y adaptar al cambio de tecnología. Por ejemplo, si aparece una nueva<br />

librería gráfica, habría que crear simplemente una subclase dependiente del dispositivo,<br />

y modificar la función New() de la superclase correspondiente. De esta forma se<br />

pueden crear instancias de la subclase correspondiente, a partir de las variables de<br />

entorno u otra información del sistema. Esta extensión se realiza sólo en un sitio del<br />

código, y todas las aplicaciones basadas en estas fábricas de objetos serían portadas<br />

automáticamente, sin cambios.<br />

215


Aquí se ha podido ver una de las ventajas de la programación orientada a objetos<br />

y de la modularidad (una de sus principales características).<br />

B.1.2.<br />

Detalles de la implementación de VTK<br />

En esta sección voy se describen algunos detalles de la implementación de VTK.<br />

Implementación Mediante Lenguaje C++<br />

Visualization Toolkit se ha implementado en el lenguaje de programación,<br />

orientado a procedimientos, C++. Se facilita la creación de aplicaciones <strong>para</strong> visualización<br />

mediante librerías de clases, que contienen datos y objetos <strong>para</strong> realizar<br />

procesos sobre los datos. Se soportan objetos abstractos <strong>para</strong> derivar nuevos objetos.<br />

Se ha diseñado el pipeline de visualización <strong>para</strong> que pueda ser conectado<br />

directamente al sistema de gráficos explicado en el apartado B.1.1.<br />

Se podría haber implementado un interfaz visual (similar al de Khoros), mediante<br />

las librerías de clases realizadas. Sin embargo, <strong>para</strong> aplicaciones del mundo<br />

real un lenguaje orientado a procedimientos tiene algunas ventajas. Entre ellas se<br />

encuentran la implementación directa y sencilla de expresiones condicionales y ejecución<br />

de bucles; además de permitir interfaces sencillos a otros sistemas, como los<br />

GUIs.<br />

Pipeline de Visualización<br />

El pipeline de visualización o red de visualización, representa los pasos <strong>para</strong><br />

crear la visualización. Consta de objetos <strong>para</strong> representar los datos (objetos de<br />

datos), objetos <strong>para</strong> operar sobre los datos (objetos de proceso) y las indicaciones<br />

del flujo de datos (flechas entre los objetos). Las redes de visualización se<br />

usan, en general, <strong>para</strong> describir la implementación de una determinada técnica de<br />

visualzación.<br />

Objetos de Datos: Los objetos de datos representan información. También proporcionan<br />

funciones <strong>para</strong> cerar, acceder y eliminar esta información. No se<br />

permite la modificación directa de los datos, a no ser que se usen las funciones<br />

de la clase. También tienen funciones <strong>para</strong> obtener características de los datos<br />

(por ejemplo, el número de datos, valor mínimo y máximo permitidos, . . . ).<br />

Objetos de Proceso: Operan sobre datos de entrada, <strong>para</strong> obtener datos de salida.<br />

Un objeto de proceso deriva nuevos datos de su entrada, o bien transforma<br />

los datos de entrada a una nueva forma. Por ejemplo, un objeto de proceso<br />

podría obtener el gradiente de presión a partir de un campo de presión, o<br />

transformar el campo de presión a isolíneas de presión. La entrada a un objeto<br />

216


de proceso incluye uno o más objetos y parámetros locales <strong>para</strong> controlar su<br />

funcionamiento.<br />

Los objetos de proceso se pueden, a su vez dividir, según si inician, mantienen,<br />

o terminan el flujo de datos de visualización.<br />

Objetos Fuente: sirven de interfaz a fuentes externas de datos, o bien<br />

los generan a partir de parámetros locales. Los objetos fuente, que sirven<br />

de interfaz a datos externos, se llaman objetos lectores, ya que se ha<br />

de leer un fichero externo, y convertirlo a una forma de representación<br />

interna.<br />

Objetos Filtro: necesitan una o más entradas de objetos de datos y<br />

generan una o más salidas de objetos de datos. El funcionamiento del<br />

objeto se controla mediante parámetros locales.<br />

Objetos Mapeadores: corresponden a los “sumideros” de la red de<br />

visualización. Los objetos mapeadores requieren una o más entradas de<br />

datos y terminan el flujo de datos. Normalmente los mapeadores se usan<br />

<strong>para</strong> convertir los datos en primitivas gráficas, pero también pueden, por<br />

ejemplo, escribir los datos a un fichero. Los mapeadores que escriben los<br />

datos a un fichero se llaman objetos escritores.<br />

Control Implícito de la Ejecución<br />

Se ha implementado un control implícito de la ejecución de las redes de visualización.<br />

La ejecución de la red ocurre cuando se solicita la salida de un objeto<br />

(ejecución bajo demanda). Esto es fácil de implementar, casi transparente al usuario<br />

del sistema, y muy adaptado a la ejecución condicional y de bucles. En ordenadores<br />

con procesadores en <strong>para</strong>lelo, u otro hardware especial, se puede usar el control implícito,<br />

junto con un esquema explícito de balance de carga, dividiendo la red de<br />

visualización en subredes más pequeñas.<br />

Esta implementación se basa en dos funciones clave: Update() y Execute().<br />

Update() normalmente se ejecuta cuando el usuario solicita al sistema que renderice<br />

una escena. Como parte de este proceso, los actores envían un método Render()<br />

a sus mapeadores. En este momento empieza la ejecución de la red. El mapeador<br />

invoca la función Update() de su(s) entrada(s). Éstas invocan, de forma recursiva,<br />

las funciones Update() de su(s) entrada(s). Este proceso continúa hasta que se<br />

encuentra un objeto fuente. En este punto, el objeto fuente com<strong>para</strong> su tiempo<br />

de modificación con el de la última vez que se ejecuto. Si se ha modificado más<br />

recientemente que su última ejecución, se re-ejecuta mediante la función Execute().<br />

La recursión se rebobina con cada filtro, com<strong>para</strong>ndo su tiempo de entrada con su<br />

tiempo de ejecución. De esta forma, Execute() se invoca cuando sea necesario. El<br />

proceso termina cuando se devuelve el control al mapeador.<br />

217


Este proceso es extremadamente simple, pero depende del mantenimiento correcto<br />

del tiempo de modificación y ejecución de cada objeto de la red. Si se crea<br />

una fuente o un filtro y no se gestiona bien el tiempo de modificación y ejecución,<br />

habrá casos en los que el pipeline no se ejecute correctamente.<br />

Entrada y Salida de los Objetos<br />

Aunque la arquitectura de VTK soporta objetos con múltiples entradas y salidas,<br />

en la práctica, la mayoría de los filtros y las fuentes generan una única salida y los<br />

filtros aceptan una única entrada. Esto es debido a que, en general, los algoritmos<br />

son por naturaleza, de entrada y salida únicas.<br />

En la red de visualización, hemos visto que las fuentes, filtros y sumideros, se<br />

conectan entre sí. La entrada de cada objeto en el pipeline es la salida del anterior, y<br />

así sucesivamente. Los datos de entrada se represntan con la variable de la instancia<br />

Input y se asigna mediante el método de la instancia SetInput(). Los datos de<br />

salida se representan mediante la variable de la instancia Output y se accede a ellos<br />

mediante el método de la instancia GetOutput(). Para conectar dos filtros juntos,<br />

normalmente se usa la sentencia C++<br />

filtro2->SetInput(filtro1->GetOutput());<br />

donde filtro1 y filtro2 son objetos filtro de tipo compatible, es decir, la salida<br />

de filtro1 es compatible con la entrada que debe tener filtro2. (El compilador<br />

de C++ se encarga de asegurar esta compatibilidad).<br />

La clave de esta arquitectura es que los objetos de datos conocen qué filtros los<br />

poseen. Esto quiere decir que si un filtro crea un objeto de datos de salida, el objeto<br />

de datos sabe qué filtro lo creó. Esto nos permite delegar ciertos mensajes desde<br />

un filtro, a través del objeto de datos al filtro al que está conectado. Por ejemplo,<br />

si filtro2 recibe un mensaje Update(), lo envía a su objeto de datos de entrada,<br />

que a su vez lo envía a su filtro dueño (si es alguno). En este caso filtro1 es el<br />

filtro dueño del objeto de datos. Este proceso, como dijimos, continúa hasta que se<br />

alcanza un objeto fuente, en el que termina la propagación del método Update().<br />

B.1.3.<br />

Representación de los Datos<br />

En este apartado se decriben los objetos usados en VTK <strong>para</strong> representar los<br />

datos.<br />

Representación de las Celdas<br />

En VTK se implementa cada tipo de celda mediante clases específicas. Todas las<br />

clases que sirven <strong>para</strong> representar celdas se derivan de la clase abstracta vtkCell. La<br />

218


topología de la celda se especifica mediante una lista ordenada de índices de puntos.<br />

Su geometría se especifica mediante una lista con las coordenadas de los puntos.<br />

Los tipos de celda son los siguientes:<br />

vtkVertex. Es una celda primaria de cero dimensiones. Se define con un solo punto.<br />

vtkPolyVertex. Es una celda compuesta de cero dimensiones. Está definida por<br />

una lista de puntos ordenados arbitrariamente.<br />

vtkLine. Es una celda primaria 1D. Está definida por dos puntos. La dirección de<br />

la línea es del primer punto al segundo.<br />

vtkPolyLine. Es una celda compuesta unidimensional, que consta de una o más<br />

líneas conectadas. Está definida por una lista ordenada de n+1 puntos, donde<br />

n es el número de líneas de la poli-línea. Cada par de puntos (i, i + 1) definen<br />

una línea.<br />

vtkTriangle. Es una celda primaria bidimensional. Está definida por tres puntos<br />

ordenados en el sentido de las agujas del reloj. El orden de los puntos especifica<br />

la normal a la superficie, según la regla de la mano derecha.<br />

vtkTriangleStrip. Es una celda compuesta, que consta de uno o más triángulos.<br />

No es encesario que sus puntos sean coplanares. Está definida por una lista de<br />

n + 2 puntos, donde n es el número de triángulos. El orden de los puntos es<br />

tal que cada tres puntos (i, i + 1, i + 2) con 0 ≤ i ≤ n, define un triángulo.<br />

vtkQuad. El cuadrilátero es una celda primaria bidimensional. Está definido por<br />

una lista de cuatro puntos ordenados en un plano. Debe ser convexo y sus lados<br />

no se deben cortar. La normal a la superficie se define como en el triángulo.<br />

vtkPixel. Es una celda primaria bidimensional. Topológicamente es igual al cuadrilátero,<br />

pero con restricciones geométricas. Todos los lados son perpendiculares entre<br />

sí, y los lados y la normal a la superficie siguen las direcciones de los ejes coordenados<br />

x − y − z. El orden de los puntos es diferente del cuadrilátero. Los<br />

puntos se ordenan en las direcciones crecientes de los ejes, empezando por x,<br />

luego y, y por último z. Este tipo de celda mejora el funcionamiento, respecto<br />

al cuadrilátero (más general). La definición de pixel dada aquí es diferente de<br />

la que se le suele dar (cada elemento de una imagen de valor constante).<br />

vtkPolygon. Es una celda bidimensional primaria. Está definido por una lista de<br />

tres o más puntos en un plano. Su vector normal está definido según el orden<br />

en el sentido de las agujas del reloj de los puntos que lo forman. Puede ser<br />

no convexo, pero sus lados no se pueden cortar. Tiene n lados, donde n es el<br />

número de puntos.<br />

219


vtkTetra. Es una celda primaria tridimensional. Está definida por cuatro puntos<br />

no coplanares. Tiene 6 aristas y cuatro caras triangulares.<br />

vtkHexaedron. Es una celda primaria tridimensional, que consta de seis cuadriláteros<br />

en sus caras, doce aristas y ocho vértices. Está formado por una lista ordenada<br />

de ocho puntos. Sus caras y aristas no se deben cortar, y debe ser convexo.<br />

vtkVoxel. Es una celda primaria tridimensional. Topológicamente es equivalente<br />

al hexaedro, pero con restricciones geométricas adicionales. Todas sus caras<br />

deben ser perpendiculares a uno de los ejes coordenados x − y − z. La lista de<br />

puntos se ordena en la dirección creciente de los ejes. Es un caso particular de<br />

hexaedro, usado <strong>para</strong> mejorar el funcionamiento.<br />

Al igual que pixel, la definición de voxel que hemos hecho difiere de la usual<br />

(elemento de volumen de valor constante).<br />

Representación de Estructuras de Datos<br />

En VTK se han implementado cinco estrucuturas de datos (datasets): vtkPoly-<br />

Data, vtkStructuresPoints, vtkStructuredGrid, vtkRectilinearGrid, y vtk-<br />

UnstructuredGrid. Todos ellos se derivan de la superclase vtkDataSet.<br />

Se usa una representación interna distinta <strong>para</strong> cada uno de los datasets. De<br />

esta forma se minimiza la memoria requerida <strong>para</strong> almacenar las estructuras de datos<br />

y se implementan métodos de acceso a los mismos más eficientes. Los objetos más<br />

generales se podrían usar <strong>para</strong> representar al resto, pero la sobrecarga computacional<br />

y de memoria es inaceptable <strong>para</strong> conjuntos grandes de datos. A continuación se<br />

describen someramente estos objetos:<br />

vtkStructuredPoints. Es la forma de representación más simple y compacta. Es<br />

una colección de puntos y celdas colocados en una malla rectangular regular.<br />

Consta de elemenstos de línea (1D), pixels (2), o voxels (3D).<br />

Tanto los puntos, como las celdas de este dataset se representan de forma implícita,<br />

especificando las dimensiones, el espaciado entre los datos, y el origen.<br />

Las dimensiones definen la topología de los datos , mientras que el origen y<br />

espaciado definen la geometría. Las filas, columnas y planos de la malla son<br />

<strong>para</strong>lelos al sistema de coordenadas global, x − y − z.<br />

Hay un orden implícito, tanto de los puntos, como de las celdas, que componen<br />

el objeto vtkStructuredPoints. Ambos están numerados en la dirección<br />

creciente de x, y, y z. El número total de puntos es n x × n y × n z , donde n x ,<br />

n y , y n z son las dimensiones del vtkStructuredPoints. El número total de<br />

celdas es (n x − 1) × (n y − 1) × (n z − 1).<br />

vtkRectilinearGrid. Es una colección de puntos y celdas colocados en una malla<br />

regular. Las filas, columnas y planos de la malla son <strong>para</strong>lelos al sistema de<br />

220


coordenadas global, x−y−z. Mientras que la topología es regular, la geometría<br />

es sólo parcialmente regular. Esto es, los puntos están alineados a lo largo de<br />

los ejes de coordenadas, pero el espaciado entre ellos puede variar. Al igual<br />

que vtkStructuredPoints, consta de pixels (2D), o voxels (3D).<br />

La topología se representa especificando las dimensiones de los datos a lo largo<br />

de los ejes coordinados x, y, y z. La geometría se define con tres arrays <strong>para</strong><br />

los valores coordinados a lo largo de estos ejes. Estos tres arrays coordinados<br />

se combinan <strong>para</strong> determinar las coordenadas de cualquier punto en el<br />

dataset. Los arrays se representan en VTK, mediante tres instancias de la<br />

clase vtkScalars.<br />

vtkStructuredGrid. Es una estructura de datos con topología regular, por lo que<br />

al igual que en vtkStructuredPoints, se representa mediante las dimensiones<br />

en el sistema de coordenadas topológico i − j − k. Sin embargo, la geometría<br />

es irregular. La malla se puede “doblar” de cualquier forma, siempre que las<br />

celdas no se solapen o intersecten.<br />

La geometría se representa especificando las coordenadas de todos sus puntos<br />

en el sistema de coordenadas global x−y−z. Para representar las coordenadas<br />

de los puntos se usa la clase abstracta vtkPoints y sus subclases concretas<br />

(por ejemplo, vtkFloatPoints).<br />

vtkPolyData. La topología de este tipo de estructura de datos es irregular, por<br />

lo que tanto la topología, como la geometría se deben especificar de forma<br />

explícita. Los puntos de la geometría se representan mediante instancias de la<br />

clase vtkPoints.<br />

Consta de todos o algunos de los siguientes tipos de celdas: vértices, polivértices,<br />

líneas, polilíneas, polígonos y tiras de triángulos. La topología y<br />

la geometría de vtkPolyData es irregular, y las celdas que forman esta estructura<br />

de datos varían en su número de dimensiones topológicas. Se usa la clase<br />

vtkCellArray <strong>para</strong> representar de forma explícita la topología de la celda.<br />

vtkUnstructuredGrid. Es la forma de dataset más general. Tanto la geometría,<br />

como la topología son completamente irregulares. Se puede usar cualquier<br />

combinación de celdas en este tipo de datos. En general, cualquier dataset<br />

se puede implementar usando una instancia de esta clase. Sin embargo, sólo<br />

se debe usar cuando es absolutamente necesario (no se puede representar con<br />

objetos más sencillo), pues es el que requiere más memoria y capacidad de<br />

computación.<br />

Tanto los puntos, como las celdas se especifican mediante subclases derivadas<br />

de vtkPoints y vtkCellArray.<br />

221


B.2.<br />

Intérpretes y Tcl/Tk<br />

Vamos a com<strong>para</strong>r las ventajas y desventajas de los lenguajes interpretados frente<br />

a los lenguajes tradicionales compilados (algunas de ellas ya se han señalado en<br />

capítulos anteriores). VTK permite programar en C++, en Java y en el lenguaje<br />

interpretado Tcl/Tk.<br />

B.2.1.<br />

Lenguajes Interpretados vs Compilados<br />

Los lenguajes de programación se pueden dividir en compilados e interpretados.<br />

Esta clasificación atiende a la forma meidante la que se interactúa con el<br />

lenguaje.<br />

En un lenguaje compilado, el código fuente se ha de compilar (traducir a<br />

instrucciones máquina), linkar (se juntan los módulos y se resuelven los símbolos), y<br />

después ejecutar. Cuando se detecta un error, se debe editar, recompilar y relinkar<br />

el código, antes de poderse a probar, lo cual consume mucho tiempo.<br />

En un lenguaje interpretado, no hace falta compilar, ni linkar el código. En<br />

su lugar, las instrucciones se escriben directamente en el ordenador, o se escriben<br />

en un fichero, que es leído y ejecutado línea a línea por el intérprete. Usando un<br />

lenguaje interpretado, se puede reducir drásticamente el tiempo de desarrollo de los<br />

programas.<br />

Sin embargo, mientras que el tiempo de desarrollo de los programas es menor en<br />

los lenguajes interpretados, la compilación produce tiempos de ejecución menores.<br />

Los compildores usan métodos eficientes <strong>para</strong> representar y manipular estructuras<br />

complejas de datos. Hay unos pocos lenguajes que soportan compilación e interpretación;<br />

sin embargo, hoy en día, la mayoría de los lenguajes son de un tipo o de<br />

otro.<br />

Para el software VTK, se han escrito las clases usando el lenguaje compilado<br />

C++, debido a sus capacidades como lenguaje orientado a objetos, velocidad de<br />

ejecución eficiente, y uso muy extendio. Pero además, es deseable poder desarrollar<br />

aplicaciones de forma rápida (incluyendo interfaces gráficos de usuario). Por eso<br />

se ha “empaquetado” el lenguaje interpretado Tcl con los objetos de C++. Tcl<br />

es un lenguaje interpretado sencillo, que se puede “embeber” dentro de programas<br />

realizados en otros lenguajes de programación. Además posee un interfaz gráfico,<br />

Tk, muy sencillo de programar. Tcl/Tk es un lenguaje muy usado, cada vez más, y<br />

se distribuye gratuitamente.<br />

El resultado de todo ello es una herramienta de desarrollo de aplicaciones que<br />

ofrece la posibilidad de elegir entre realizar aplicaciones interpretadas o compiladas.<br />

Además, como todos los objetos se han realizado en C++, incluso las aplicaciones<br />

interpretadas se ejecutan de forma relativamente rápida (se deja el lenguaje interpretado<br />

<strong>para</strong> las partes del programa que requieren menor capacidad computacional).<br />

222


El intérprete se usa, normalmente, sólo <strong>para</strong> la manipulación a alto nivel de los<br />

objetos, y raras veces se usa <strong>para</strong> grandes cálculos complicados.<br />

B.2.2.<br />

Introducción a Tcl<br />

Tcl es un lenguaje interpretado, desarrollado por John Outerhout a finales de los<br />

años 80. Se va a hacer una descripción de Tcl muy elemental; <strong>para</strong> más información<br />

sobre este lenguaje de programación, consultar [15].<br />

Se diseñó <strong>para</strong> proporcionar un lenguaje de comandos sencillo, que pudiera ser<br />

integrado con gran variedad de aplicaciones. Tcl está escrito en lenguaje C, y tiene<br />

un API (Interfaz <strong>para</strong> la Programación de Aplicaciones) <strong>para</strong> integrar nuevas funciones.<br />

Su sintaxis es muy similar a la del lenguaje C shell. El script de ejemplo<br />

que se muestra a continuación, ilustra algunas de sus caracterísitcas básicas. Todas<br />

las líneas que empiezan por ‘#’, son comentarios. Las líneas de código comienzan<br />

siempre por un comando, seguido posiblemente por argumentos. Un punto y coma,<br />

o una nueva línea indican el final de un comando.<br />

# Script Tcl <strong>para</strong> calcular la circunferencia de un circulo<br />

set pi 3.1416<br />

set radio 2<br />

set area [expr $pi*$radio*2.0]<br />

puts $area<br />

El comando set toma dos argumentos: el nombre de la variable a crear y su valor<br />

inicial. La segunda línea del ejemplo usa este comando <strong>para</strong> crear una variable<br />

llamada pi con un valor de 3.1415. Hasta la versión 8.0 de Tcl, todas las variables<br />

se almacenaban como cadenas, y se convertían a enteros o punto flotante cuando<br />

hacía falta. La nueva versión Tcl/Tk 8.0 almacena las variables numéricas en este<br />

formato, sin necesidad de conversiones, lo que mejora mucho el rendimiento. En la<br />

cuarta línea se crea la variable area mediante el comando set, pero su inicialización<br />

es más compleja. Encerrando una sentencia Tcl entre corchetes, se puede usar el<br />

resultado de esta sentencia como argumento <strong>para</strong> un comando. El formato de una<br />

línea de código anidada es el mismo que el de cualquier otra, excepto que está entre<br />

corchetes. A esto se le llama sustitución de comandos.<br />

Dentro de los corchetes queremos calcular el área del círculo. Por ello usamos<br />

el comando expr, que evalúa sus argumentos como una expresión matemática y<br />

devuelve el resultado. Se puede ver que hay signos de dólar delante de las dos<br />

variables creadas antes. Esto hace que Tcl haga sustitución de variables y use<br />

el valor de la variable, en lugar de su nombre. Por último, la quinta línea usa el<br />

comando puts, <strong>para</strong> sacar por pantalla el resultado almacenado en la variable area.<br />

Tcl permite crear procedimientos, equivalentes a las funciones de C. En el siguiente<br />

ejemplo podemos ver un procedimiento <strong>para</strong> calcular el área de un rectángulo.<br />

223


Al crear un procedimiento, se crea un nuevo comando de Tcl, con sus correspondientes<br />

argumentos.<br />

# Script Tcl <strong>para</strong> calcular el area de un rectangulo mediante<br />

# un procedimiento<br />

set ancho 4.5<br />

set largo 5.6<br />

set area1 [areaRect $largo $ancho]<br />

puts "El area del rectangulo vale: $area1 metros cuadrados"<br />

# Procedimiento <strong>para</strong> calcular el area de un rectangulo<br />

proc areaRect {lado1 lado2} {<br />

set area [expr $lado1 * $lado2]<br />

return area<br />

}<br />

En la cuarta línea se puede ver que tenemos un nuevo comando llamado areaRect,<br />

creado mediante el procedimiento que aparece al final del ejemplo. Los procedimientos<br />

se crean mediante el comando proc, que tiene dos argumentos; el primero es una<br />

lista con sus argumentos de entrada, y el segundo es el cuerpo del procedimiento.<br />

En la sentencia puts, se puede ver que el argumento de este comando está entre<br />

comillas. Esto permite meter todo lo que está entre comillas como un solo argumento.<br />

Las comillas permiten sustitución de variables. Las llaves también permiten<br />

agrupar argumentos, pero en ellas no se realiza sustitución de variables (por eso<br />

aquí se ponen comillas, <strong>para</strong> que se sustituya $area por su valor).<br />

Veamos un último ejemplo. En el script que aparece abajo, se escriben los<br />

números desde 1 hasta 10 y sus cuadrados.<br />

# Script Tcl <strong>para</strong> imprimir los numeros 1-10 y sus cuadrados<br />

#<br />

for {set num 1} {$num


que la condición sea falsa. El tercer argumento se evalúa al final de cada iteración.<br />

Normalmente se usa <strong>para</strong> incrementar la variable del bucle. En el script se usa el<br />

comando incr <strong>para</strong> incrementar la variable num. El cuarto y último argumento es<br />

el cuerpo del bucle, que se evalúa en cada iteración.<br />

Las llaves son importantes ya que, como hemos dicho, evitan que los argumentos<br />

sean evaluados antes de ser pasados al bucle for. En otro caso, el resultado de<br />

$num


Para las funciones que devuelven punteros, es algo más difícil. Como no se puede<br />

devolver el puntero, se debe convertir a un nombre de cadena único. Para hacer<br />

esto, se mantienen tablas de traducción, que convierten entre punteros y nombres<br />

de cadena. Siempre que se crea un objeto VTK en un script Tcl, el nombre del<br />

objeto y el puntero de la instancia se almacenan en tablas de traducción. Si se usa<br />

ese nombre como argumento de un método, se convierte automáticamente el nombre<br />

de cadena, a un puntero de instancia, usando estas tablas de traducción. Cuando<br />

un método necesita devolver un puntero a una instancia que no estaba creado en<br />

el script Tcl, se crea un nombre único, como por ejemplo vtkTemp0, vtkTemp1,<br />

etc. Este nombre de cadena, así como el puntero, se meten también en las tablas de<br />

traducción <strong>para</strong> un uso futuro. Por ejemplo, en la quinta línea del ejemplo anterior, se<br />

usa el comando set <strong>para</strong> crear la variable propiedad. Su valor inicial es el resultado<br />

de invocar la función GetProperty() de la instancia actor. Normalmente, esta<br />

función devuleve un puntero C++, pero el código de empaquetamiento, lo convierte<br />

a un nombre de cadena y es lo que devuelve. La sexta línea muestra cómo se puede<br />

usar este resultado, mediante la variable propiedad.<br />

La mayoría de los argumentos de entrada y salida de los métodos, son de tipo<br />

simple, como enteros o valores en punto flotante. Cuando una función toma como<br />

argumento un array de tamaño fijo, como por ejemplo float fargs[3], se divide<br />

en componentes individuales. por ejemplo, en C++ y Tcl se usan las siguientes<br />

sentencias <strong>para</strong> llamar a una función:<br />

C++:<br />

Tcl:<br />

Instancia->unMetodo(int iarg, float fargs[3])}<br />

Instancia unMetodo iarg fargs1 fargs2 fargs3}<br />

Para los métodos que devuelven un valor o un puntero a un array, se realiza la<br />

operación opuesta. Se devuelve una cadena sencilla que consta del valor devuelto<br />

por el método o los componentes del array. Para los métodos que devuelven arrays,<br />

los componentes del array están limitados en espacio. Estamos limitados a cadenas<br />

simples a causa de la semántica del lenguaje Tcl.<br />

A causa de las diferencias entre C++ y Tcl, no todos los métodos disponibles<br />

en C++ sin accesibles en Tcl. Se ha desarrollado un programa en Lex y Yacc <strong>para</strong><br />

leer las cabeceras de los ficheros C++ y generar automáticamente el código de empaquetamiento.<br />

Los pocos métodos que no se pueden empaquetar, no se encuentran<br />

disponibbles en el intérprete de Tcl. Cuando se crean nuevas clases en C++ <strong>para</strong><br />

VTK, no hay que preocuparse del empaquetamiento a Tcl, pues se realiza de forma<br />

automática, al realizar la compilación de la clase.<br />

Librerías Dinámicas<br />

El comando Tcl load, se usa <strong>para</strong> cargar de forma dinámica la librería de VTK,<br />

que es una librería dinámica (.dll en Windows y .so en Linux). El comando usado<br />

<strong>para</strong> cargar esta librería, es<br />

226


catch {load vtktcl}<br />

Sólo cuando se carga la librería, los comandos de VTK se hacn disponibles <strong>para</strong><br />

ser usados. El comando Tcl catch es necesario, porque en algunos sistemas la carga<br />

dinámica de librerías no está disponible, o no es necesaria. En estos sistemas, el<br />

comando load provoca un error. El comando catch, evita que los errores hagan al<br />

intérprete abortar la ejecuación. Por eso, se usa la combinación de ambos comandos,<br />

<strong>para</strong> asegurar la portabilidad de los scripts Tcl en distintas plataformas.<br />

B.2.4.<br />

Ejemplo de C++ y Tcl<br />

En este ejemplo se com<strong>para</strong> el código de C++ y de Tcl <strong>para</strong> renderizar un cubo.<br />

Se puede ver cómo se tratan lo punteros de C++ en Tcl. Ambos ejemplos realizan<br />

la renderización de un cubo, y pueden usarse como punto de partida <strong>para</strong> otras<br />

muchas redes de visualización, pues en muchas redes, cambian los objetos fuente y<br />

los filtros, pero el resto es equivalente.<br />

// Codigo C++ <strong>para</strong> dibujar un cubo<br />

#include "vtk.h"<br />

main()<br />

{<br />

vtkRenderer *ren1 = vtkRenderer::New();<br />

vtkRenderWindow *renWin = vtkRenderWindow::New();<br />

renWin->AddRenderer(ren1);<br />

vtkCubeSource *cubeSrc = vtkCubeSource::New();<br />

vtkPolyDataMapper cubeMpr = vtkPolyDataMapper::New();<br />

vtkActor *cubeActor = vtkActor::New();<br />

}<br />

cubeMpr->SetInput(cubeSrc->GetOutput());<br />

cubeActor->SetMapper(cubeMpr);<br />

ren1->AddActor(cubeActor);<br />

renWin->Render();<br />

# Codigo Tcl <strong>para</strong> dibujar un cubo<br />

catch {load vtktcl}<br />

227


vtkRenderer ren1<br />

vtkRenderWindow renWin<br />

renWin AddRenderer ren1<br />

vtkCubeSource cubeSrc<br />

vtkPolyDataMapper cubeMpr<br />

vtkActor cubeActor<br />

cubeMpr SetInput [cubeSrc GetOutput]<br />

cube1 SetMapper cubeMpr<br />

ren1 AddActor cubeActor<br />

renWin Render<br />

B.2.5.<br />

Interfaces de Usuario con Tk<br />

Tk es una extensión de Tcl que proporciona la posibilidad de realizar interfaces<br />

de usuario, con elementos tales como botones, entradas de texto, barras de<br />

desplazamiento, canvas, . . . No se van a describir las posibilidades de Tk; <strong>para</strong> más<br />

información, consultar [15].<br />

Una de las mejores caracterísitcas de Tk es su independiencia con el sistema de<br />

ventanas. Se pueden crear interfaces gráficas de usuario que funcionan, tanto en el<br />

sistema de ventanas de X Windows, como en el de Microsoft Windows. Por tanto,<br />

las aplicaciones realizadas en Tcl/Tk son independientes de la plataforma.<br />

Usando vtkTkRenderWidget<br />

Si se realiza el GUI directamente en VTK, la ventana de renderización, y el<br />

GUI aparecen en ventanas se<strong>para</strong>das. Aunque esto puede ser deseable <strong>para</strong> ciertas<br />

aplicaciones, a menudo es mejor integrar la ventana de renderización y el GUI en una<br />

sola ventana. La aplicación realizada <strong>para</strong> el proyecto es un ejemplo muy completo<br />

de ello.<br />

La integración de la ventana de renderización y Tcl/Tk se realiza usando el objeto<br />

especial vtkTkRenderWidget. Este objeto actúa como un widget de Tk (como canvas,<br />

por ejemplo), pero tiene un método especial, que permite integrarlo con VTK. A<br />

continuación se muestra un ejemplo <strong>para</strong> crear una ventana de renderización como<br />

widget de Tk.<br />

vtkRenderer Renderer<br />

228


vtkTkRenderWidget .window -width 300 -height 300<br />

set RenWin [.window GetRenderWindow]<br />

$renWin AddRenderer Renderer<br />

Otra característica adecuada de vtkTkRenderWidget es que es posible realizar tratamiento<br />

de eventos en el widget. De esta forma, se puede crear un estilo de interacción<br />

propio <strong>para</strong> manipular actores, luces, o cámaras, o características adicionales, como<br />

captura de imagen, o animación de secuencias.<br />

229


230


Apéndice C<br />

Planos<br />

En este apéndice, se muestran parte de los listados de la aplicación Comp3D.<br />

No se muestran todos, por su gran extensión. Se ha intentado que con los listados<br />

que aparecen el proyecto sea autocontenido.<br />

231

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!