E: Widget para MatPlotLib en Jupyter-Lab

Con MatPlotLib hemos ido visualizando gráficas e imágenes. Sin embargo dicha biblioteca tal cual es un poco incómoda en Jupyter-Lab ya que ajustar el tamaño y escalas de las imágenes es un proceso un tanto tedioso. Existe un widget que nos va a facilitar mucho la visualización de datos en nuestros notebooks. Podemos instalarlo como:

> conda install -c conda-forge ipympl

Para activarlo solo hay que usar el decorador %matplotlib widget:

[1]:
%matplotlib widget

import matplotlib.pyplot as plt
import numpy as np

Cuando creemos una gráfica como venimos haciendo hasta ahora al mover el ratón por encima se nos descubrirá una barra de botones (a la izquierda arriba por defecto) que nos permitirá mover la gráfica, hacer zoom e incluso guardar cómodamente la gráfica:

[2]:
# Testing matplotlib interactions with a simple plot
fig = plt.figure()
plt.plot(np.sin(np.linspace(0, 20, 100)));

Añadiendo componentes a la interfaz

Se pueden incluso crear elementos de interfaz para modificar interactivamente aspectos del gráfico. En el siguiente ejemplo se crea un slider para modificar un parámetro de la gráfica:

[3]:
from ipywidgets import AppLayout, FloatSlider

plt.ioff()

slider = FloatSlider(             # Creamos un slider que permite valores de .02 a 2.0 inicializado en 1.0
    orientation='horizontal',
    description='Factor:',
    value=1.0,
    min=0.02,
    max=2.0
)

slider.layout.margin = '0px 30% 0px 0px'    # Márgenes y tamaño del slider
slider.layout.width = '100%'

fig = plt.figure()                          # Dibujamos la gráfica
fig.canvas.header_visible = False
fig.canvas.layout.min_height = '400px'
plt.title('Plotting: y=sin({} * x)'.format(slider.value))

x = np.linspace(0, 20, 500)                   # Creamos los puntos de la gráfica
lines = plt.plot(x, np.sin(slider.value * x))

def update_lines(change):                     # Función que se ejecutará cada vez que se cambie el valor del slider
    plt.title('Plotting: y=sin({} * x)'.format(change.new))
    lines[0].set_data(x, np.sin(change.new * x))
    fig.canvas.draw()
    fig.canvas.flush_events()

slider.observe(update_lines, names='value')   # Indicamos la función que tiene que ejecutarse cuando cambie el slider

AppLayout(                                    # Indicamos donde tiene que aparecer la gráfica (center) y el slider (abajo)
    center=fig.canvas,
    footer=slider,
    pane_heights=[0, 6, 1]
)
[3]:

Un ejemplo un poco más elaborado

Vamos a representar una imagen astronómica y crear 3 sliders para controlar el valor mínimo, máximo y gamma de la paleta de colores. Además veremos que cuando situamos el cursor sobre la imagen nos dice las coordenadas a las que estamos apuntando y el valor del píxel sobre el que nos encontramos (¡muy útil!):

[4]:
%matplotlib widget

import matplotlib.pyplot as plt
from ipywidgets import AppLayout, FloatSlider
from astropy.io import fits
import numpy as np
import matplotlib.colors as colors
from ipywidgets import GridspecLayout

hdul = fits.open("imagenes/m1_new.fit")       # La imagen que vamos a representar
data = hdul[0].data

plt.ioff()
plt.clf()
plt.draw()

slider_max = FloatSlider(                     # Creamos los 3 sliders
    orientation='vertical',
    description='Max',
    value=np.max(data),
    min=0,
    max=65536
)
slider_gamma = FloatSlider(
    orientation='vertical',
    description='Gamma',
    value=1.0,
    min=0.2,
    max=5.0
)
slider_min = FloatSlider(
    orientation='vertical',
    description='Min',
    value=np.min(data),
    min=0,
    max=65536
)

slider_max.layout.padding = '60px 0px 60px 0px'     # Cuestiones de margenes y tamaños de los sliders
slider_max.layout.width = '80px'
slider_max.layout.height = '100%'
slider_gamma.layout.padding = '60px 0px 60px 0px'
slider_gamma.layout.width = '80px'
slider_gamma.layout.height = '100%'
slider_min.layout.padding = '60px 0px 60px 0px'
slider_min.layout.width = '80px'
slider_min.layout.height = '100%'


fig = plt.figure()                             # Creamos la gráfica
fig = plt.figure("matrix", figsize=[10, 5])
fig.canvas.toolbar_position = 'left'
fig.canvas.header_visible = False

image = plt.imshow(data, norm=colors.PowerNorm(gamma=1.0, vmin=np.min(data), vmax=np.max(data)), origin='lower')
plt.colorbar(label='Counts')


def update_max(change):                  # Función cuando cambiamos el slider max
    newval = change.new

    if newval <= slider_min.value:
        slider_min.value = newval - 1

    update_values()

def update_gamma(change):                # Función cuando cambiamos el slider gamma
    update_values()

def update_min(change):                  # Función cuando cambiamos el slider min
    newval = change.new

    if newval >= slider_max.value:
        slider_max.value = newval + 1

    update_values()

def update_values():                     # Actualiza la escala de colores de la gráfica de acuerdo a los valores
                                         # de los sliders
    image.set_norm(norm=colors.PowerNorm(gamma=slider_gamma.value, vmin=slider_min.value, vmax=slider_max.value))
    fig.canvas.draw()
    fig.canvas.flush_events()


slider_max.observe(update_max, names='value')      # Asignamos las funciones a llamar en cada cambio de un slider
slider_gamma.observe(update_gamma, names='value')
slider_min.observe(update_min, names='value')

grid = GridspecLayout(1, 3)                        # Situamos cada uno de los elementos (gráfica y 3 sliders)
grid[0, 0] = slider_min
grid[0, 1] = slider_gamma
grid[0, 2] = slider_max
AppLayout(
    center=fig.canvas,
    right_sidebar=grid,
    pane_widths=[0,5,1.5]
)
[4]:

Se puede usar otros widgets dentro de Jupyter-Lab: