Trabajando con imágenes de gran tamaño en JavaScript

eye-test-710x393Una de las líneas de investigación de CETA-Ciemat es el procesamiento de mamografías para el soporte al diagnóstico del cáncer de mama. Hasta hace unos años toda la producción software surgida del proyecto ha sido en forma de aplicaciones nativas, pero llevamos ya unos meses portando nuestra tecnología al navegador.

El mayor reto de esta migración es llevar nuestro sistema de análisis de mamografías a la web, ya que requiere el renderizado de imágenes de gran tamaño en cuanto al desarrollo web se refiere (hablamos de imágenes de 6,5 MB de media y dimensión 3000 x 4000 píxeles aproximadamente). Estas imágenes no sólo tienen que mostrarse sino también deben ser manipuladas en tiempo real. Hace unos años esto no era posible sin utilizar herramientas de terceros como Adobe Flash pero, como contamos en nuestro anterior artículo, hay dos nuevos elementos a tener en cuenta: la etiqueta <canvas> y WebGL, los cuales han abierto un mundo de posibilidades.

En nuestro caso hemos estado realizando pequeños prototipos que implementan parte de la funcionalidad de nuestra herramienta para diagnosticar mamografías, en concreto las siguientes operaciones:

  • Panning.
  • Zooming (con rueda del ratón).
  • Modificación del brillo.
  • Operaciones de convolución (ventana 3 x 3).
  • Dibujado de un polígono para la selección de la región de interés de una mamografía.

No todos los prototipos desarrollados implementan todas estas funcionalidades, ya que algunas librerías las hemos descartado antes de terminar la implementación. Del listado del artículo anterior sólo hemos completado el prototipo con dos herramientas: FabricJS y PixiJS.

Las primeras versiones estuvieron centradas en FabricJS por ser la librería a priori más atractiva, con una API muy conveniente para construir el tipo de aplicación que tenemos en mente y una documentación bastante detallada. Como principal ventaja podríamos destacar que de todas las librerías es la más cómoda para trabajar, pero como contrapunto no rinde según lo esperado, quedándose corta en la renderización de imágenes sin WebGL y demorándose demasiado en refrescar la imagen tras la ejecución de una operación sobre éstas (por ejemplo tras un filtrado).

logo

Por ejemplo, una operación de convolución con una matriz 3 x 3 sobre una de nuestras imágenes tarda aproximadamente, desde que se aplica hasta que se renderiza, unos 2,9 s, lo cual resulta excesivo para el tipo de experiencia de usuario que buscamos. Cabe destacar que existen librerías que permiten aplicar filtros usando WebGL (como glx.js o WebGLImageFilter), y su integración con FabricJS es sencilla, pero aún así el rendimiento sigue siendo insuficiente (2,6 s de media utilizando WebGLImageFilter). Estos datos empeoran cuantos más filtros se apliquen, ya que se aplican de nuevo cuando se refresca la imagen (FabricJS los aplica siguiendo el patrón de diseño Command).

Por eso mismo empezamos a estudiar librerías que funcionasen sobre WebGL, en concreto PixiJS. Este tipo de frameworks funcionan de otra manera, más parecida a como lo hacen los motores gráficos: en este caso la medida a tener en cuenta son los frames por segundo (fps), ya que todo se renderiza mediante continuas llamadas a una función que se encarga de refrescar la vista (a efectos prácticos esta función es como si estuviese dentro de un bucle infinito).

PixiJS requiere algo de trabajo extra para algunas cosas que FabricJS trae implementadas por defecto (por ejemplo, el panning), pero también es una librería muy completa. De hecho, hemos podido reimplementar todo el prototipo con PixiJS fácilmente. Adicionalmente, nos hemos encontrado una mejora radical en el rendimiento de la plataforma, logrando tasas de 60 fps en nuestro entorno de desarrollo habitual (Ubuntu 14.04, la última versión estable de Chrome y una tarjeta gráfica NVidia GeForce GTX 560 Ti), con un uso normal de los filtros (PixiJS sigue el mismo patrón que FabricJS a la hora de gestionar los filtros).

60fps estables con PixiJS

 

Visto esto, decidimos llevar el prototipo más allá y manipular 4 mamografías a la vez (que es el caso habitual en las aplicaciones para visualizar mamografías: dos mamografías por mama, una “desde arriba” o Cranio-Caudal y otra “de lado” u Oblicuo-Lateral). En este caso nos encontramos con un rendimiento similar en nuestro equipo de desarrollo habitual, pero notándose el impacto en otros navegadores y dispositivos:

  • En el mismo ordenador, en Firefox 40 no se alcanzan tasas mayores que 30 fps.
  • En un Macbook Air con gráfica Intel HD Graphics 6000, en ninguno de los tres navegadores probados (Chrome 40, Firefox 45 y Safari 8) se superan los 60 fps.
  • En un móvil Nexus 5, el rendimiento medio es de unos 20 fps. En un iPhone 6 éste sube a unos 25 fps.
  • En ordenadores con gráficos más modestos y en dispositivos móviles más antiguos (Nexus 4), se puede llegar a cotas de unos 10 fps.

En todos los casos al reducir el número de imágenes a 2 (la disposición habitual que usa un radiólogo a la hora de detectar posibles lesiones en una mama), casi en todas las pruebas se alcanzan los 60 fps (no siempre estables), salvo en los dispositivos más modestos que aún así llegan a 20-30 fps. En los dispositivos menos potentes la acumulación de operaciones (por ejemplo, la ejecución de una convolución 3 x 2 10 veces) suele implicar una bajada de 10 fps.

Con más imágenes y filtros la cosa se complica.

Con más imágenes y filtros la cosa se complica.

Está claro que WebGL (y PixiJS en concreto) es la tecnología que nos ofrece la posibilidad de llevar a la web el tipo de aplicación que queremos implementar, pero aún así el futuro más inmediato presenta una serie de retos que aún tenemos que resolver:

  • Preparar un fallback para los navegadores que no soporten WebGL. PixiJS soluciona gran parte de esto, pero parte de su funcionalidad se pierde (por ejemplo, los filtros sobre las imágenes).
  • Homogeneizar la experiencia de usuario, sea cual sea el navegador y la máquina usada. Siempre va a haber máquinas donde todo vaya mejor y más fluido, pero ahora mismo el prototipo muestra una excesiva diferencia de rendimiento según el dispositivo utilizado.

Aún con estos problemas, el rendimiento obtenido es lo suficientemente prometedor como para que hayamos empezado a trabajar en un visor de mamografías que lleve la experiencia de las aplicaciones médicas de escritorio a la web, con todas las ventajas que esto conlleva.