[gAF] Implementación del soporte de los formatos de archivo

En el anterior post, comentábamos que, actualmente, el software ActiLife nos permite recuperar la información en tres formatos de archivos diferentes: Texto plano (.dat), Valores separados por comas (.csv) y base de datos SQLite (.agd).

En gAF, una vez que un archivo es leído, su información es almacenada en una clase llamada GafFile. Esta clase contiene la información necesaria para realizar diferentes tipos de análisis y proceso de la información contenida en los diferentes ficheros.

Por lo tanto, necesitamos un mecanismo que nos permita, en función de la extensión del archivo, leer la información de los diferentes formatos y almacenarla en una instancia de GafFile.

Abstract Factory Pattern al rescate

Una buena opción para resolver esta situación es utilizando el patrón de factoría abstracta. Básicamente, tenemos dos interfaces que definen los contratos de las factorías y de los lectores de archivos. Más o menos así:

    interface IGafFileReaderFactory
    {
        string SupportedFormat { get; }

        IGafFileReader CreateReader();
    }
    interface IGafFileReader
    {
        GafFileHeader GetDatInfo(string path);

        GafFile GetDat(string path, bool useThirdAxis);
    }

De esta forma, cada factoría es responsable de instanciar un GafFileReader en función de si el formato del archivo a leer coincide con el formato soportado por la factoría. De esta forma, el GafFileReader instanciado para una extensión, conoce perfectamente qué es lo que tiene que hacer con ese archivo para convertirlo a un GafFile.

La implementación de una factoría es trivial. Sólo ha de definir la extensión que conoce y crear el GafFileReader específico para esa extensión.

    class PlainTextFileReaderFactory : IGafFileReaderFactory
    {
        public string SupportedFormat
        {
            get
            {
                return ".dat";
            }
        }

        public IGafFileReader CreateReader()
        {
            return new PlainTextFileReader();
        }
    }

Y por supuesto, la clase PlainTextFileReader, ha de implementar la interfaz IGafFileReader y especializarse en la interpretación de los archivos con la extensión .dat.

Y para dejarlo fácil: Facade Pattern

Para que la API sea sencilla de utilizar para los clientes, es necesario abstraerles del uso de esta factoría abstracta. Para conseguirlo se utiliza una fachada que expone las operaciones que el cliente necesita para obtener un GafFile.

En este caso, como fachada se utiliza una clase estática que ofrece las operaciones necesarias para recuperar un GafFile. Básicamente, ofrece las dos operaciones de la interfaz IGafFileReader, así como algunos métodos auxiliares. Esta clase mantiene una lista privada con las factorías registradas en el sistema (actualmente se cargan desde el archivo de configuración de la aplicación) y, lo que hace es delegar en las factorías registradas la ejecución de las operaciones necesarias.

    public static class GafFileReader
    {
        #region Campos privados

        private static readonly List<IGafFileReaderFactory> factories;

        #endregion

        #region Constructores

        static GafFileReader()
        {
            factories = new List<IGafFileReaderFactory>();

            var configfactories = GafConfigurationManager.GafSection.ReaderFactories
                .OfType<System.Configuration.NameValueConfigurationElement>()
                .Select(configFactory => Type.GetType(configFactory.Value))
                .Select(factoryType => (IGafFileReaderFactory)Activator.CreateInstance(factoryType));

            foreach (var factory in configfactories)
            {
                factories.Add(factory);
            }
        }

        public static IEnumerable<string> RegisteredFormats
        {
            get
            {
                return factories.Select(f => f.SupportedFormat);
            }
        }

        #endregion

        #region Métodos

        #region Métodos públicos

        public static GafFileHeader GetDatInfo(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentException(Resources.GafFileReader_El_valor_no_puede_ser_nulo_ni_una_cadena_vacia, "path");
            }

            var reader = GetParser(path);

            return reader.GetDatInfo(path);
        }

        public static GafFile GetDat(string path, bool useThirdAxix)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentException(Resources.GafFileReader_El_valor_no_puede_ser_nulo_ni_una_cadena_vacia, "path");
            }

            var reader = GetParser(path);

            return reader.GetDat(path, useThirdAxix);
        }

        #endregion

        #region Métodos privados

        private static IGafFileReader GetParser(string path)
        {
            string extension = Path.GetExtension(path);

            if (string.IsNullOrWhiteSpace(extension))
            {
                throw new ArgumentException(string.Format(Resources.GafFileReader_No_se_encuentra_la_extension_del_archivo, path), "path");
            }

            extension = extension.ToLower();

            var factory = factories.FirstOrDefault(x => x.SupportedFormat.Contains(extension));

            if (factory == null)
            {
                throw new InvalidOperationException(string.Format(
                        CultureInfo.CurrentCulture,
                        "El archivo que se intenta analizar tiene una extensión desconocida: [{0}].",
                        extension));
            }

            return factory.CreateReader();
        }

        #endregion

        #endregion
    }

De esta forma cualquier cliente, para recuperar un GafFile de un archivo, lo único que tiene que hacer es llamar al método estático de la fachada de lectura de archivos sin preocuparse de qué tipo de archivo está intentando leer.

¡Hasta la próxima!

gAF: Procesando los datos de los acelerómetros

gAF surge de la necesidad de proporcionar una ayuda en el proceso y análisis de los datos recogidos en las diferentes líneas de investigación surgidas en el seno del Grupo de Investigación de Educación Física y Promoción de la Actividad Física d ela Universidad de Zaragoza, concretamente en la línea de investigación relacionada con los niveles de actividad física de los alumnos de Educación Secundaria Obligatoria en la ciudad de Huesca.

Para evaluar los niveles de actividad física, se utilizan acelerómetros de diferentes modelos de la firma Actigraph. Estos acelerómetros son pequeños aparatos programables que los usuarios llevan sujetos al cuerpo, y que registran las variaciones de aceleración del sujeto en cada uno de los tres ejes. Esta información proporciona una medición indirecta del nivel de actividad del sujeto, así como de la intensidad a la que se desarrolla.

La gran cantidad de sujetos necesarios para que las muestras sean significativas y, el hecho de que, de forma habitual, se realicen estudios longitudinales que requieren de la recogida de información de los mismos sujetos a lo largo del tiempo, hace que la cantidad de información generada sea importante.

Así pues, gAF surje como una herramienta que añade funcionalidad en el proceso de análisis de la información recogida en los trabajos de campo.

Formatos de archivos

Para programar y recuperar la información recogida por los acelerómetros, Actigraph dispone de un software específico: ActiLife.

Los formatos de salida generados por ActiFife han ido evolucionando en el tiempo. Al principio, se descargaba toda la información del acelerómetro a un archivo de texto plano con extensión .dat que consta de dos partes bien diferenciadas. imageLas 10 primeras líneas, almacenan los metadatos del archivo, donde se almacena información como la fecha y hora del inicio de la recogida de datos, el epoch time (define cada cuanto se graba un nuevo registro de cambios de aceleración), el modo de grabación, y otra información importante. El resto de líneas del archivo, recogen los counts (unidades en las que se mide la variación de la aceleración) para cada epoch time programado. Este archivo era dificil de interpretar y siempre muy propenso a errores. El más destacado tiene relación con el formato de las fechas en el que se registraba el inicio de la grabación (dependiendo de diferentes versiones del software ActiLife, este formato era siempre el formato Inglés, en otras el formato de fecha del idioma de la máquina en la que era descargado, vamos, un lío).

Vistas las limitaciones del formato anterior, posteriormente se pudo generar también un archivo con la extensión .csv (Comma Separated Values). En este formato, se mantenían imagelas 10 líneas de la cabecera, pero la información de counts ocupaba una línea por cada epoch time. Desde luego, tiene ventajas con respecto al anterior, ya que es mucho más fácil identificar los valores de cada periodo, pero además, al tratarse de un formato estandar, se puede consultar utilizando conexiones de datos ODBC, lo que facilita de forma notable el trabajo con el.

Pero este formato sigue teniendo bastantes limitaciones, entre ellas, la lentitud de las conexiones y consultas. Por ello, la última novedad es la descarga de la información en el formato .agd, que no es otra cosa que una base de datos en formato SQLite que contiene las tablas mínimas para almacenar toda la información. Evidentemente, este es el formato más rápido y flexible y, desde luego, el que más sencillo y menos propenso a errores en la grabación e interpretación.

gAF soporta el análisis de los tres tipos de archivos, lo que le da una gran versatilidad a la hora de generar con archivos de datos recogidos desde el software ActiLife en cualquiera de sus versiones y formatos.

En posteriores entradas iremos haciendo un repaso de las principales funcionalidades de la aplicación, y por supuesto, ¡cualquier sugerencia será bienvenida!