\chapter{Sistema desarrollado}

\section{Descripción}

Se ha desarrollado para el presente proyecto un visualizador
interactivo de imágenes JPEG2000 remotas. El visualizador ha sido
desarrollado en Java, apoyándose mediante el API JNI en la
biblioteca Kakadu para el procesamiento de las imágenes. Permite
visualizar de forma progresiva e interactiva imágenes alojadas en
cualquier servidor HTTP/1.1.

\section{Justificación}

Existe una gran demanda de aplicaciones que permitan visualizar
imágenes de gran tamaño a través de Internet. JPEG2000, debido a
sus características, es el estándar de compresión de imágenes
ideal para este cometido.

En la Parte 9 del estándar se define el protocolo JPIP. Este
protocolo define el esquema de comunicación en un sistema cliente/servidor
para la transmisión de imágenes JPEG2000 de forma
eficiente. Este protocolo, no obstante, presenta una serie de
desventajas debido a las cuales no se ha empleado para realizar la
aplicación del presente proyecto:

\begin{itemize}

\item Al ser un protocolo nuevo, requiere de un servidor
específico. Por el momento, la única implementación disponible de
un servidor JPIP está escrita para el sistema Windows, con lo
cual, de necesitar instalar el servidor en otro sistema operativo
diferente, habría que realizar las correspondientes modificaciones
a dicha implementación. Además, no siempre se tiene la posibilidad
de disponer de un servidor específico con acceso a Internet, sino
que únicamente se dispone de un servidor HTTP para alojar el
contenido Web de nuestro sitio y no se tiene control total sobre
el mismo.

\item El protocolo ofrece la posibilidad de mantener \emph{caches}, tanto
en el servidor como en el cliente, con el fin de mejorar el
rendimiento. Este sistema de \emph{cache} ya se dispone en el protocolo
HTTP, en el cual, tanto los \emph{proxys} como los servidores mantienen una
\emph{cache}. Con el protocolo JPIP, la \emph{cache} de los \emph{proxys} resulta
inservible y descartada, ya que generalmente el proxy no entenderá el
contenido de los mensajes y no realizará un \emph{caching} eficiente.

\end{itemize}

Por estos motivos se ha descartado la opción de usar el protocolo
JPIP para transmitir las imágenes entre el cliente y el servidor.
Se ha optado por el protocolo HTTP, en su versión 1.1, el
principal protocolo empleado hoy día en Internet. Es por ello que
no se requerirá trabajar con un servidor específico, sino que
simplemente haremos uso de algún servidor HTTP ya implementado, y
posiblemente implantado. Además, podremos alojar nuestras imágenes
en cualquier servidor que disponga de acceso HTTP, aunque no
tengamos el control total del mismo (porque sencillamente no sea
nuestro, sino un servidor contratado a algún proveedor de
servicios de Internet).

El protocolo HTTP/1.1 posee las siguientes características que lo
hacen perfecto para nuestro cometido:

\begin{itemize}

\item Es el protocolo que se usa en Internet para el WWW.
Cualquier página Web es accedida a través de un servidor HTTP. Es
por ello que el protocolo es algo ya testeado y comprobado, y no
es algo todavía novedoso y poco utilizado, como ocurre con el
protocolo JPIP.

\item Al ser un protocolo tan difundido, no es difícil encontrar
una implementación de un servidor HTTP para cualquier tipo de
plataforma. Esto no ocurre con el protocolo JPIP, para el que la
única implementación conocida está diseñada para correr en
sistemas Windows, aún teniendo en cuenta que la mayoría de los
servidores Web corren en sistemas Unix.

\item Ofrece un sistema de \emph{cache} eficiente y ampliamente testado.
Este sistema resulta invisible para el cliente, aunque se tiene cierto
acceso al mismo. El sistema de \emph{cache} que ofrece el protocolo
JPIP es similar al ofrecido por el protocolo HTTP, con la diferencia
de que no explota la \emph{cache} de los \emph{proxys} que puedan
existir, ya que éstos, a menos que se realice una implementación
específica, no entenderán el nuevo protocolo.

\end{itemize}

Este no es el primer desarrollo que intenta explotar la
combinación del estándar de compresión de imágenes JPEG2000 y el
protocolo HTTP. Saching Deshpande y Wenjun Zeng \cite{SachinZeng}
proponen una solución: para cada imagen, a la cual se requiere
acceso a través del visualizador, es preciso construir un índice,
que se almacena en un archivo independiente. Es posible construir
ese índice con varios niveles de complejidad, de forma que cuanto
más complejo sea, más espacio ocupa, pero más eficiente
resulta la comunicación; y cuanto menos complejo sea, menos espacio
ocupa, pero menos eficiente es la comunicación. El
cliente leería el índice asociado a una imagen, antes de leer ni
un sólo byte de ella, con el fin de localizar aquellas partes de
la misma que necesita. Esta solución presenta los siguientes
inconvenientes:

\begin{itemize}

\item Necesita la implementación de una pequeña aplicación
adicional para la construcción de los índices.

\item El archivo índice ocupará un espacio en disco, en función de
la complejidad elegida para su construcción, con información
bastante redundante, ya que mucha de la información almacenada en
dicho archivo, se encuentra ya en el archivo de imagen.

\item El archivo de índice debe estar ligado inexorablemente al
archivo de la imagen. Esto hace que, si modificamos la imagen por
cualquier motivo, debamos actualizar el archivo de índice
correspondiente.

\item EL cliente debe leer todo el archivo índice, antes de poder
presentar ninguna visualización de la imagen. Esto, para imágenes
grandes, hace que el tiempo de espera inicial sea bastante
elevado, independientemente de la complejidad del archivo índice,
y aunque se quiera visualizar únicamente una porción de la imagen
a una resolución reducida.

\end{itemize}

La Parte 9 del estándar, no sólo define el protocolo JPIP, sino
que define además una forma de incluir un índice en el archivo de
imagen, pensando en aplicaciones como la de Saching Deshpande y
Wenjun Zeng, o la desarrollada para el presente proyecto. Esto
haría que el índice estuviese encapsulado en el archivo de imagen
y no en un archivo independiente como proponen Saching Deshpande y
Wenjun Zeng. Esto soluciona únicamente los dos últimos
inconvenientes mencionados anteriormente, pero los otros dos
continúan existiendo. No sólo eso, sino que la solución que se
propone provoca un aumento excesivo del tamaño que ocupa el
archivo de imagen.

Como se pudo ver en la sección 2.3, el
indexado se facilita para los archivos de la familia JP2. Estos
archivos están organizados en cajas, cada una de las cuales
puede contener a su vez, subcajas. Para la reconstrucción de una
área determinada de una imagen un visualizador necesitaría conocer
dónde se encuentran en el archivo de imagen los paquetes de los
precintos asociados, y qué tamaño ocupan, para poder leerlos. Para
la construcción del índice, según propone la Parte 9, la
información de la posición y longitud de todos los paquetes de
todos los precintos se almacena en la caja \emph{Precinct Packet
Index Table}. En ella, cada paquete ocupa como mínimo 8 bytes,
cuatro bytes para el offset y cuatro para la longitud. Teniendo en
cuenta que una imagen JPEG2000 grande construida con la idea de
ser visualizada remotamente (con un tamaño de precinto
pequeño, un número elevado de capas de calidad, etc.), puede contener una cantidad enorme de paquetes, esta
sobrecarga de espacio es considerable. Eso sin tener en cuenta el
espacio del resto de las cajas necesarias para el índice (y del
espacio que la propia estructura de la caja necesita, aunque esté
vacía). La versión simple del archivo de índice, en la propuesta
de Saching Deshpande y Wenjun Zeng, se construye almacenando los
marcadores del \emph{codestream} de la imagen que facilita el acceso
aleatorio al mismo, como por ejemplo TLM, PLT, PPM, etc. En el
caso de la longitud de los paquetes, ésta es almacenada en los
paquetes PLT o PLM. El offset no es necesario almacenarlo
realmente, ahorrándonos esos 4 bytes, porque todos los
paquetes de un \emph{tile-part} son consecutivos, y la posición de comienzo de cada
\emph{tile-part} se sabe mediante el marcador TLM. En los marcadores PLT
y PLM, la longitud $L$ de cada paquete, como se vio en la sección
1.4, ocupa $\left \lceil \frac{B_{L}}{7} \right
\rceil$ bytes, donde $B_{L}$ es el mínimo de bits requeridos para
representar $L$. Esto quiere decir que $L$, en el peor de los
casos, ocupará 5 bytes, pero sólo en el peor de los casos.
Normalmente, la longitud de un paquete de una imagen con un tamaño
de precinto pequeño y con un nivel elevado de capas de calidad,
ocupará como media 2 bytes, ahorrándonos otros 2 bytes por paquete.

La solución adoptada en el presente proyecto no hará uso del protocolo
JPIP, sino del protocolo HTTP/1.1 para la transmisión de la imagen. No
empleará el indexado propuesto por la Parte 9 del estándar, ni tampoco
la solución propuesta por Saching Deshpande y Wenjun Zeng, basándonos
en lo anteriormente expuesto. La aplicación desarrollada permitirá
visualizar progresivamente un imagen JPEG2000 remota, almacenada en
un archivo J2C, mediante el protocolo HTTP/1.1, sin necesidad de índice
alguno.

Además de los protocolos JPIP y HTTP, Jin Li y Hong-Hui Sun
\cite{LiSun} propusieron un protocolo especial llamado \emph{Vmedia}.

\section{Formato del archivo de imagen}

Aunque existen varios tipos de formato para los archivos de imagen
JPEG2000, la aplicación sólo trabajará con el formato J2C. En
realidad, un archivo J2C no posee ningún formato interno, sino que
simplemente almacena el \emph{codestream} de una imagen JPEG2000. Las
principales razones de utilizar este formato son las siguientes:

\begin{itemize}

\item La lectura de la imagen es mucho más sencilla que para el
caso del resto de formatos. Por ello, la lectura será también mucho más
rápida. Para el caso del formato JP2, por ejemplo, el \emph{codestream}
está almacenado en una caja, a la cual se accede una vez leídas
las cajas que le preceden. No obstante, se comentará cómo poder
ampliar la aplicación desarrollada para que soporte este tipo de
archivos.

\item Al no incluir otras estructuras, el archivo de imagen ocupa
lo que ocupe el \emph{codestream}. Se minimiza por ello la cantidad de
bytes a transmitir, limitándose únicamente a lo estrictamente
necesario.

\end{itemize}

Por contra, la adopción del formato J2C, supone la desventaja de
no poder incluir otro tipo de información que sí es posible
incluir en los otros formatos, como por ejemplo, la paleta de
colores, si resultara necesaria. Esto nos limita a que las
imágenes que empleemos no requieran información adicional, sino
que el \emph{codestream} sea autosuficiente. No podríamos, entonces,
emplear imágenes con paleta, ya que la paleta de colores
únicamente es posible incluir dentro de una caja en el formato JP2
o similar. No obstante, tampoco es una limitación preocupante, ya
que generalmente las imágenes grandes, a las cuales va orientada
nuestra aplicación, no son imágenes con paleta.

Nuestra aplicación será capaz de visualizar cualquier tipo de
imagen J2C, pero sólo lo hará correctamente si la compresión de la
imagen ha sido realizada cumpliendo una serie de requisitos:

\begin{itemize}
\item Debe poseer un único \emph{tile}.

\item Debe tener una progresión RLCP.

\item Debe poseer un \emph{tile-part} por resolución.

\item Debe poseer uno o más marcadores TLM.

\item Debe poseer uno o más marcadores PLT en la cabecera de cada
\emph{tile-part}.
\end{itemize}

El hecho de tener un solo \emph{tile} es algo común en las imágenes, ya
que el uso de varios \emph{tiles} empeora la calidad de la imagen. El
\emph{tiling} sólo es recomendado para cierto tipo de aplicaciones, que
no es el caso de la desarrollada.

El requisito de tener la progresión RLCP va asociado al de poseer los
marcadores PLT y al de tener un \emph{tile-part} por resolución.  Los
marcadores PLT son los que almacenan la longitud de los paquetes de un
\emph{tile-part}. Si hubiese un solo \emph{tile-part} general, habría
un marcador PLT (podría haber más, contiguos, pero generalmente, habrá
sólo uno por \emph{tile-part}) general con la información de todos los
paquetes. Esto supondría que el visualizador, antes de poder recuperar
cualquier conjunto aleatorio de paquetes de la imagen, necesitaría
leer el marcador PLT completo, el cual puede tener un tamaño
considerable, aumentando el tiempo necesario. Esto no sería eficiente,
ya que generalmente el conjunto de paquetes que requiere el
visualizador corresponden a un área de la imagen a una resolución
determinada. Por ello, el \emph{tile} se divide en tantos
\emph{tile-parts} como resoluciones tenga la imagen. En la cabecera de
cada \emph{tile-part} se incluye el correspondiente marcador PLT. De esta
forma, cuando el visualizador requiera un área de la imagen a una
resolución determinada, se tendrá que leer los marcadores PLT del
\emph{tile-part} asociado a esa resolución y el de todos los \emph{tile-parts}
de las resoluciones inferiores, si no lo ha hecho ya.

El dividir el \emph{tile} en \emph{tile-parts} por resolución obliga a asumir o
bien una progresión RLCP o bien una RPCL. En nuestro caso se ha
optado por escoger la progresión RLCP, debido a que la aplicación
desarrollada leerá los paquetes tal y como específica esta
progresión, por capa, por componente y por precinto. Además, en el
proceso de lectura por HTTP mediante \emph{byte-ranging}, habrá paquetes
que aparezcan contiguos, para una región de la imagen determinada,
con lo cual, en vez de solicitar al servidor HTTP esos paquetes
uno a uno, se podrán juntar en la misma petición, con el
consiguiente aumento en la velocidad de transmisión.

Estos son los requerimientos necesarios para que el visualizador
funcione correctamente. Si la imagen no cumple estos requisitos,
se deberá leer la imagen entera para poder visualizarla.

Después existen una serie de recomendaciones para mejorar el
rendimiento del visualizador:

\begin{itemize}
\item Usar precintos de pequeño tamaño, de 128x128 o 64x64. El
motivo es que el visualizador, para poder visualizar una región
determinada de la imagen, leerá todos los paquetes
correspondientes a los precintos necesarios para reconstruir dicha
región. Por lo tanto, cuanto más pequeños son los precintos en la
imágenes, más fina es la granularidad, haciendo que el tamaño de
los paquetes a leer por el visualizador sea menor.

\item Utilizar un número elevado de capas de calidad. El
visualizador va repitiendo el proceso de reconstrucción y
visualización del área de la imagen requerido por el usuario desde
que empieza a solicitar el primer paquete necesario, hasta que
llega la información del último. Por ello, y debido a que la
transmisión de los paquetes se realiza por capas, cuantas más capas
existan, más se reduce el número de bytes necesarios a leer por el
visualizador para que el usuario pueda ver de forma aceptable la
imagen.

\end{itemize}

\section{Estructura interna de la aplicación}

La estructura de la aplicación de divide en un primer nivel en dos
grandes sistemas, el sistema de visualización y el sistema de
gestión de imagen. Empleamos el concepto de sistema para designar
a una parte del programa con una funcionalidad definida, y que
puede estar formada por una o más clases Java. No se describirán
todas las clases que forman la aplicación, obviando las que sirven
solamente de apoyo a la estructura principal.

La figura \ref{fig:estructuraApp} muestra estos dos sistemas
principales. En ella se puede apreciar que el sistema de gestión
de imagen posee a su vez dos subsistemas, el sistema de
comunicación y el sistema de descompresión.

\begin{figure}%[!hbp]
  \begin{center}
    \resizebox{!}{!}{\input{figuras/estructuraApp}}
    \caption{Estructura de la aplicación.}
    \label{fig:estructuraApp}
  \end{center}
\end{figure}

En las figuras representadas en esta sección, tanto la clase como
el sistema se representan mediante un rectángulo en el que va
incluido el nombre. Los hilos de ejecución que pueda lanzar una
determinada clase son también representado mediante rectángulos.
Las entidades externas a la aplicación, como el usuario o el
servidor, son representadas por una elipse. Las flechas muestran
la relación existente entre dos clases o sistemas. Las flechas
pueden ir acompañas de un pequeño texto aclaratorio del tipo de
relación que es, o de la información que se comparte en dicha
relación.

\subsection{Sistema de visualización}

El sistema de visualización está formado por todas las clases Java
encargadas de construir la interfaz de usuario.

La clase encargada de construir la casi totalidad de la interfaz
de usuario es la clase \emph{ImageWindow}, que deriva de la clase
\emph{JFrame}. Se encarga de crear la ventana, los botones, las
barras de desplazamiento, etc.

La zona donde se visualiza cualquier imagen, o un área de la misma, es
administrada por la clase \emph{ImagePanel}. Debido a que tenemos la
posibilidad de incluir una vista en miniatura como ventana interna, un
\emph{JInternalFrame}, la clase \emph{ImagePanel} debe derivar de la
clase \emph{JDesktopPane}. La clase \emph{ImagePanel} es la encargada
de permitir la interacción del usuario con la imagen, permitiéndole al
usuario hacer \emph{zoom} o moverse por el contenido de la imagen. Las
operaciones que realiza el usuario con la imagen, la clase
\emph{ImagePanel} las traduce al par (ROI, resolución), es decir, que
resolución se requiere, ya que puede cambiar al hacer el usuario
\emph{zoom}, y la región de interés (ROI, \emph{Region Of Interest})
de la imagen a visualizar en la resolución dada. Nótese que la
posibilidad ofrecida al usuario de hacer \emph{zoom} está directamente
vinculada a los niveles de resolución de la imagen, suponiendo que el
hacer \emph{zoom} consiste en moverse por dichos niveles. El par (ROI,
resolución) es transmitido al sistema de gestión de imagen.  El
sistema de gestión de imagen, conforme vaya teniendo nuevas porciones
de la ROI requerida, se lo irá notificando al sistema de
visualización, en especial a la clase \emph{ImagePanel}, para que
actualice la visualización en pantalla. La clase \emph{ImagePanel}
implementa la interfaz \emph{ImageObserver}, a través de la cual el
sistema de gestión de imagen puede realizarle las correspondientes
notificaciones. Para ello, deberá haber llamado la clase
\emph{ImagePanel} al método \emph{addImageObserver()} de la clase
principal del sistema de gestión de imagen, la clase \emph{JP2Image}.

La figura \ref{fig:sistemaVisualizacionApp} representa de forma
general la estructura del sistema de visualización, y su relación
con el resto de sistemas o entidades.

\begin{figure}%[!hbp]
  \begin{center}
    \resizebox{!}{!}{\input{figuras/sistemaVisualizacionApp}}
    \caption{Estructura del sistema de visualización.}
    \label{fig:sistemaVisualizacionApp}
  \end{center}
\end{figure}

\subsection{Sistema de gestión de imagen}

El sistema de gestión de imagen es el encargado de la gestión y
tratamiento de la imagen a petición del sistema de visualización.
El sistema de visualización le comunica a este sistema qué ROI y
resolución requiere el usuario, y éste lleva a cabo las tareas
necesarias para conseguirlo, notificando al sistema de
visualización en cuanto existan resultados.

El sistema de gestión de imagen es representado únicamente por la
clase \emph{JP2Image}, encargada de toda la gestión de la imagen.

La clase \emph{JP2Image} incluye además dos subsistemas, el sistema de
comunicación y el sistema de descompresión.

Al recibir la clase \emph{JP2Image} una petición del sistema de
visualización, esta analizará la estructura del \emph{codestream}
de la imagen, empleando la clase \emph{Kdu\_codestream}, para
determinar qué paquetes son necesarios leer del servidor. De los
paquetes necesarios, se descartan aquellos que ya existan en la
\emph{cache}, mantenida por la clase \emph{JP2Cache}, que a su vez
deriva de la clase \emph{Kdu\_cache}. Esta clase será la empleada
para inicializar la clase \emph{Kdu\_codestream} y ésta última la
empleará para acceder a los datos del \emph{codestream} de la
imagen. Los paquetes que son necesarios se comunican al sistema
de comunicación para que lleve a cabo las peticiones HTTP
correspondientes al servidor. El sistema de comunicación va
almacenando directamente los paquetes recibidos del servidor en la
\emph{cache}, en la clase \emph{JP2Cache}.

La clase \emph{JP2Image} posee un objeto de la clase
\emph{BufferedImage} donde almacenar la reconstrucción de la ROI.

Al establecer la ROI y la resolución, no sólo se determinan los
paquetes necesarios a leer, sino que se determinan también que
regiones son necesarias descomprimir, basándose en la ROI
anterior. Estas regiones son comunicadas al sistema de
descompresión para que las vaya descomprimiendo. Para la
descompresión, este sistema necesita del \emph{codestream} de la
imagen, cuyo contenido irá siendo actualizado por el sistema de
comunicación. En el objeto de la clase \emph{Kdu\_codestream} se
establecerá que resolución es la necesaria.

La figura \ref{fig:sistemaGestionApp} representa de forma general
la estructura del sistema de gestión de imagen, y su relación con
el resto de sistemas o entidades.

\begin{figure}
  \begin{center}
    \resizebox{\textwidth}{!}{\input{figuras/sistemaGestionApp}}
    \caption{Estructura del sistema de gestión de imagen.}
    \label{fig:sistemaGestionApp}
  \end{center}
\end{figure}

\subsection{Sistema de descompresión}

El sistema de descompresión es un subsistema del sistema de
gestión de imagen. Su misión es descomprimir aquellas regiones de
la imagen que éste último requiera. La clase que identifica al
sistema de descompresión es la clase \emph{JP2Render}.

La figura \ref{fig:sistemaDescompresion} representa de forma
general la estructura del sistema de descompresión, y su relación
con el resto de sistemas o entidades.

\begin{figure}
  \begin{center}
    \resizebox{\textwidth}{!}{\input{figuras/sistemaDescompresion}}
    \caption{Estructura del sistema de descompresión.}
    \label{fig:sistemaDescompresion}
  \end{center}
\end{figure}

Cada región se especifica mediante un objeto de la clase
\emph{Region} y se almacena en una lista, representada por un
objeto de la clase \emph{RegionsList}, la cual deriva a su vez de
la clase \emph{ArrayList}. Ésta es una lista FIFO.

La clase \emph{JP2Render} lanza un hilo de ejecución que
permanecerá continuamente esperando a que haya regiones para
descomprimir en la lista de la clase \emph{RegionsList}. En el
momento en el que haya una nueva región a descomprimir, empleará
para su descompresión un objeto de la clase
\emph{Kdu\_region\_decompressor}, y el \emph{codestream} de la
imagen, al cual se accede mediante el objeto de la clase
\emph{Kdu\_codestream}, de la clase \emph{JP2Image}. Al sistema de
descompresión no es necesario pasarle la resolución, ya que
empleará la establecida en \emph{Kdu\_codestream}.

Conforme se vayan obteniendo porciones de cada región, éstas se
irán almacenando directamente en el \emph{buffer} de la imagen, en
el objeto de la clase \emph{BufferedImage} de la clase
\emph{JP2Image}, a la vez que se lo notifica al sistema de
visualización para realice la correspondiente actualización.

\subsection{Sistema de comunicación}

El sistema de comunicación es el encargado de leer a través del
protocolo HTTP/1.1 los paquetes necesarios para construir la ROI
requerida a la resolución indicada. La clase que identifica al
sistema de comunicación es la clase \emph{JP2Reader}.

La figura \ref{fig:sistemaComunicacionApp} representa de forma
general la estructura del sistema de comunicación, y su relación
con el resto de sistemas o entidades.

\begin{figure}
  \begin{center}
    \resizebox{!}{!}{\input{figuras/sistemaComunicacionApp}}
    \caption{Estructura del sistema de comunicación.}
    \label{fig:sistemaComunicacionApp}
  \end{center}
\end{figure}

Los paquetes que requiere el sistema de gestión de imagen son
indicados mediante objetos de la clase \emph{PacketData}. Estos
objetos se almacenan en una lista FIFO representada por un objeto
de la clase \emph{PacketsList}, la cual deriva de la clase
\emph{ArrayList}.

La clase \emph{JP2Reader} lanza dos hilos de ejecución, uno para
escritura y otro para lectura. El de escritura va recorriendo la
lista de la clase \emph{PacketsList} y enviando al servidor las
peticiones correspondientes. El hilo de lectura va leyendo paquete
a paquete del servidor según especifique la información de cada
paquete en la lista de la clase \emph{PacketsList}. Conforme se
vaya recibiendo cada paquete, éste se va almacenando directamente
en la \emph{cache} de la clase \emph{JP2Image}.

La comunicación con el servidor HTTP se realiza mediante un objeto
de la clase \emph{HTTPReader}. Esta clase permite realizar
\emph{byte-ranging} con el protocolo HTTP/1.1 y admite mensajes
\emph{``chunkeados''}.

\section{Funcionamiento}

En la sección anterior se dio una breve descripción de la
estructura del código de la aplicación desarrollada, así como una
pequeña introducción al funcionamiento de cada uno de los sistemas
que la componen. En esta sección se entrará más en detalle en el
funcionamiento de cada una de las tareas que puede llevar a cabo
la aplicación. Cada tarea es desarrollada por uno o varios de los
sistemas de la aplicación.

\subsection{Apertura de la imagen}

Cuando el usuario introduce la dirección de una nueva imagen y
pulsa la tecla INTRO, o simplemente pulsa sobre el botón Recargar,
se procede a realizar la apertura de la imagen cuya URL viene
indicada por la cadena que haya en el cuadro de texto
\emph{Dirección}.

Si la cadena no forma una URL correcta, o el protocolo indicado no
es el HTTP, se asumirá que se desea visualizar una imagen local,
cuyo proceso a seguir no se describirá en la presente
documentación, al no ser el cometido del proyecto. Si la cadena
forma una URL correcta y el protocolo indicado es el HTTP, se
procederá a la apertura normal de la imagen remota indicada. Este
proceso no ha sido referenciado en las figuras de la sección
anterior.

El proceso de apertura de imagen comienza al llamar el objeto de
la clase \emph{ImagePanel} al método \emph{open()} de la clase
JP2Image, es decir, el sistema de visualización le solicita al
sistema de gestión de imagen la apertura de una nueva imagen. A
este método se le pasa la cadena (\emph{String}) con la URL de la
imagen. El código del método \emph{open()} crea un objeto nuevo de
la clase \emph{Kdu\_codestream}, y otro también de la clase
\emph{JP2Cache}. A continuación, se crea provisionalmente un
objeto de la clase \emph{HTTPReader} que nos permitirá leer la
cabecera principal del \emph{codestream} de la imagen remota. Esta
lectura no se realizará en un hilo a parte, puesto que no es
posible llevar a cabo ninguna tarea posterior sin acabar antes la
lectura de la cabecera principal, ni empleando \emph{pipelining},
ya que cada lectura depende de la anterior.

El algoritmo de la lectura de la cabecera principal es como el
siguiente:

\begin{verbatim}
marcador = httpReader.read(2);
si (marcador != SOC) error;
cache.addToMainHeader(marcador);

repetir {
  marcador = httpReader.read(2);
  si (marcador == SOT) salir del bucle;
  si no {
    marcadorLen = httpReader.read(2);
    si (marcador == COM) o
       (marcador == PLM) o
       (marcador == PPM) httpReader.skip(marcadorLen-2);
    si no {
      datos = httpReader.read(marcadorLen-2);
      si (marcador == TLM) tlmRecord.addSegment(datos);
      si no {
        cache.addToMainHeader(marcador);
        cache.addToMainHeader(marcadorLen);
        cache.addToMainheader(datos);
    }
  }
}

cache.completeMainHeader()

\end{verbatim}

En el algoritmo, se asume que las variables \emph{marcador} y \emph{marcadorLen}
son del tipo \emph{byte[]}, el objeto \emph{httpReader} es de la clase
\emph{HTTPReader}, el objeto \emph{tlmRecord} es de la clase \emph{TLMRecord} y el
objeto \emph{cache} de la clase \emph{JP2Cache}.

La clase \emph{HTTPReader} mantiene un puntero de archivo,
inicializado a cero, de forma que permite realizar lecturas
consecutivas, como se indica en el algoritmo. Estas lecturas se
realizan mediante el método \emph{byte[] read(int nbytes)}.

Dicho método devuelve un \emph{array} (\emph{byte[]}) con los
datos leídos. Esta lectura incluye la conexión con el servidor
HTTP, si no existe la conexión aún, el envío de la petición HTTP
con el \emph{byte-range} apropiado, y la lectura de la respuesta.

La clase \emph{JP2Cache} deriva de la clase \emph{Kdu\_cache}. Como se
indicó en el capítulo dedicado al paquete Kakadu, esta clase permite
disponer de una \emph{cache}. La \emph{cache}, al estar pensada para
desarrollar sistemas clientes/servidor empleando el protocolo JPIP,
está organizada en \emph{data-bins}. El método de la clase
\emph{Kdu\_cache} que permite añadir contenido en bytes a un
\emph{data-bin} determinado es:

\begin{verbatim}
void Add_to_databin(int databin_class, long codestream_id,
                    long databin_id, byte[] data, int offset,
                    int num_bytes, boolean is_final,
                    boolean add_as_most_recent,
                    boolean mark_if_augmented)
\end{verbatim}

El parámetro \emph{databin\_class} indica qué tipo de \emph{data-bin}
es, pudiendo tomar el valor \emph{KDU\_MAIN\_HEADER\_DATABIN} para la
cabecera principal, \emph{KDU\_TILE\_HEADER\_DATABIN} para la cabecera
de un \emph{tile} o \emph{KDU\_PRECINCT\_DATABIN} para los datos de un
precinto; \emph{codestream} contiene el identificador de
\emph{codestream}, cuando trabajamos con varios \emph{codestreams}
simultáneamente, que no es nuestro caso, por el que siempre valdrá
cero; \emph{databin\_id} identifica al \emph{data-bin}, útil
únicamente para el caso de los \emph{data-bins} de las cabeceras de
los \emph{tiles} y de los precintos, ya que para el caso de la
cabecera principal valdrá cero; \emph{data} contiene los datos a
añadir al \emph{data-bin}; \emph{offset} posee la posición dentro del
\emph{buffer} del \emph{data-bin} a partir de la cual copia los datos
de data; \emph{num\_bytes} contiene el número de bytes a copiar;
\emph{is\_final} indicará si el \emph{data-bin} se completa al añadir
los datos de data o no. El resto de parámetros no los emplearemos y
los dejaremos con el valor \emph{false}.

El uso de este método es un poco engorroso, por lo que la clase
JP2Cache encapsula las llamadas a este método en una serie de
métodos, cuya implementación sería:

\begin{verbatim}
void addToMainHeader(byte datos[])
{
  void Add_to_databin(
    KDU_MAIN_HEADER_DATABIN, 0, 0,
    datos,
    Get_databin_length(KDU_MAIN_HEADER_DATABIN, 0, 0, null),
    datos.length,
    false,
    false, false);
}

void completeMainHeader()
{
  void Add_to_databin(
    KDU_MAIN_HEADER_DATABIN, 0, 0,
    null,
    Get_databin_length(KDU_MAIN_HEADER_DATABIN, 0, 0, null),
    0,
    true,
    false, false);
}

void addToTileHeader(byte datos[], int tileID) {
  void Add_to_databin(
    KDU_TILE_HEADER_DATABIN, 0, tileID,
    datos,
    Get_databin_length(KDU_TILE_HEADER_DATABIN, 0, 0, null),
    datos.length,
    false,
    false, false);
}

void completeTileHeader(int tileID) {
  void Add_to_databin(
    KDU_TILE_HEADER_DATABIN, 0, tileID,
    null,
    Get_databin_length(KDU_TILE_HEADER_DATABIN, 0, 0, null),
    0,
    true,
    false, false);
}

void addToPrecinct(byte datos[], int precID) {
  void Add_to_databin(
    KDU_PRECINCT_DATABIN, 0, precID,
    datos,
    Get_databin_length(KDU_PRECINCT_DATABIN, 0, 0, null),
    datos.length,
    false,
    false, false);
}

void completePrecinct(int precID) {
  void Add_to_databin(
    KDU_PRECINCT_DATABIN, 0, precID,
    null,
    Get_databin_length(KDU_PRECINCT_DATABIN, 0, 0, null),
    0,
    true,
    false, false);
}
\end{verbatim}

El algoritmo mostrado anteriormente sirve para leer la cabecera
principal del \emph{codestream} y almacenarlo en el \emph{data-bin}
correspondiente de la \emph{cache}. Nótese que esta lectura salta los
marcadores PPM, PLM y COM y no los incluye en la \emph{cache}. El
motivo del marcador COM es obvio, al contener un mero comentario, de
principio sin función alguna. Los marcadores PPM Y PLM, no son
utilizados en la aplicación, y su inclusión o no influye en el
rendimiento de la biblioteca de Kakadu, por lo que son ignorados.
Además, suelen tener un tamaño considerable.

El marcador TLM tampoco es incluido en la \emph{cache}, pero sí leído
y almacenado en un objeto de la clase \emph{TLMRecord}. La clase
\emph{TLMRecord} permite almacenar los marcadores TLM de un
\emph{codestream} para después poder ser procesados, con el fin de
obtener la información de cada \emph{tile-part}, esto es, el
\emph{offset} y la longitud de cada uno. Para añadir el contenido de
un marcador TLM a un objeto de esta clase empleamos el método
\emph{addSegment(byte data[])}. No se incluye ni los 2 bytes del
marcador ni los dos bytes de la longitud del marcador.

El bucle de lectura de la cabecera principal finaliza cuando
encuentra el primer marcador SOT. Entonces se le debe indicar a la
\emph{cache} que el \emph{data-bin} de la cabecera principal está
completo, ya que si no, el comportamiento de la clase
\emph{Kdu\_codestream} no será el correcto. Este se realiza
llamando al método \emph{Add\_to\_databin()} con el parámetro
\emph{is\_final} a \emph{true} y \emph{num\_bytes} igual a cero.
Esta llamada está encapsulada en el método
\emph{completeMainHeader()} de la clase \emph{JP2Cache}.

Una vez leída la cabecera principal se procederá a leer las
cabeceras de todos los \emph{tile-parts} del primer \emph{tile}.
Recordemos que la imagen debería constar de un único \emph{tile}.

El algoritmo para la lectura de las cabeceras sería similar al
siguiente:

\begin{verbatim}
para i desde 0 hasta (tlmRecord.numTileParts(0)-1) {
  httpReader.seek(tlmRecord.getTilePartOffset(0, i));
  marcador = httpReader.read(2);
  si (marcador != SOT) error;
  marcadorLen = httpReader.read(2);
  httpReader.skip(marcadorLen-2);
  repetir {
    marcador = httpReader.read(2);
    si (marcador == SOD) salir bucle;
    si no {
      marcadorLen = httpReader.read(2)
      si (marcador == PPT) o
         (marcador == POC) o
         (marcador == COM) httpReader.skip(marcadorLen-2);
      si no {
        si (marcador == PLT) {
          pltRecord.addSegment(i, httpReader.getPos(), marcadorLen-2);
          httpReader.skip(marcadorLen-2);

        } si no {
          cache.addToTileHeader(0, marcador);
          cache.addToTileHeader(0, marcadorLen);
          cache.addToTileHeader(0, datos);
        }
      }
    }
  }
}

cache.completeTileHeader(0)
\end{verbatim}

Con el método \emph{numTileParts()} de la clase \emph{TLMRecord}
obtenemos el número de \emph{tile-parts} del primer, y en teoría,
único \emph{tile}. Con el método \emph{getTilePartOffset()}
obtenemos el \emph{offset} dentro del archivo donde comienza cada
\emph{tile-part}, para poder leer la cabecera asociada.

El \emph{data-bin} correspondiente a la cabecera de un \emph{tile}
debe contener las cabeceras de todos los \emph{tile-parts} que lo
componen, quitando los marcadores SOT y SOD. En nuestro caso
eliminamos también los marcadores COM, PPT y POC, ya que de todos
modos serán ignorados.

El marcador PLT de cada \emph{tile-part} tampoco se almacena, pero
se guarda la referencia del mismo, esto es, se guarda el \emph{offset},
la longitud y el número de \emph{tile-part} al que pertenece. Se almacena en un objeto
de la clase \emph{PLTRecord}. Esta clase nos permitirá después
analizar el contenido de dichos marcadores con el fin de obtener
la longitud de cada paquete de cada \emph{tile-part}.

Una vez leída la cabecera principal y la cabecera del primer
\emph{tile}, estamos en condiciones de inicializar el objeto de la
clase \emph{Kdu\_codestream} con el objeto de la clase
\emph{JP2Cache}, que contiene la \emph{cache} con todos los datos
leídos. Esta inicialización la realizamos llamando al método
\emph{Create()}. Le indicamos a esta clase también que trabaje haciendo
los recursos persistentes con el método \emph{Set\_persistent()}.

Finalmente, es necesario realizar una serie de comprobaciones para
asegurarnos de que el \emph{codestream} de la imagen es compatible
con la aplicación, es decir:

\begin{itemize}
\item Que tenga un sólo \emph{tile}: El subsistema de parámetros de la
  biblioteca Kakadu, a través del marcador SIZ almacenado, nos da el
  tamaño de los \emph{tiles}, $(t_{w}, t_{h})$, y el tamaño de la
  imagen, $(i_{w}, i_{h})$, de forma que habrá un sólo \emph{tile} si
  $t_{w} \geq i_{w}$ y $t_{h} \geq i_{h}$.

\item Que tenga un \emph{tile-part} por resolución: No podemos
  asegurar esto con certeza, a menos que recorramos el archivo entero,
  pero pero podemos comprobar al menos que existen tantos
  \emph{tile-parts}, como número de resoluciones haya.

\item Que tenga la progresión RLCP: Accediendo al subsistema de
parámetros de Kakadu, en concreto al marcador COD, a través de la
clase \emph{Kdu\_codestream}, podemos determinar la progresión del
\emph{codestream} y comprobar si realmente es RLCP o no.

\item Que disponga de marcadores TLM: El objeto de la clase
\emph{TLMRecord} debe tener al menos un marcador almacenado.

\item Que disponga de marcadores PLT: El objeto de la clase
\emph{PLTRecord} debe tener al menos tantos marcadores como
\emph{tile-parts} existen.

\end{itemize}

Si el \emph{codestream} no cumple alguno de estos requisitos, se le
notificará al usuario un mensaje de error.

Una vez abierta la imagen, el sistema de gestión de imagen está
preparado para aceptar peticiones del sistema de visualización.
Inicialmente no se establece ninguna ROI, por lo que los
subsistemas de descompresión y comunicación permanecen inactivos,
a la vez que el \emph{buffer} para la imagen del objeto de la
clase \emph{BufferedImage} estará vacío. Es necesario mencionar
que antes de proceder con la apertura de la imagen, el sistema
de gestión de imagen debe finalizar la ejecución del subsistema
de descompresión y del subsistema de comunicación, si éstos
están en ejecución.

\subsection{Establecimiento de la ROI}

Cuando el usuario interacciona con la imagen, a través del sistema
de visualización, éste le comunica al sistema de gestión de imagen
la ROI y la resolución necesaria.

Si no se ha establecido con anterioridad ninguna ROI, simplemente se
crea el \emph{buffer} de imagen, que será un objeto de la clase
\emph{BufferedImage}, con las dimensiones de la ROI. En el que caso de
que ya se hubiera establecido una ROI anteriormente, se creará un
\emph{buffer} de imagen nuevo para la nueva ROI, pero, si la nueva ROI
intersecciona con la anterior ROI, se copiará la intersección del
\emph{buffer} anterior al nuevo \emph{buffer}. Este proceso se ilustra
en la figura \ref{fig:cambioROIApp}.

\begin{figure}
  \begin{center}
    \resizebox{!}{!}{\input{figuras/cambioROIApp}}
    \caption{Cambio de la ROI.}
    \label{fig:cambioROIApp}
  \end{center}
\end{figure}

La región de interés y la resolución deseada se la comunicamos al
objeto de la clase \emph{Kdu\_codestream} mediante el siguiente método:

\begin{verbatim}
void Apply_input_restrictions(int first_component,
                              int max_components, int discard_levels,
                              int max_layers,
                              Kdu_dims region_of_interest)
\end{verbatim}

Este método, como su propio nombre indica, se emplea para restringir
el acceso al \emph{codestream} de la imagen, pudiendo así limitar el
acceso a un número de resoluciones, a un numero de capas o a un numero
de componentes. El parámetro \emph{first\_component} identifica el
índice (empezando desde 0) de la primera componente a partir de la
cual se permitirá el acceso; \emph{max\_components} es el número de
componentes permitidas, empezando por la componente indicada en
\emph{first\_component}, pero si es 0, todas las componentes son
permitidas; \emph{discard\_levels} es el número de resoluciones
superiores a las que no se tendrá acceso; \emph{max\_layers} es el número
máximo de capas de calidad permitidas y si es 0, todas las capas
son permitidas; \emph{region\_of\_interest} define una área a la cual
restringir el acceso, no pudiendo descomprimir ninguna otra parte
de la imagen.

En nuestro caso, llamaríamos a este método con los siguientes
parámetros:

\begin{verbatim}
codestream.Apply_input_restrictions(0, 0, resolucion, 0, ROI)
\end{verbatim}

Con ello permitiríamos el acceso a todos los componentes y todas
las capas de calidad disponibles, restringiríamos el acceso a
tantas resoluciones, empezando por la mayor, como la variable
\emph{resolucion} indique, y para dicha resolución, restringiríamos el
acceso a la región establecida por la variable \emph{ROI}, la cual
asumimos que es de la clase \emph{Kdu\_dims}. Nótese que en la aplicación
desarrollada, el concepto de resolución es diferente del concepto
de resolución del estándar JPEG2000. En nuestro caso, la
resolución cero sería la máxima resolución, que sería la imagen
original. Al establecer la llamada anterior, cuando el sistema de
descompresión reconstruya la imagen, se obtendrá una imagen del
tamaño de ROI y con la resolución, según el estándar JPEG2000, de
$D_{t,c} - resolucion$.

El sistema de gestión de imagen debe determinar qué áreas de la
ROI debe reconstruir. En primer lugar, obtendrá la partición en
regiones rectangulares de la región resultante al quitarle a la
nueva ROI la parte de la intersección de ella misma con la
anterior ROI. La figura \ref{fig:particionROIApp} muestra un ejemplo
gráfico de dicha partición. La partición crea regiones
rectangulares, ya que el descompresor no admite otro tipo de
regiones. Estas regiones las introduce en la lista del sistema de
descompresión.

\begin{figure}
  \begin{center}
    \resizebox{!}{!}{\input{figuras/particionROIApp}}
    \caption{Regiones a descomprimir de una ROI.}
    \label{fig:particionROIApp}
  \end{center}
\end{figure}

En la lista de regiones a descomprimir del sistema de
descompresión, cada región se almacena como un objeto de la clase
\emph{Region}. Esta clase deriva de la clase \emph{Rectangle}, heredando así las
variables miembro \emph{x}, \emph{y}, \emph{width} y \emph{height}.

El sistema de gestión solicita a la lista de regiones del sistema
de descompresión que actualice su contenido en función de la ROI.
Esto se realizará llamando al método \emph{actualize(Rectangle ROI)} de
la clase \emph{RegionsList}, clase a la que pertenece el objeto que
representa la lista de regiones. Este método recorre la lista y
actualiza cada región con la intersección de ella con la ROI. Si
no intersecciona una región con la ROI simplemente es eliminada de
la lista.

Una vez definidas las regiones que son necesarias descomprimir, es
preciso obtener que paquetes son los que se necesitan leer con el
fin de poder descomprimir dichas regiones. Para ello emplearemos
el objeto de la clase \emph{Kdu\_codestream}. Al haber restringido el
área a la cual tenemos acceso, mediante el método
\emph{Apply\_input\_restrictions()}, la clase \emph{Kdu\_codestream} no nos
permitirá acceder a ninguna partición (\emph{tile}, componente,
resolución, precinto), que no forme parte de dicha área.

El algoritmo para determinar que
paquetes serían los necesarios sería similar al siguiente:

\begin{verbatim}
Kdu_tile tile = codestream.Open_tile(new Kdu_coords(0, 0));

para l desde 0 hasta (numero_de_capas-1) {
  para c desde 0 hasta (tile.Get_num_components()-1) {
    Kdu_tile_comp tile_comp = tile.Access_component(c);
    para r desde 0 hasta (tile_comp.Get_num_resolutions()-1) {
      Kdu_resolution res = tile_comp.Access_resolution(r);
      Kdu_dims precintos = res.Get_valid_precincts();

      int pxInicio = precintos.Access_pos().Get_x();
      int pyInicio = precintos.Access_pos().Get_y();
      int pxFin    = precintos.Access_pos().Get_x() +
                     precintos.Access_size().Get_x() - 1;
      int pyFin    = precintos.Access_pos().Get_x() +
                     precintos.Access_size().Get_x() - 1;

      para py = pyInicio hasta pyFin
        para px = pxInicio hasta pxFin {

          // Incluir la informacion del
          // paquete (l, c, r, px, py)
        }
    }
  }
}

tile.Close();
\end{verbatim}

En primer lugar, abrimos el único \emph{tile} que debe existir en la imagen,
con el método \emph{Open\_tile()} de la clase \emph{Kdu\_codestream}. A continuación
vamos recorriendo todas las componentes, y todas las resoluciones de
cada componente. En cada resolución de cada componente, solicitamos
los precintos a los cuales tenemos acceso, mediante el método
\emph{Get\_valid\_precincts()}. Este devuelve un objeto de la clase \emph{Kdu\_dims},
el cual almacena el índice del precinto situado en la esquina superior
izquierda del área permitida en el objeto de la clase \emph{Kdu\_coords} que
devuelve el método \emph{Access\_pos()}. El número de precintos horizontales
y verticales permitidos viene indicado en el objeto de la clase \emph{Kdu\_coords}
devuelto por el método \emph{Access\_size()}. Cada precinto \emph{(px, py)}, de
una resolución \emph{r}, de la componente \emph{c}, de la capa \emph{l}, define un paquete
que es necesario para la descompresión, y cuya información
se debe de incluir en la lista de paquetes del sistema de comunicación.
Como se puede observar, la información de los paquetes se va incluyendo
por capas de calidad.

La información de un paquete requerido se almacena en la lista
de paquetes del sistema de comunicación en un objeto de la
clase \emph{PacketData}. Esta clase contiene tres variables miembro: \emph{precinctID},
identificador de precinto al cual pertenece el paquete, \emph{offset}, posición
del comienzo del paquete dentro del archivo, y \emph{length}, longitud en bytes
del paquete.

El identificador del precinto es un concepto definido en la biblioteca
Kakadu, necesario para referenciar el \emph{data-bin} asociado a un precinto.
Es decir, para utilizar método \emph{Add\_to\_databin()}, anteriormente mencionado,
para añadir información al \emph{data-bin} asociado a un precinto determinado,
necesitaremos indicar el identificador de dicho precinto en el parámetro
\emph{databin\_id}. La forma de calcular dicho identificador no será necesario
conocerla, ya que el método \emph{Get\_precinct\_id()} de la clase \emph{Kdu\_resolution}
nos devuelve el identificador de un precinto, pasándole la posición
del mismo (en el algoritmo anterior, los valores \emph{px} y \emph{py}).

La posición y longitud de cada paquete las podremos obtener gracias a
las referencias de los marcadores PLT que hemos almacenado en la
apertura de la imagen. La clase \emph{PLTRecord} es la que ha
almacenado esta información, y será la que nos permita conocer la
información de un determinado paquete.  El método \emph{int
getOffset(int res, int layer, int comp, int px, int py)} de la clase
\emph{PLTRecord} nos devuelve el \emph{offset} de un paquete,
identificado por la resolución \emph{res}, la capa \emph{layer}, el
componente \emph{comp}, y la posición del precinto al que pertenece
\emph{(px, py)}. El método \emph{int getLength(int res, int layer, int
comp, int px, int py)} nos devuelve la longitud de paquete.  Si
llamamos a alguno de los dos métodos anteriores, y la clase
\emph{PLTRecord} no posee la información correspondiente, será porque
todavía no ha leído los marcadores correspondientes a la resolución
indicada. Si no los ha leído, construirá un objeto \emph{HTTPReader} y
los solicitará al servidor.

Al incluir la información de los paquetes requeridos en la lista del sistema
de comunicación, se debe consultar la \emph{cache} para ver qué paquetes se han
leído ya. El \emph{data-bin} de un precinto incluye todos los paquetes asociados
al precinto, ordenados por capa de calidad. Sabiendo esto, obteniendo la
longitud del \emph{data-bin} asociado a un precinto determinado, podremos saber
qué paquetes están ya incluidos, con el fin de no solicitarlos de nuevo
al servidor.

Una vez concluidas todas estas etapas, el sistema de gestión de imagen pondrá
en marcha el sistema de descompresión y el sistema de comunicación.

\subsection{Lectura de los paquetes}

La lectura de los paquetes indicados por el sistema de gestión de imagen la realiza el sistema
de comunicación. Este sistema, identificado por la clase \emph{JP2Reader}, crea un objeto de la
clase \emph{HTTPReader}, y dos hilos de ejecución, uno para la lectura y otro para la escritura.

El objeto de la clase \emph{HTTPReader} nos permitirá comunicarnos con el servidor empleando
el protocolo HTTP/1.1. Este clase nos permite leer \emph{byte-ranges} de un determinado
archivo y admite mensajes ``\emph{chunkeados}'' del servidor. El constructor de la clase
acepta como único parámetro una URL del recurso sobre el cual interaccionar, por lo que,
para cada recurso o archivo remoto, habrá que crear un objeto de la clase \emph{HTTPReader} diferente.
Los métodos principales de esta clase son los siguientes:

\begin{itemize}
\item \emph{void addRange(int offset, int length)}: Con esta función añadimos un \emph{byte-range} para ser
incluido en la próxima petición HTTP que se haga con el método \emph{request()}. Este \emph{byte-range} se almacena
en una lista interna de la clase.

\item \emph{byte[] readData(int numBytes)}: Lee un número de bytes igual a \emph{numBytes}. El hilo que llama
a este método se queda bloqueado hasta que llegue esa cantidad de bytes del servidor o se
produzca algún error, o bien en forma de excepción o bien en forma de respuesta del servidor.
El número de bytes indicado no incluye ninguna cabecera, y puede incluir además varios mensajes
de respuesta consecutivos. Esto quiere decir que todas las respuestas del servidor se leerán
secuencialmente, siendo transparente el hecho de poder existir cabeceras o mensajes ``\emph{chunkeados}''.
Para que haya datos para leer, se habrá tenido antes que llamar al método \emph{request()} al menos una
vez.

\item \emph{void request()}: Realiza una petición al servidor HTTP incluyendo todos los \emph{byte-ranges} que
se hayan definido anteriormente llamando al método \emph{addRange()}. Este método simplemente realiza la
petición, no espera a la respuesta, que será el cometido del método \emph{readData()}. Al llamar a éste método
se vacía la lista interna de \emph{byte-ranges}.

\item \emph{byte[] read(int numBytes)}: Este método ya se ha visto anteriormente y simplemente llama
en primer lugar al método \emph{addRange()} para especificar el \emph{byte-range} necesario, en función de la posición actual,
después llama al método \emph{request(}) para realizar la petición y finalmente al método \emph{readData()} para
esperar la respuesta. Permite realizar lecturas secuenciales debido a que la clase \emph{HTTPReader} mantiene
un puntero para el recurso asociado. Este puntero es modificado por este método, pero no es
modificado por el método \emph{readData()}.
\end{itemize}

Al tener dividida la clase \emph{HTTPReader} la petición de la lectura, podemos hacer \emph{pipelining} en el
protocolo HTTP/1.1. Por ello se crean dos hilos de ejecución, el de escritura, encargado de realizar
las peticiones HTTP al servidor, y el de lectura, que se encarga de leer los mensajes de respuesta.
Ambos hilos emplean un mismo objeto de la clase \emph{HTTPReader} para comunicarse con el servidor. El hilo
de escritura hará uso de los métodos \emph{addRange()} y \emph{request()}, mientras que el hilo de lectura hará
uso del método \emph{readData()}.

El hilo de escritura va leyendo los objetos de la clase \emph{PacketData} almacenados en la lista de la clase
\emph{PacketsList}, lista en la cual deposita el sistema de gestión de imagen los paquetes que necesita. Como
vimos, la clase \emph{PacketData} almacena el \emph{offset} y la longitud (\emph{length}) de cada paquete. El hilo de
escritura juntará en un mismo byte-range aquellos paquetes que sean contiguos, haciendo que, al haber
impuesto la progresión RLCP al \emph{codestream} de las imágenes, pida los paquetes de los precintos línea
a línea. Es necesario limitar el número de \emph{byte-ranges} a incluir en una misma petición HTTP, ya que
los servidores pueden tener limitada la longitud de cada cabecera (recordemos que los \emph{byte-ranges} se
especifican en HTTP mediante una cabecera ASCII). En la presente aplicación se ha limitado a 20 el número
de \emph{byte-ranges} por petición. El hilo de escritura finalizará cuando acabe de realizar todas las peticiones
necesarias, conforme a lo incluido en la lista de la clase \emph{PacketsList}, o cuando se produzca algún error.
Es necesario mencionar que los paquetes de longitud igual a 1 no se solicitarán al servidor, debido
a que en el contexto del \emph{codestream} JPEG2000, un paquete de 1 byte de longitud, es un paquete ``vacío'', teniendo
ese byte un valor constante.

El hilo de lectura irá leyendo los paquetes especificados en la lista de la clase \emph{PacketsList}. La
lectura, gracias a la clase \emph{HTTPReader}, no tendrá en cuenta si los paquetes se han incluido
en la misma petición o en peticiones diferentes, o de si la respuesta ha sido \emph{chunkeada} o no. Al leer
cada paquete, se elimina el objeto \emph{PacketData} asociado de la lista \emph{PacketsList}, y se incluye
la información en la \emph{cache} (\emph{JP2Cache}) mantenida por el sistema de gestión de imagen. Como vimos
para la apertura de la imagen, para añadir la información del paquete emplearemos el método \emph{Add\_to\_databin()}.
Se debe tener en cuenta no leer los paquetes de un byte de tamaño, ya que no se solicitaron al servidor.
No obstante es preciso incluirlos en la \emph{cache}. Estos paquetes son bytes cuyo primer bit está a cero (el resto
de los bits pueden tener cualquier valor). El hilo de lectura finalizará cuando acabe de leer todos los
objetos \emph{PacketData}, o cuando se produzca algún error. Es el hilo encargado de cerrar la comunicación con
el servidor, tanto si ha acabado o no el hilo de escritura.

Cuando el sistema de gestión de imagen requiere detener el sistema de comunicación, o bien porque se
ha abierto una imagen nueva o bien porque se ha cambiado de ROI, se llamará al método \emph{stop()} de la clase
\emph{JP2Reader}. Este método elimina todos los objetos de la clase \emph{PacketData} de la lista de la clase \emph{PacketsList},
y cierra la conexión con el servidor, si aún está abierta, es decir, llamada al método \emph{close()} del
objeto de la clase \emph{HTTPReader}. Esto provoca que el hilo que aún permanezca en ejecución finalice debido
a una excepción. Se dice que el sistema de comunicación ha finalizado, cuando los dos hilos de ejecución
están parados.

Es imprescindible para el correcto uso de la \emph{cache}, que el hilo de lectura almacene los paquetes
completos, que no se pueda detener su ejecución quedándose en la \emph{cache} almacenada una porción de
un determinado paquete. Esto es garantizado al ser la llamada que realiza el hilo de lectura al método
\emph{readData()} de la clase \emph{HTTPReader} una llamada bloqueante.

El orden que siguen tanto el hilo de escritura como el de lectura para recorrer la lista de la clase \emph{PacketsList}
es el mismo.

\subsection{Descompresión}

El sistema de descompresión está representado por la clase \emph{JP2Render}. Su misión es descomprimir las regiones
almacenadas en la lista de clase \emph{RegionsList}. Para la descompresión emplea un objeto de la clase \emph{Kdu\_region\_decompressor}.
Esta clase permite descomprimir regiones rectangulares de una imagen.

Para comenzar la descompresión de una región, empleamos el siguiente método de la clase \emph{Kdu\_region\_decompressor}:

\begin{verbatim}
boolean Start(Kdu_codestream codestream,
              Kdu_channel_mapping mapping, int single_component,
              int discard_levels, int max_layers, Kdu_dims region,
              Kdu_coords sampling, boolean precise)
\end{verbatim}

Este método únicamente inicializa el descompresor. Los parámetros que nos
interesan son: \emph{codestream}, que referencia al \emph{codestream} de la imagen; \emph{discard\_levels},
que indica el número de niveles de transformada \emph{wavelet} a descartar (la resolución); y región
que es la región propiamente dicha de la imagen a descomprimir.

Para descomprimir, empleamos el siguiente método:

\begin{verbatim}
boolean Process(int[] buffer, Kdu_coords buffer_origin,
                int row_gap, int suggested_increment,
                int max_region_pixels, Kdu_dims incomplete_region,
                Kdu_dims new_region)
\end{verbatim}

A este método se le pasa, principalmente, el \emph{buffer} donde almacenar la región descomprimida.
A la hora de descomprimir la región pasada al método \emph{Start()}, el método \emph{Process()} tendrá en
cuenta el tamaño del \emph{buffer} pasado (\emph{buffer.length}), de forma que descomprimirá una subregión de la región
indicada haciendo que el tamaño total de los datos descomprimidos sea menor o igual en el tamaño del \emph{buffer}
pasado. Esto obligará a que, en función del \emph{buffer} utilizado, necesitemos más o menos
llamadas sucesivas al método \emph{Process()}. Este método devuelve en el parámetro \emph{incomplete\_region} la
parte de la región indicada inicialmente al método \emph{Start()} que aún queda por descomprimir. En el
parámetro \emph{new\_region} devuelve que subregión ha podido descomprimir, en función del tamaño del \emph{buffer}
pasado. En el caso de que tras la llamada a \emph{Process()} se haya descomprimido ya la totalidad de la región
inicialmente pasada a \emph{Start()}, el método devolverá \emph{false}, y como es lógico, \emph{incomplete\_region}
contendrá una región vacía. En caso contrario, devolverá \emph{true}.

Finalmente, cuando tras sucesivas llamadas al método \emph{Process()}, éste devuelve \emph{false} para indicar
que la descompresión de la región de interés ha finalizado, se deberá llamar al método \emph{Finish()},
para liberar los recursos empleados por el descompresor.

El sistema de descompresión al iniciarse, crea un hilo de ejecución que recorre la lista \emph{RegionsList}
descomprimiendo, con los métodos anteriormente mencionados de la clase \emph{Kdu\_region\_decompressor}, cada
lista almacenada en cada objeto del tipo \emph{Region} de la lista. Como hemos visto, la descompresión
de cada región supone una o más llamadas al método \emph{Process()}. Cada subregión descomprimida tras cada
llamada a dicho método, le es comunicada al sistema de visualización para que éste actualice su
representación en pantalla.

Cuando se termina de descomprimir una región, se comprueba si el sistema de comunicación ha finalizado.
Si ha finalizado, la región es eliminada de la lista. Si el sistema de comunicación no ha finalizado
aún, la región se elimina de la primera posición de la lista y se coloca en la última posición. Esto hace
que el sistema de descompresión permanezca descomprimiendo las regiones solicitadas por el sistema de
gestión de imágenes una y otra vez mientras que el sistema de comunicación no finalice.

\subsection{Creación de la vista en miniatura}

Cuando se pulsa el botón ``Vista en miniatura'', se crea una subventana (\emph{JInternalFrame}) dentro del panel de visualización,
representado por la clase \emph{ImagePanel} (\emph{JDesktopPane}). El sistema de visualización le notifica entonces
al sistema de gestión de imagen que lo que requiere ahora es la visualización en miniatura de la imagen.
El sistema de gestión de imagen asumirá esta petición como una petición normal y corriente en la que la
resolución de la imagen es aquella cuya dimensión sea menor o igual a $200 \times 200$, y la ROI igual al
tamaño completo de la imagen a dicha resolución. El sistema de visualización, cuando éste primero le va
notificando las regiones descomprimidas, las va mostrando, en vez de en el panel principal, en la ventana
dedicada a la vista en miniatura.
En el momento en el que el usuario vuelva a interaccionar con la imagen (cambie la ROI o la resolución),
se detendrá el proceso de creación de la vista en miniatura. Cuando se reinicie este proceso, volviendo
a pulsar sobre el botón ``Vista en miniatura'', se continuará el proceso por donde se dejó.

\section{Interfaz de usuario}

La figura \ref{fig:interfaz} muestra una captura de pantalla de la
aplicación desarrollada, en la que se puede ver la interfaz de
usuario, estando señalados los principales elementos.

\begin{figure}
  \begin{center}
    \resizebox{\textwidth}{!}{\includegraphics{figuras/interfaz}}
    \caption{Interfaz de usuario.}
    \label{fig:interfaz}
  \end{center}
\end{figure}

La aplicación consta únicamente de una ventana redimensionable. La
ventana se divide en cuatro zonas principales: la barra de botones, el
cuadro de dirección, la zona de visualización y la barra de estado.
Estas zonas se sitúan de arriba a abajo, ocupando cada una de ellas
todo el ancho de la ventana.

La barra de botones posee cuatro botones, que son, de izquierda a derecha:

\begin{itemize}
\item \emph{Abrir imagen local}: Aunque la aplicación ha sido
  desarrollada para trabajar principalmente con imágenes remotas, se
  ha incluido la opción de poder visualizar imágenes locales. En este
  caso, la única restricción existente para los archivos de imagen es
  la que impone la biblioteca Kakadu, es decir, se admitirán los
  archivos J2C y JP2.

\item \emph{Refrescar}: Este botón permite volver a cargar la imagen actualmente visualizada desde el inicio.
Su comportamiento es similar al que se produce cuando se abre una imagen, pero en este caso es la misma
imagen. Si situamos el cursor en el cuadro de dirección y pulsamos la tecla INTRO se produce el mismo
efecto.

\item \emph{Zoom}: Estando pulsado este botón, el usuario puede realizar \emph{zooms} en la imagen.

\item \emph{Vista en miniatura:} Al pulsar este botón, se muestra la ventana con la vista en miniatura de la
imagen, si aún no ha sido mostrada.
\end{itemize}

El cuadro de dirección es donde especificamos la URL de la imagen a cargar. Una vez que escribimos la URL de
la imagen deberemos pulsar la tecla INTRO.

La zona de visualización es la zona principal de la aplicación. Es
donde se visualiza la ROI actual, y es donde el usuario puede
interaccionar con la misma. Por defecto, el usuario interacciona con
la zona de visualización para cambiar la posición de la ROI. Esto lo
puede hacer mediante las barras de desplazamiento, existiendo una
vertical y otra horizontal, o bien mediante el cursor del ratón: si
pulsa con el ratón en la imagen y, sin soltar el ratón, desplaza el
puntero, se desplazará la ROI según el movimiento del puntero. Si el
usuario pulsa el botón de \emph{Zoom}, entonces será capaz de cambiar
la resolución que desea de la imagen, mediante el ratón: al pulsar el
botón izquierdo se aumenta una resolución, y al pulsar el botón
derecho disminuye una resolución.  Recordemos que el usuario no se
puede salir del rango de resoluciones que admita la imagen. Al cambiar
de resolución, la ROI tendrá el mismo tamaño, pero se desplazará para
que su centro se sitúe donde ha pulsado en usuario al hacer
\emph{zoom}.

Cuando se pulsa sobre el botón ``Vista en miniatura'', en la zona de visualización aparece una pequeña ventana, donde
aparece la imagen con la que se está trabajando a baja resolución, en concreto, a la resolución en la que la
imagen quepa en un cuadrado de $200 \times 200$. Esta ventana no es redimensionable. Sobre la vista en miniatura se
representa un rectángulo rojo que identificará la posición y tamaño de la ROI actual sobre la imagen completa. El
usuario puede mover dicho rectángulo, arrastrándolo con el ratón, para mover la ROI.

La barra de estado visualiza información de interés, como es el ancho y el alto de la imagen, la escala
a la cual se está trabajando, y si la imagen no es local, la cantidad de bytes que se han recibido hasta el momento.

\section{Evaluación}

Para la evaluación del rendimiento de la aplicación, se ha realizado una comparativa de la misma con
la aplicación \emph{kdu\_show} del paquete de Kakadu, empleando el protocolo JPIP.

Tanto la aplicación desarrollada como \emph{kdu\_show} visualizarán
progresivamente la imagen \emph{boatsip.jp2}, que tiene un tamaño de
$2045\times 2045$, no posee \emph{tiles}, posee un tamaño de precinto
de $128\times 128$ y un tamaño de \emph{code-block} de $64\times
64$. Para el caso de nuestra aplicación, la imagen se ha pasado a
formato J2C, y se le ha impuesto una progresión RLCP y el
particionamiento del \emph{tile} en
\emph{tile-parts}, uno por cada resolución. Ambas aplicaciones
visualizarán la imagen a una escala del 25\%. Se han hecho pruebas con otras imágenes, pero el resultado obtenido
ha sido muy similar.

Se han realizados dos gráficas, una para cada aplicación, en las que
se puede ver el PSNR, en el eje vertical, por \emph{kilobytes}
recibidos, en el eje horizontal. Estas dos gráficas aparecen en la
figura \ref{fig:graficaPSNR}.

La medida PSNR calculada para imágenes con 8 bits/componente, y
expresada en decibelios, se calcula como:
\begin{displaymath}
\mathrm{PSNR[dB]}=10\log\frac{255^2}{\frac{1}{N}
\sum_{i=1}^{N}(s[i]-\widehat{s}[i])^2}
\end{displaymath}
donde $s[i]$ y $\widehat{s}[i]$ denotan a un punto RGB de la imagen
original y reconstruida, respectivamente. $N$ es el número de puntos
de la imagen.

\begin{figure}
  \begin{center}
    \includegraphics[width=\textwidth]{figuras/comparativa.eps}
    \caption[Sistema desarrollado vs. protocolo JPIP (Kakadu)]
    {Comparativa entre el sistema desarrollado y el protocolo JPIP,
      empleando el paquete de Kakadu.}
    \label{fig:graficaPSNR}
  \end{center}
\end{figure}

Como se puede apreciar, \emph{kdu\_show}, empleando el protocolo JPIP,
necesita leer menor cantidad de bytes para mostrar una reconstrucción
aceptable de la imagen, que la aplicación desarrollada en el presente
proyecto. Existe una diferencia media aproximada de $3dB$ en la
distorsión de la imagen, con la misma cantidad de bytes leídos. El
motivo es que al emplear el protocolo HTTP, debemos leer las cabeceras
de cada mensaje, cuya longitud se ha tenido en cuenta al realizar las
gráficas. Las cabeceras en el protocolo JPIP ocupan mucho menos
espacio. No obstante hay que tener en cuenta que el servidor empleado
para el caso del protocolo HTTP tiene limitada la longitud de
cabecera, por lo que el número de \emph{byte-ranges} que podemos
incluir en una misma petición está limitado, en concreto, a
aproximadamente unos 20 \emph{byte-ranges}. Cuantos más
\emph{byte-ranges} permita un servidor HTTP en la misma petición,
tendremos que leer menos cabeceras, al reducirse el número de
peticiones necesarias.  Además el servidor JPIP de Kakadu optimiza el
orden de los paquetes a transmitir, como se verá en el apartado de
posibles mejoras, minimizando la distorsión de la imagen en relación a
los bytes transmitidos. De incluir ésta mejora en la presente
aplicación, su curva se aproximaría a la del sistema JPIP.

\section{Posibles mejoras}

De las posibles mejoras que se le podrían aplicar a la aplicación
desarrollada, las principales serían la de incluir soporte para
archivos JP2 y la secuenciación óptima de paquetes.

\subsection{Soporte para archivos JP2}

Esta no supondría una mejora del rendimiento, pero sí le daría más
flexibilidad a la aplicación. No se ha dado soporte a los archivos JP2
debido a su complejidad estructural, que haría ralentizar el proceso
de lectura.  Sin embargo, es posible que nos encontremos en la
necesidad de tener que incluir información adicional a un archivo de
imagen, como por ejemplo, una paleta de colores, información de
interés relacionada con la imagen, etc. Esto no es posible hacerlo con
los archivos J2C, los cuales tan sólo incluyen un \emph{codestream}
JPEG2000.

La aplicación utiliza la clase \emph{Kdu\_cache} de la biblioteca
Kakadu para implementar el sistema de \emph{cache}. Esta clase está
preparada para trabajar con archivos JP2, de manera que existe un tipo
de \emph{data-bin} para albergar las cajas de un archivo JP2. Estos
\emph{data-bins} son llamados \emph{meta data-bins}. En un mismo
\emph{meta data-bin} es posible albergar más de una caja, tal y como
aparecen en el archivo.

La aplicación debería ir leyendo caja a caja de la imagen,
almacenándola en la \emph{cache}, hasta llegar a la caja del
\emph{codestream}, la cual se leería, una vez saltada la cabecera, de
la misma forma que para un archivo J2C normal y corriente. Es preciso
tener en cuenta que un archivo JP2 puede incluir más de una caja de
\emph{codestream}. En el caso de encontrarse con un archivo con esta
peculiaridad, habría que decidir qué \emph{codestream} visualizar, o
dejar al usuario que lo decidiera. La clase \emph{Kdu\_cache}, por su
parte, soporta perfectamente varios \emph{codestreams}, debido a que a
cada \emph{data-bin} se le asigna una identificador de
\emph{codestream} (salvo en el caso de los \emph{meta data-bins}, que
no están asociados a ningún \emph{codestream}).

En este caso, la inicialización del objeto de la clase
\emph{Kdu\_codestream} no se realizaría pasándole directamente el
objeto de la clase \emph{Kdu\_cache}. Se debería crear un objeto de la
clase \emph{Jp2\_family\_src}, que sería el que se inicializará con la
\emph{cache}, llamando al método \emph{Open()}. Posteriormente se
crearía otro objeto, pero de la clase \emph{Jp2\_source},
inicializándolo con el último objeto creado, también llamando a su
método \emph{Open()}. Sería este objeto el que le pasaríamos al método
\emph{Create()} de la clase \emph{Kdu\_codestream}.

Por lo demás, todo lo desarrollado para la presente aplicación se
mantendría prácticamente igual.

\subsection{Secuenciación óptima de paquetes}

Esta sería una mejora consistiría en emplear una técnica que ya
utiliza el paquete Kakadu para optimizar la transmisión de las
imágenes a través del protocolo JPIP.

Cuando se requiere una ROI determinada a una resolución dada, la
aplicación desarrollada para el presente proyecto solicitaría al
servidor todos los paquetes de todos los precintos relevantes para
dicha ROI de la imagen, desde la mínima resolución hasta la resolución
dada, capa por capa.

Aunque sólo se solicitan los precintos necesarios para la
reconstrucción de la ROI, a menudo de incluyen precintos que
contribuyen a la reconstrucción de regiones de la imagen que no
pertenecen a la ROI solicitada.

El paquete de Kakadu realiza dos cosas con el fin de optimizar la
secuenciación de paquetes, a la hora de ser enviados por el servidor
JPIP al cliente, y ordenarlos así por relevancia en relación a la
reconstrucción de la ROI. La primera de ellas es que, para cada capa
de calidad, los precintos son ordenados por su relevancia, en vez de
por su resolución, como hace nuestra aplicación. La relevancia,
$\rho_{i}$, del precinto $i$, es calculado con la siguiente fórmula:

\begin{equation*}
\rho_{i} = \frac{\|A_{i} \cap W_{R_{i}} \|}{\|A_{i}\|}
\end{equation*}

donde $A_{i}$ es la región espacial ocupada por el precinto $i$ en su
\emph{tile}-componente-resolución, $R_{i}$, y $W_{R_{i}}$ es la
proyección de las muestras de las subbandas que pertenecen a la
resolución $R_{i}$, y que contribuyen a la reconstrucción de la ROI en
dicha resolución.

El servidor JPIP de Kakadu ordena los paquetes de los precintos a
enviar al cliente, basándose en la fórmula anterior. Los paquetes
siguen siendo enviados por capa, pero para cada capa, el orden de los
paquetes ya no es por resolución.

La segunda fase de la optimización de la secuenciación de los paquetes
sólo es posible si el archivo de imagen contiene almacenada la
información de los umbrales de la pendiente de distorsión que fueron
usados por el compresor para generar las capas del \emph{codestream}.
El compresor de Kakadu permite almacenar esta información en una
marcador COM especial, incluido en la cabecera principal. Si el
archivo de imagen posee dicha información, el servidor es capaz de
ordenar los paquetes, ya no sólo por su relevancia en la misma capa,
sino entre las distintas capas.

Esta segunda fase de optimización se basa en asumir que el impacto de
un paquete de un precinto $i$ en la distorsión MSE (\emph{Mean Squared
Error}) de la ROI es igual a $\rho_{i}$ veces el impacto que tiene
este mismo paquete en el MSE de la imagen completa. Las contribuciones
de cada \emph{code-block} incluidas en la capa $\lambda$ tienen unas
pendientes de distorsión no más pequeñas que $s_{\lambda}$, donde
$s_{\lambda}$ sería uno de los valores almacenados por el compresor en
el marcador COM especial mencionado anteriormente. Para modificar las
pendientes de distorsión de los \emph{code-blocks} para que reflejen
la relevancia que tienen con respecto a la ROI sólo necesitaríamos
multiplicarlos por $\rho_{i}$. Esto permite al servidor, a la hora de
transmitir un precinto, empezar por los paquetes de las capas de mayor
relevancia.

La figura \ref{fig:PSNR} muestra una gráfica donde se puede comprobar
el resultado de emplear la secuenciación óptima de paquetes, respecto
a emplear la secuenciación tradicional.

\begin{figure}%[htb]
  \begin{center}
    \includegraphics[width=\textwidth]{figuras/PSNR}
    \caption{Impacto de la secuenciación óptima de paquetes.}
    \label{fig:PSNR}
  \end{center}
\end{figure}

Es evidente la mejora que supone esta técnica en el rendimiento del cliente,
de forma que para el mismo número de bytes leídos, la distorsión de la
imagen mostrada es menor.

El incluir esta técnica en el sistema propuesto no sería sencillo, ya
que en Kakadu es el servidor el que realiza toda la ordenación de
paquetes, debido a que tiene acceso a la imagen completa. En nuestro
caso, el cliente no tiene acceso a la imagen completa, por lo que no
puede calcular $\rho_{i}$. Esta información se debería incluir, al
igual que hace Kakadu para la segunda parte de su optimización, en un
marcador COM especial, el cual debería ser leído en el proceso de
apertura de la imagen, antes de solicitar ningún paquete. Para ello
sería necesario implementar una pequeña aplicación que incluyera en un
archivo J2C dicho marcador.
