noviembre 7, 2020
~ 7 MIN
Producción - Front End
< Blog RSSFront End
En el post anterior aprendimos a implementar un servidor web simple con Flask
capaz de recibir imágenes a través de internet, usando una URL
proporcionada por Heroku
. Una vez recibida la imagen, el programa se la da a una red neuronal
que hemos entrenado previamente para clasificar imágenes, la cual la procesa y calcula la probabilidad de que la imagen en cuestión pertenezca a las diferentes clases en las que fue entrenada. Finalmente devolvemos esta predicción. Utilizando el programa CURL
fuimos capaces de probar nuestro servidor a través del terminal utilizando imágenes de muestra.
Sin embargo, para poder hacer que nuestra API
sea lo más accesible posible, lo mejor es implementar una interfaz de usuario para poder interactuar con nuestra red neuronal
desde un smartphone o un ordenador. Para ello vamos a construir una aplicación web, que será el front end
de nuestro sistema.
Puedes aprender más sobre las diferentes tecnologías web que usaremos en nuestros vídeos, dónde también encontrarás otros recursos de aprendizaje.
La aplicación
La red neuronal
que utilizamos en nuestra API
ha sido entrenada para la clasificación de imágenes aéreas, útil para la generación automática de mapas de uso del terreno.
Así pues, nuestro front end
consistirá en un mapa con cobertura global de imágenes aéreas (similar a google maps) en el que un usuario será capaz de seleccionar un área de interés en concreto, enviar la imagen al servidor y visualizar la predicción. Aquí puedes ver el resultado final.
Puedes encontrar todo el código de la aplicacion aquí
Setup del proyecto
Empezamos creando un nuevo repositorio en Github
en el que tendremos nuestro código. Esto no sólo es importante desde el punto de vista de desarrollo de software, si no que también nos permitirá desplegar nuestra aplicación de manera sencilla (y gratuíta) como veremos más adelante.
Aquí tienes un par de vídeos para aprender más sobre Git y Github.
HTML
Empezamos definiendo la estructura de nuestra aplicación en un archivo llamado index.html
. En él definiremos los siguientes elementos:
- El mapa que usaremos para seleccionar el área de interés y extraer las imágenes
- Un panel con:
- Un botón para enviar la imagen al servidor
- Un elemento vacío en el que insertaremos la predicción
- Un rectángulo para indicar el área que seleccionaremos
- Un elemento
canvas
para recortar el centro del mapa (sus atributoswidth
andheight
determinarán las dimensiones de la imagen usada para la predicción)
Para renderizar el mapa y generar las imágenes que enviaremos al servidor, utilizaremos la librería Leaflet y el plugin Leaflet Image, por lo que linkaremos estas librería en nuestro html
. Por último, linkaremos también nuestros archivos style.css
y index.js
con los estilos y la lógica de nuestra aplicación, respectivamente.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mapa</title>
<!-- Importamos Leaflet -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin="" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossorigin=""></script>
<script src="leaflet-image.js"></script>
<!-- Importamos nuestros estilos y lógica -->
<link rel="stylesheet" href="style.css">
<script src="index.js"></script>
</head>
<body>
<div id="map"></div>
<div class="panel">
<button id="btn">CLASIFICA</button>
<p id="resultado"></p>
</div>
<div class="rect"></div>
<canvas id="crop_canvas" width="224" height="224" style="display: none"></canvas>
</body>
</html>
Aprende sobre HTML en este vídeo.
CSS
Estos son los estilos que usaremos en nuestra APP
. El mapa ocupará toda la pantalla, mientras que el panel con el botón y la información estará en la esquina superior derecha. El rectángulo estará centrado en el mapa, con un borde rojo, para indicar a un usuario la zona del mapa que se usará exactamente para la predicción. En este caso usaremos un cuadrado de 224 x 224 pixeles ya que es el tamaño que usamos para entrenar nuestra red. Aún así, puedes usar otro tamaño (la API
de Flask
se asegura de hacer un re-escalado de la imagen si las dimensiones no son las esperadas).
* {
margin: 0;
padding: 0;
}
body {
position: relative;
}
#map {
width: 100%;
height: 100vh;
z-index: 1;
}
.panel {
position: absolute;
top: 10px;
right: 10px;
width: 200px;
height: 120px;
background-color: #fafafa;
z-index: 2;
box-sizing: border-box;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
#btn {
background-color: black;
color: white;
border: none;
width: 100px;
height:30px;
}
.rect {
position: absolute;
width: 224px;
height: 224px;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
border: 3px solid red;
background: none;
z-index: 3;
}
Aprende más sobre CSS en este vídeo.
Javascript
Y aquí tienes la lógica de la APP
. El proceso es el siguiente:
- Una vez se carga el contenido de la página, instanciamos el mapa (centrado en unas coordenadas determinadas) y le asignamos una capa, en este caso imágenes aéreas proporcionadas por google.
- Asignamos las acciones a llevar a cabo cuando un usuario hace
click
en el botón:- Deshabilitamos el botón para evitar llamadas mútliples
- Generamos una imagen del mapa entero usando el plugin
leaflet-image
. - Cortamos el centro de la imagen a las dimensiones deseadas (ancho y alto del elemento
crop_canvas
). - Enviamos la imagen a la
API
- Cuando recibimos la respuesta, se la mostramos al usuario y volvemos a habilitar el botón.
// Pon aquí la URL de tu API
const url = 'https://juansensio-blog-flask.herokuapp.com/predict'
// Puedes usar una url local durante el desarrollo para asegurarte que todo funciona bien
//const url = 'http://127.0.0.1:5000/predict'
// esperar a que se cargue el contenido de la app
document.addEventListener('DOMContentLoaded', function () {
// instanciar el mapa con coordenadas y zoom inicial
const map = L.map('map', {
preferCanvas: true
}).setView([41.39, 2.15], 17);
// assignar capa al mapa con imágenes aéreas de google maps
L.tileLayer('http://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', {
maxZoom: 20,
subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
}).addTo(map);
// asignar acciones al botón
const btn = document.getElementById('btn')
btn.addEventListener('click', function () {
// cambiar el texto y deshabilitar el botón para evitar llamadas concurrentes
btn.innerText = 'Cargando ...'
btn.disabled = true
// convertir el mapa a imagen
return leafletImage(map, function (err, canvas) {
// generamos una imagen con el mapa
var img = new Image();
img.src = canvas.toDataURL();
// cuando la imagen ha sido cargada, cortamos el centro y lo enviamos a la API
img.onload = function () {
// cortamos el centro al tamaño deseado (224 x 224)
crop_canvas = document.getElementById('crop_canvas');
const w = crop_canvas.width
const h = crop_canvas.height
const sx = (canvas.width - w) / 2
const sy = (canvas.height - h) / 2
crop_canvas.getContext('2d').drawImage(img, sx, sy, w, h, 0, 0, w, h);
// enviamos la imagen a la API
return crop_canvas.toBlob(function (blob) {
const formData = new FormData()
formData.append('file', blob)
return fetch(url, {
method: 'post',
body: formData
})
// recibimos la respuesta y la pintamos en la app
.then(res => res.json())
.then(res => {
const panel = document.getElementById('resultado')
panel.innerText = res.label
// habilitamos de nuevo el botón
btn.innerText = 'Clasifica'
btn.disabled = false
})
})
}
})
})
})
Si quieres jugar con el código te aconsejo utilizar tu API
en local, así podrás ver de manera sencilla qué es lo que recibe, por ejemplo puedes guardar la imagen en un archivo para asegurarte que estás enviando imágenes correctas.
Desplegando en Github Pages
Una vez nos hemos asegurado que todo funciona correctamente, podemos desplegar la aplicación en Github Pages. Puedes ver un ejemplo aquí.
Aprender más sobre Github Pages con este vídeo.
Resumen
En este post hemos visto cómo implementar una interfaz de usuario para comunicarnos con nuestro servidor Flask
. Esta aplicación está implementada con tecnologías web: HTML
, CSS
y Javascript
. Una vez implementado el código, podemos usar Github Pages
para desplegar nuestra aplicación, obteniendo una URL
para poder conectarnos a través de un navegador. De esta manera hemos conseguido cerrar el círculo completo de la puesta en producción de una red neuronal, desde su entrenamiento, puesto en marcha en un servidor web y accesible a través de una aplicación.