“””
tl;dr: proyecto generador de ASCII Art, modificado para implementar prácticas de ingeniería de software. Repo.
“””

Una de las áreas de trabajo que me llamó la atención desde que empecé a estudiar Python, es el procesamiento de imágenes. Se puede hacer de todo: revisar histogramas de color, modificar ratios de aspecto, transponer, etc., y todo automatizable con pocas líneas de código.

Con la finalidad de comenzar a implementar buenas prácticas de programación, me decidí a empezar por un sencillo proyecto que trabaja con imágenes y que permite generar textos la emulan. El proyecto original, está basado en una versión publicada por la No Starch Press en su libro Python Playground. La versión original se ocupa de generar el código para su implementación a través de un solo archivo *.py, por lo que decidí darle un giro personal y modularicé las principales funciones e implementé testeos mediante pytest en algunas de ellas. También me ocupé de redactar algunos docstrings para ensayar el uso de formatos estándar.

Los resultados del programa son bastante buenos, y los parámetros opcionales que incluye otorgan un grado de flexibilidad en cuanto a las características que permite administrar al momento de crear un ASCII tales como cantidad de columnas, aspecto (ratio filas/columnas), e incluso la posibilidad de utilizar una escala personalizada de luminosidad de caracteres.

Procedimiento

El procedimiento que sigue el programa para generar el ASCII es bastante lineal, y consta de los siguiente pasos:

  • Apertura de imagen: utiliza librería PIL de Python para abrir la imagen inicial y asignarle una variable para su posterior utilización.
  • Generación de matriz de imagen: la imagen se divide en una matriz de de m x n en la que:
    • m es el número de columnas que tendrá el ASCII. Este número puede ser asignado por el usuario, o en su defecto, se utiliza el valor 80.
    • n es el número de filas de la imagen. Este valor se determina en función de las columnas m y un ratio que representa la relación de tamaño ancho/alto de la fuente a utilizar. El valor por defecto del ratio es 0.43, que representa la relación de aspecto de la fuente Courier; en caso de que se desee, el usuario puede proveer un ratio distinto.
  • Determinación de luminosidad de la matriz: cada celda de la matriz generada en el paso anterior, es convertida a grayscale y se determina el promedio de su luminosidad. Para este paso, se utiliza numpy que lee las variables de imagen abiertas con Image como arrays de dos dimensiones.
  • Generación de carácter: una vez determinada la luminosidad promedio de la celda, se procede a comparar contra la luminosidad promedio de los caracteres definidos en la escala de caracteres (por defecto vienen dos escalas, una con 70 caracteres y otra con 10, cada una ordenada de menos a más luminosidad). Se asigna la letra o signo cuya luminosidad esté más cercana a la generada por la celda evaluada.
  • Escritura de archivo de salida: el programa se ocupa de recorrer la matriz por filas, y cada carácter generado es almacenado en un string que luego es almacenado y posteriormente es escrito en un archivo de texto.

Ingeniería de software

Como comenté en el inicio del post, el proyecto original está contenido en un solo archivo *.py, que funciona sin problemas. Dado que mi interés está en buscar como implementar prácticas de ingeniería de software, introduje los siguientes cambios respecto del original:

  • Modularización de funciones: saqué las dos principales funciones operativas del código y las puse en archivos separados, con la finalidad de cumplir el principio de modularidad y que cada una se encargue de una tarea específica. Las funciones son:
    • getAverageL: recibe las celdas de la matriz creada para la imagen original y retorna el valor promedio de luminosidad.
    • convertImagetoAscii: lee la imagen original, crea la matriz de celdas a ser evaluadas y convertidas, invoca a getAvergeL para utilizar su resultado como parámetro de comparación y determinar el carácter o símbolo correspondiente a esa celda, y lo pega en un string.
  • Unit Testing: implementé tests para evaluar el comportamiento de getAverageL. Los tests implementados motivaron algunos cambios en la función original. Además, el no poder implementar unidades de prueba en convertImagetoAscii me hace pensar que posiblemente dicha función deba ser modularizada y descompuesta en al menos dos funciones que sí puedan ser evaluadas. Los tests fueron implementados con pytest.
  • Otras prácticas: finalmente, opté por ajustar la forma del código al estándar PEP8 a través del sistema de warnings de Spyder. Esto incluye proveer a las funciones de sus respectivos docstrings en formato numpy.

Conclusiones

El trabajo fue bastante entretenido, cosa que es característica de los libros de la No Starch Press.

Este proyecto está fuera del scope de ciencia de datos, sin embargo, me decidí a desarrollarlo (y al menos otros dos en camino en la línea de trabajo con imágenes), porque siento que desde el punto de vista de la práctica, en los proyectos formativos que he desarrollado, esto no había sido un tema relevante. Sinceramente me gustaría que al insertarme en un espacio laboral, mi trabajo fuese algo más que desarrollar un modelo y que luego sea otra la persona quien se ocupe completamente de los aspectos de implementación. Habiendo participado de varios proyectos de diversas naturalezas (educativos, mantenimiento mecánico en minería, gestión tecnológica), puedo afirmar siempre me desempeñé mejor cuando tenía conocimientos más allá de los bordes de mi rol, ya que me permitía generar sinergias con otros miembros de la organización.

Respecto de los aspectos prácticos, pienso que es un éxito el trabajo desarrollado: el código sigue funcionando pese a las modificaciones hechas y se encuentran superados los tests implementados.

Quedan pendientes: modularizar convertImagetoAscii, implementar su testeo y agregar algunas funciones adicionales tales como imprimir un ASCII en negativo.

Finalmente, dejo el repositorio del proyecto ASCII Art. Se reciben con gratitud las propuestas de mejora.

Well done!

Leave a Reply

Your email address will not be published. Required fields are marked *