Skip to content

Note

Click here to download the full example code

Introduction to fastplotlib

fastplotlib API

1. Graphics - objects that are drawn

2. Layouts

3. Widgets - high level widgets to make repetitive UIs easier

import fastplotlib as fpl
import numpy as np
import imageio.v3 as iio
import workshop_utils

Simple Image

# create a `Figure` instance
# by default the figure will have 1 subplot
fig = fpl.Figure(size=(600, 500))

# get a grayscale image
data = iio.imread("imageio:camera.png")

# plot the image data
image_graphic = fig[0, 0].add_image(data=data, name="sample-image")

# show the plot
fig.show(sidecar=True)
image_graphic.cmap = "viridis"

Slicing data

print(image_graphic.data.value.shape)
image_graphic.data[::15, :] = 1
image_graphic.data[:, ::15] = 1
image_graphic.data[data > 175] = 255
image_graphic.vmin = 50
image_graphic.vmax = 150
image_graphic.reset_vmin_vmax()

Change graphic properties

Now that we have a better idea of how graphic properties can be dynamically changed, let's practice :D

Question: Can you change the data of the image to be random data of the same shape? Hint: Use np.random.rand() to create the new data.

Note: At first you should see a solid image. You will need to reset the vmin/vmax of the image.

# enter code here

Question: Can you change the colormap of the image? See here for a list of colormaps.

# enter code here

Image updates

This examples show how you can define animation functions that run on every render cycle.

# create another `Figure` instance
fig_v = fpl.Figure(size=(600, 500))

fig.canvas.max_buffered_frames = 1

# make some random data again
data = np.random.rand(512, 512)

# plot the data

fig_v[0, 0].add_image(data=data, name="random-image")


# a function to update the image_graphic
# a plot will pass its plot instance to the animation function as an argument

def update_data(plot_instance):
    new_data = np.random.rand(512, 512)
    plot_instance[0, 0]["random-image"].data = new_data


# add this as an animation function

fig_v.add_animations(update_data)

fig_v.show(sidecar=True)
# close the plot
fig_v.close()

Image Practice

Question: Can you do the following:

  • create a new plot called practice_fig

  • add an ImageGraphic with the following characteristics to the figure:

    • random data in the shape (512, 512)

    • colormap "viridis"

    • name "random-image"

  • set the top-left and bottom-right quadrants of the data to 1 using slicing :D

  • add an animation function that updates the top-right and bottom-left quadrants with new random data

# enter code here

ImageWidget

# get zfish data
path = workshop_utils.fetch_zfish()
# load subsampled snippet of zebrafish data from Martin Haesemeyer
zfish_data = np.load(path, allow_pickle=False)
# data is tzxy
print(zfish_data.shape)
# get the number of planes
n_planes = zfish_data.shape[1]
print(n_planes)
iw_zfish = fpl.ImageWidget(
    data=[zfish_data[:, i] for i in range(n_planes)],
    window_funcs={"t": (np.mean, 5)},
    names=[f"plane-{i}" for i in range(n_planes)],
    cmap="gnuplot2",
)

iw_zfish.show(sidecar=True)

Apply a window function

#iw_zfish.window_funcs["t"].func = np.max
# close the plot
iw_zfish.close()

z-slider

iw_z = fpl.ImageWidget(
    data=zfish_data, # you can also provide a list of tzxy arrays
    window_funcs={"t": (np.mean, 5)},
    cmap="gnuplot2",
)

iw_z.show()
# close the plot
iw_z.close()

2D Line Plots

First generate some data

# linspace, create 100 evenly spaced x values from -10 to 10
xs = np.linspace(-10, 10, 100)
# sine wave
ys = np.sin(xs)
sine = np.column_stack([xs, ys])

# cosine wave
ys = np.cos(xs)
cosine = np.column_stack([xs, ys])

# sinc function
a = 0.5
ys = np.sinc(xs) * 3
sinc = np.column_stack([xs, ys])
# Create a figure
fig_lines = fpl.Figure()
# we will add all the lines to the same subplot
subplot = fig_lines[0, 0]

# plot sine wave, use a single color
sine_graphic = subplot.add_line(data=sine, thickness=5, colors="magenta")

# you can also use colormaps for lines!
cosine_graphic = subplot.add_line(data=cosine, thickness=12, cmap="autumn", offset=(0, 5, 0))

# or a list of colors for each datapoint
colors = ["r"] * 25 + ["purple"] * 25 + ["y"] * 25 + ["b"] * 25
sinc_graphic = subplot.add_line(data=sinc, thickness=5, colors=colors, offset=(0, 8, 0))

# show the plot
fig_lines.show(sidecar=True, sidecar_kwargs={"title": "lines"})

Graphic properties support slicing! :D

# indexing of colors
cosine_graphic.colors[:15] = "magenta"
cosine_graphic.colors[90:] = "red"
cosine_graphic.colors[60] = "w"
# indexing to assign colormaps to entire lines or segments
sinc_graphic.cmap[10:50] = "gray"

# setting the colormap based on particular values
sine_graphic.cmap = "plasma"
# set the cmap transform
sine_graphic.cmap.transform = [4] * 25 + [1] * 25 + [3] * 25 + [8] * 25
# close the plot
fig_lines.close()
# Create a figure
fig_lines = fpl.Figure()

# we will add all the lines to the same subplot
subplot = fig_lines[0, 0]

# plot sine wave, use a single color
sine_graphic = subplot.add_line(data=sine, thickness=5, colors="magenta")

# you can also use colormaps for lines!
cosine_graphic = subplot.add_line(data=cosine, thickness=12, cmap="autumn", offset=(0, 5, 0))

# show the plot
fig_lines.show(sidecar=True, sidecar_kwargs={"title": "lines"})

2D Lines Practice

Question: Can you change the colormap of the sine_graphic to "hsv"?

# enter code here

Question: Can you change the color of first 50 data points of the sinc_graphic to green?

# enter code here

Question: Can you to change the color of last 50 points of the cosine_graphic to equal the colors of the last 50 points of the sine_graphic?

# enter code here

Capture changes to graphic properties as events

# first set the color of the sine and cosine wave to white so that we can better see the changes
cosine_graphic.colors = "w"
sine_graphic.colors = "w"

# will print event data when the color of the cosine graphic changes
@cosine_graphic.add_event_handler("colors")
def callback_func(ev):
    print(ev.info)
# when the cosine graphic colors change, will also update the sine_graphic colors
def change_colors(ev):
    sine_graphic.colors[ev.info["key"]] = "magenta"



cosine_graphic.add_event_handler(change_colors, "colors")
cosine_graphic.colors[:10] = "g"
fig_lines.close()

More Events :D

# generate some circles
def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray:
    theta = np.linspace(0, 2 * np.pi, n_points)
    xs = radius * np.sin(theta)
    ys = radius * np.cos(theta)

    return np.column_stack([xs, ys]) + center


# this makes 5 circles
circles = list()
for x in range(0, 50, 10):
    circles.append(make_circle(center=(x, 0), radius=4, n_points=100))
fig = fpl.Figure()

circles_graphic = fig[0, 0].add_line_collection(data=circles, cmap="tab10", thickness=10)
fig.show()
# get the nearest graphic that is clicked and change the color
def click_event(ev):
    # reset colors
    circles_graphic.cmap = "tab10"

    # map the click position to world coordinates
    xy = fig[0, 0].map_screen_to_world(ev)[:-1]

    # get the nearest graphic to the position
    nearest = fpl.utils.get_nearest_graphics(xy, circles_graphic)[0]

    # change the closest graphic color to white
    nearest.colors = "w"
# add event handler to the renderer
fig[0, 0].renderer.add_event_handler(click_event, "click")

Events Practice

Question: Can you add another event handler (using either method) to the circles_graphic that will change the thickness of a circle to 5 when you hover over it?

Hint: the event type should be "pointer_move" :)

# enter code here

Selectors

LinearSelector

fig = fpl.Figure()

# same sine data from before
sine_graphic = fig[0, 0].add_line(data=sine, colors="w")

# add a linear selector the sine wave
selector = sine_graphic.add_linear_selector()

fig[0, 0].auto_scale()

fig.show(maintain_aspect=False)
# change the color of the sine wave based on the location of the linear selector
@selector.add_event_handler("selection")
def set_color_at_index(ev):
    # get the selected index
    ix = ev.get_selected_index()
    # get the sine graphic
    g = ev.graphic.parent
    # change the color of the sine graphic at the index of the selector
    g.colors[ix] = "green"
# close the plot
fig.close()

LinearRegionSelector

fig = fpl.Figure((2, 1))

# data to plot
xs = np.linspace(0, 10 * np.pi, 1_000)

sine = np.sin(xs)
sine += 100

# make sine along x axis
sine_graphic_x = fig[0, 0].add_line(np.column_stack([xs, sine]), offset=(10, 0, 0))

# add a linear region selector
ls_x = sine_graphic_x.add_linear_region_selector()  # default axis is "x"

# get the initial selected date of the linear region selector
zoomed_init = ls_x.get_selected_data()

# make a line graphic for displaying zoomed data
zoomed_x = fig[1, 0].add_line(zoomed_init)


@ls_x.add_event_handler("selection")
def set_zoom_x(ev):
    """sets zoomed x selector data"""
    # get the selected data
    selected_data = ev.get_selected_data()

    # remove the current zoomed data
    # and update with new selected data
    global zoomed_x

    fig[1, 0].remove_graphic(zoomed_x)
    zoomed_x = fig[1, 0].add_line(selected_data)
    fig[1, 0].auto_scale()


fig.show(maintain_aspect=False)
# close the plot
fig.close()

https://fastplotlib.readthedocs.io/en/latest/_gallery/index.html

Total running time of the script: ( 0 minutes 0.000 seconds)

Download Python source code: 03_fpl_intro_users.py

Download Jupyter notebook: 03_fpl_intro_users.ipynb

Gallery generated by mkdocs-gallery