3 - Acceso Standard

3.1.1 - Image Iterators.

Iteradores

    Los iteradores son objetos ampliamente usados en esta y otras bibliotecas ( en la STL por ejemplo ), dado que son clases que permiten recorrer en forma estandar estructuras muy distintas.
La idea básica consiste en que si se tienen estructuras que contienen elementos, un array un vector, una lista, una tabla hash por ejemplo, donde se puede definir cierta precedencia, no es necesarios saber como
se organiza internamente la estructura para recorrerla. Si pensamos en un iterador como un puntero a algún elemento, basta con saber como recuperar el elemneto al que estamos apuntando y como movenos al siguiente.

Por ejemplo si tenemos tres clases bien diferentes, un vector , una lista y un arbol, todas derivadas de
una clase base, que llamamos contenedor.


Figura 3.1.2

Para cada una de estas estructuras, definimos un iterador, con lo que tendremos un nuevo arbol de clases como se  muestra en la figura 3.1.3


Figura 3.1.3

Es necesaria la clase base para definir una interfáz estandard. Las cuatro funciones que aparecen en los iteradores son las fundamentales, las más típicas.
           First ( ) -  posiciona al iterador en el primer lugar
           Next ( ) - mueve el iterador al siguiente lugar
           GetCurrent( ) - devuelve el elemento actual
           IsDone( ) - indica si se llegó al final del contenedor.
Con estas funciones se puede hacer, con un simple bucle for, un recorrido por la estructura.

            for ( iterator.First( )  ;  ! iterator.IsDone( ) ;  iterator.Next( ) )
                {
                      elemento = iterator.GetCurrent( ) ;
                                         ...
                   // se hace algo sobre el elemento
                 }

En la clase base las funciones son virtuales puras y se definen adecuadamente en cada clase concreta.
Un iterador de array conoce el formato del array, con lo que sabe como recorrerlo, pero el usuario
solo necesita conocer las funciones del iterador para recorrer la estructura, sin importar cual es.

Si usamos un Factory Method para crear los iteradores, es decir le agregamos a los contenedores una
función para que cree su correspondiente iterador, podremos hacer muchos algoritmos genéricos, sin
importar de que contenedor se trata.
Si le agregamos a las clase Contenedor una función :

                         ContenedorIterator *  CreateIterator( )

Virtual en esta clase pero implementada en Array, Lista y Arbol.
Crea un iterador y devuelve una referencia al mismo. Notese que es necesario devolver una referencia
( un puntero ) ya que en realidad se crea un iterador concreto ( ArrayIterator, ListaIterator o ArbolIterator) pero para poder hacerlo polimorficamente hay que devolver un puntero a la clase base.

De forma que podremos hacer algoritmos que reciban punteros a la clase base y la recorran usando un iterador. Los algoritmos serán de la forma :

                Algoritmo ( Contenedor *  ptr_contenedor )   // le paso un puntero a contenedor
                {
                     ContenedorIterator * ptr_iterator ;
                     ptr_iterator = ptr_contenedor->CreateIterator( ) ;  // Creo un iterador

                        for ( ptr_iterator->First( )  ;  ! ptr_iterator->IsDone( ) ;  ptr_iterator->Next( ) )
                          {
                               elemento = ptr_iterator->GetCurrent( ) ;
                                          ...
                            // se hace algo sobre el elemento
                          }
                 }

Cuando se quiere usar este algoritmo para una Lista por ejemplo, debemos crear la lista como :

            Contenedor *  ptr_lista;
            ptr_lista = new Lista ( ... );

La creo con un puntero a la clase base , pero instancio una clase concreta, así puedo hacer :

            Algoritmo ( ptr_lista );

Este tipo de algoritmo es totalmente independiente del contenedor, y se verá mucho en la biblioteca, con algunas complicaciones adicionales que se iran explicando.

Iteradores de Imágenes

    Los iteradores para imagenes siguen la filosofía descripta anteriromente, donde la estructura de contenedores es la de las implementaciones de imágenes, que se muestra en la figura 1.2.1


Figura 2.2.1

En paralelo se tendrá una estructura de iteradores como se indica en la figura 3.1.1


Figura 3.1.1

Cada uno de los iteradores del nivel más bajo está asociado a una de las implementaciones concretas.
Por ejemplo bicotiImageIterator2DArray< PixelType > es un iterador para la clase bicotiImageImplementation2DArray< PixelType >
Las implementaciones de imágenes tienen funciones para crear iteradores ( Factory Method ).
Se crea un iterador concreto y se devuelve un puntero a la clase base.
Con lo que si tenemos un puntero a una imagen ptr_image, podemos crear un iterador como :

        bicotiImageIterator< PixelType > *  ptr_iterator;
        ptr_iterator = ptr_image->CreateIterator( );

y podemos usar la interfáz estándar del iterador para recorrer la imagen, sin importar de que dimensión o tipo se trata. Lo que si es necesario saber es el tipo del pixel, ya que como la imagen es template del pixel, el iterador también lo será.

La relación Imagen - Iterador es la que se muestra en la figura 3.1.4


Figura 3.1.4

El iterador mantiene una referencia ( un puntero ) a la imagen sobre la cual "itera". Por otro lado, la imagen crea al iterador y luego pierde referencia a el.
Un iterador solo puede tener referencia a una imagen, pero una imagen puede tener más de un iterador "iterando" sobre ella , inclusive pueden estar en puntos diferentes. Toda la información referente a la posición se almacena en el iterador, con lo que dos iteradores pueden moverse en forma totalmente independiente sobre una misma imagen.

Recorrido

    Las imágenes son estructuras multidimensionales ( 2D , 3D , etc ), con lo que el sentido del recorrido no es tan obvio como en una estructura lineal como un array por ejemplo.
En el caso de una imagen 2D por ejemplo la de la figura 3.1.10


Figura 3.1.10

El iterador está ubicado en el punto de coordenadas ( x_0 = 4 , x_1=1 ). La función Next( ) puede definirse de dos formas :
                 Por filas : Incrementando x_0
                 Por columnas : Incrementando x_1
Por este motivo, en los iteradores, definimos un operador ++ ( el equivalente al Next( ) ) que se moverá en la dirección predeterminada y funciones que nos dejen movernos en otros sentidos.

Por ejemplo en 2D.
         X0Increment( ) - aumenta la columna.
         X1Increment( ) - aumenta la fila.
         X0Decrement( ) - disminuye la columna.
         X1Decrement( ) - disminuye la fila.

La dirección predeterminada depende de la estrategia de borde que se elija. Ver estrategias de borde.

La forma típica de recorrer una imagen es:

        bicotiImageIterator< PixelType > *  ptr_iterator;
        ptr_iterator = ptr_image->CreateIterator( );  // Creo el iterador

         for ( ptr_iterator->First() ; ! (ptr_iterator->IsDone()) ; ( * ptr_iterator )++ )
                pixel = * * ptr_iterator ;

Este tipo de loops se verá mucho en los algoritmos de la biblioteca.

Se pueden ver todos los detalles sobre las funciones en :
       bicotiImageIterator
           bicotiImageIterator2D
           bicotiImageIterator2DArray
           bicotiImageIterator2DSparse
       bicotiImageImplementation3D
            bicotiImageIterator3DArray
            bicotiImageIterator3DLinear