Quick Start Guide 🚀¶
This notebook goes through the basic components of the fastplotlib
API, images, image updates, line plots, scatter plots, and grid plots.
NOTE: This quick start guide in the docs is NOT interactive. Download the examples from the repo and try them on your own computer. You can run the desktop examples directly if you have ``glfw`` installed, or try the notebook demos: https://github.com/kushalkolar/fastplotlib/tree/master/examples
It will not be possible to have live demos on the docs until someone can figure out how to get pygfx to work with wgpu
in the browser, perhaps through pyodide or something :D.
The example images are from ``imageio`` so you will need to install it for this example notebook. But ``imageio`` is not required to use ``fasptlotlib``
[1]:
!pip install imageio
Collecting imageio
Downloading imageio-2.34.1-py3-none-any.whl.metadata (4.9 kB)
Requirement already satisfied: numpy in /home/docs/checkouts/readthedocs.org/user_builds/fastplotlib/envs/latest/lib/python3.11/site-packages (from imageio) (1.26.4)
Collecting pillow>=8.3.2 (from imageio)
Downloading pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl.metadata (9.2 kB)
Downloading imageio-2.34.1-py3-none-any.whl (313 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 313.5/313.5 kB 7.0 MB/s eta 0:00:00
Downloading pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl (4.5 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.5/4.5 MB 63.4 MB/s eta 0:00:00
Installing collected packages: pillow, imageio
Successfully installed imageio-2.34.1 pillow-10.3.0
[2]:
import imageio.v3 as iio
[3]:
import fastplotlib as fpl
from ipywidgets import VBox, HBox, IntSlider
import numpy as np
error: XDG_RUNTIME_DIR not set in the environment.
No windowing system present. Using surfaceless platform
No config found!
No config found!
Available devices:
❗ (default) | llvmpipe (LLVM 15.0.7, 256 bits) | CPU | Vulkan | Mesa 23.2.1-1ubuntu3.1~22.04.2 (LLVM 15.0.7)
❗ | llvmpipe (LLVM 15.0.7, 256 bits) | CPU | OpenGL |
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
Images¶
[4]:
# create a `Figure` instance
fig = fpl.Figure()
# 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()
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
error: XDG_RUNTIME_DIR not set in the environment.
Imageio: 'camera.png' was not found on your computer; downloading it now.
Try 1. Download from https://github.com/imageio/imageio-binaries/raw/master/images/camera.png (136 kB)
Downloading: 8192/139512 bytes (5.9%)139512/139512 bytes (100.0%)
Done
File saved as /home/docs/.imageio/images/camera.png.
[4]:
In live notebooks or desktop applications, you can use the handle on the bottom right corner of the canvas to resize it. You can also pan and zoom using your mouse!
This is how you can take a snapshot of the canvas. Snapshots are shown throughout this doc page for the purposes of documentation, they are NOT necessary for real interactive usage. Download the notebooks to run live demos.
[5]:
fig.canvas.snapshot()
[5]:
Changing graphic “features”
[6]:
image_graphic.cmap = "viridis"
[7]:
fig.canvas.snapshot()
[7]:
Slicing data¶
Most features, such as ``data`` support slicing!
Out image data is of shape [n_rows, n_cols]
[8]:
image_graphic.data().shape
[8]:
(512, 512)
[9]:
image_graphic.data[::15, :] = 1
image_graphic.data[:, ::15] = 1
[10]:
fig.canvas.snapshot()
[10]:
Fancy indexing
[11]:
image_graphic.data[data > 175] = 255
[12]:
fig.canvas.snapshot()
[12]:
Adjust vmin vmax
[13]:
image_graphic.cmap.vmin = 50
image_graphic.cmap.vmax = 150
[14]:
fig.canvas.snapshot()
[14]:
Set the entire data array again
Note: The shape of the new data array must match the current data shown in the Graphic.
[15]:
new_data = iio.imread("imageio:astronaut.png")
new_data.shape
Imageio: 'astronaut.png' was not found on your computer; downloading it now.
Try 1. Download from https://github.com/imageio/imageio-binaries/raw/master/images/astronaut.png (773 kB)
Downloading: 8192/791555 bytes (1.0%)791555/791555 bytes (100.0%)
Done
File saved as /home/docs/.imageio/images/astronaut.png.
[15]:
(512, 512, 3)
This is an RGB image, convert to grayscale to maintain the shape of (512, 512)
[16]:
gray = new_data.dot([0.3, 0.6, 0.1])
gray.shape
[16]:
(512, 512)
[17]:
image_graphic.data = gray
/home/docs/checkouts/readthedocs.org/user_builds/fastplotlib/envs/latest/lib/python3.11/site-packages/fastplotlib/graphics/_features/_base.py:34: UserWarning: converting float64 array to float32
warn(f"converting {array.dtype} array to float32")
[18]:
fig.canvas.snapshot()
[18]:
reset vmin vmax
[19]:
image_graphic.cmap.reset_vmin_vmax()
[20]:
fig.canvas.snapshot()
[20]:
Indexing plots¶
Plots are indexable and give you their graphics by name
[21]:
fig
[21]:
fastplotlib.Figure @ 0x7fd087f10550
Subplots:
unnamed: Subplot @ 0x7fd087f111d0
[22]:
fig[0, 0]["sample-image"]
[22]:
<weakproxy at 0x7fd053e046d0 to ImageGraphic at 0x7fd053e08dd0>
You can also use numerical indexing on ``plot.graphics``
[23]:
fig[0, 0].graphics
[23]:
(<weakproxy at 0x7fd053e046d0 to ImageGraphic at 0x7fd053e08dd0>,)
[24]:
fig[0, 0].graphics[0]
[24]:
<weakproxy at 0x7fd053e046d0 to ImageGraphic at 0x7fd053e08dd0>
The Graphic
instance is also returned when you call plot.add_<graphic_type>
.
[25]:
image_graphic
[25]:
<weakproxy at 0x7fd053e046d0 to ImageGraphic at 0x7fd053e08dd0>
[26]:
image_graphic is fig[0, 0]["sample-image"]
[26]:
True
RGB images¶
cmap
arguments are ignored for rgb images, but vmin vmax still works
[27]:
fig_rgb = fpl.Figure()
fig_rgb[0, 0].add_image(new_data, name="rgb-image")
fig_rgb.show()
[27]:
[28]:
fig_rgb.canvas.snapshot()
[28]:
vmin and vmax are still applicable to rgb images
[29]:
fig_rgb[0, 0]["rgb-image"].cmap.vmin = 100
[30]:
fig_rgb.canvas.snapshot()
[30]:
Image updates¶
This examples show how you can define animation functions that run on every render cycle.
[31]:
# create another `Figure` instance
fig_vid = fpl.Figure()
# make some random data again
data = np.random.rand(512, 512)
# plot the data
fig_vid[0, 0].add_image(data=data, name="random-image")
# a function to update the image_graphic
# a subplot will pass its instance to the animation function as an argument
def update_data(subplot):
new_data = np.random.rand(512, 512)
subplot["random-image"].data = new_data
#add this as an animation function to the subplot
fig_vid[0, 0].add_animations(update_data)
# show the plot
fig_vid.show()
[31]:
Share controllers across plots
This example creates a new plot, but it synchronizes the pan-zoom controller
[32]:
fig_sync = fpl.Figure(controllers=fig_vid.controllers)
data = np.random.rand(512, 512)
image_graphic_instance = fig_sync[0, 0].add_image(data=data, cmap="viridis")
# you will need to define a new animation function for this graphic
def update_data_2():
new_data = np.random.rand(512, 512)
# alternatively, you can use the stored reference to the graphic as well instead of indexing the Plot
image_graphic_instance.data = new_data
# add the animation function to the figure instead of the subplot
fig_sync.add_animations(update_data_2)
fig_sync.show()
[32]:
Keeping a reference to the Graphic instance, as shown above image_graphic_instance
, is useful if you’re creating something where you need flexibility in the naming of the graphics
You can also use ipywidgets.VBox
and HBox
to stack plots. See the gridplot
notebooks for a proper gridplot interface for more automated subplotting
Not shown in the docs, try the live demo for this feature
[33]:
#VBox([plot_v.canvas, plot_sync.show()])
[34]:
#HBox([plot_v.show(), plot_sync.show()])
Line plots¶
2D line plots
This example plots a sine wave, cosine wave, and ricker wavelet and demonstrates how Graphic Features can be modified by slicing!
Generate some data.
[35]:
# 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.dstack([xs, ys])[0]
# cosine wave
ys = np.cos(xs) + 5
cosine = np.dstack([xs, ys])[0]
# sinc function
a = 0.5
ys = np.sinc(xs) * 3 + 8
sinc = np.dstack([xs, ys])[0]
Plot all of it on the same plot. Each line plot will be an individual Graphic, you can have any combination of graphics on a plot.
[36]:
# Create a plot instance
fig_line = fpl.Figure()
# plot sine wave, use a single color
sine_graphic = fig_line[0, 0].add_line(data=sine, thickness=5, colors="magenta")
# you can also use colormaps for lines!
cosine_graphic = fig_line[0, 0].add_line(data=cosine, thickness=12, cmap="autumn")
# or a list of colors for each datapoint
colors = ["r"] * 25 + ["purple"] * 25 + ["y"] * 25 + ["b"] * 25
sinc_graphic = fig_line[0, 0].add_line(data=sinc, thickness=5, colors = colors)
fig_line.show()
[36]:
“stretching” the camera, useful for large timeseries data
Set maintain_aspect = False
on a camera, and then use the right mouse button and move the mouse to stretch and squeeze the view!
You can also click the ``1:1`` button to toggle this.
[37]:
fig_line[0, 0].camera.maintain_aspect = False
reset the plot area
[38]:
fig_line[0, 0].auto_scale(maintain_aspect=True)
Graphic features support slicing! :D
[39]:
# 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"
sine_graphic.cmap = "seismic"
# more complex indexing, set the blue value directly from an array
cosine_graphic.colors[65:90, 0] = np.linspace(0, 1, 90-65)
Make a snapshot of the canvas after slicing
[40]:
fig_line.canvas.snapshot()
[40]:
You can capture changes to a graphic feature as events
[41]:
def callback_func(event_data):
print(event_data)
# Will print event data when the color changes
cosine_graphic.colors.add_event_handler(callback_func)
[42]:
# more complex indexing of colors
# from point 15 - 30, set every 3rd point as "cyan"
cosine_graphic.colors[15:50:3] = "cyan"
FeatureEvent @ 0x7fd053e2cb50
type: colors
pick_info: {'index': range(15, 50, 3), 'collection-index': None, 'world_object': <weakproxy at 0x7fd053e47fb0 to Line at 0x7fd053e1b290>, 'new_data': array([[0., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 1., 1.]], dtype=float32)}
[43]:
fig_line.canvas.snapshot()
[43]:
Graphic data
is also indexable
[44]:
cosine_graphic.data[10:50:5, :2] = sine[10:50:5]
cosine_graphic.data[90:, 1] = 7
[45]:
cosine_graphic.data[0] = np.array([[-10, 0, 0]])
/home/docs/checkouts/readthedocs.org/user_builds/fastplotlib/envs/latest/lib/python3.11/site-packages/fastplotlib/graphics/_features/_base.py:31: UserWarning: converting int64 array to int32
warn(f"converting {array.dtype} array to int32")
[46]:
fig_line.canvas.snapshot()
[46]:
Toggle the presence of a graphic within the scene
[47]:
sinc_graphic.present = False
[48]:
fig_line.canvas.snapshot()
[48]:
[49]:
sinc_graphic.present = True
[50]:
fig_line.canvas.snapshot()
[50]:
You can create callbacks to present
too, for example to re-scale the plot w.r.t. graphics that are present in the scene
[51]:
sinc_graphic.present.add_event_handler(fig_line[0, 0].auto_scale)
[52]:
sinc_graphic.present = False
[53]:
fig_line.canvas.snapshot()
[53]:
[54]:
sinc_graphic.present = True
[55]:
fig_line.canvas.snapshot()
[55]:
You can set the z-positions of graphics to have them appear under or over other graphics
[56]:
img = np.random.rand(20, 100)
fig_line[0, 0].add_image(img, name="image", cmap="gray")
# z axis position -1 so it is below all the lines
fig_line[0, 0]["image"].position_z = -1
fig_line[0, 0]["image"].position_x = -50
[57]:
fig_line.canvas.snapshot()
[57]:
3D line plot¶
[58]:
# just set the camera as "3d", the rest is basically the same :D
fig_line_3d = fpl.Figure(cameras='3d')
# create a spiral
phi = np.linspace(0, 30, 200)
xs = phi * np.cos(phi)
ys = phi * np.sin(phi)
zs = phi
# use 3D data
# note: you usually mix 3D and 2D graphics on the same plot
spiral = np.dstack([xs, ys, zs])[0]
fig_line_3d[0, 0].add_line(data=spiral, thickness=2, cmap='winter')
fig_line_3d.show()
[58]:
[59]:
fig_line_3d[0, 0].auto_scale(maintain_aspect=True)
Scatter plots¶
Plot tens of thousands or millions of points
There might be a small delay for a few seconds before the plot shows, this is due to shaders being compiled and a few other things. The plot should be very fast and responsive once it is displayed and future modifications should also be fast!
[60]:
# create a random distribution
# only 1,000 points shown here in the docs, but it can be millions
n_points = 1_000
# if you have a good GPU go for 1.5 million points :D
# this is multiplied by 3
#n_points = 500_000
# dimensions always have to be [n_points, xyz]
dims = (n_points, 3)
clouds_offset = 15
# create some random clouds
normal = np.random.normal(size=dims, scale=5)
# stack the data into a single array
cloud = np.vstack(
[
normal - clouds_offset,
normal,
normal + clouds_offset,
]
)
# color each of them separately
colors = ["yellow"] * n_points + ["cyan"] * n_points + ["magenta"] * n_points
# create plot
fig_scatter = fpl.Figure()
# use an alpha value since this will be a lot of points
scatter_graphic = fig_scatter[0, 0].add_scatter(data=cloud, sizes=3, colors=colors, alpha=0.7)
fig_scatter.show()
[60]:
Scatter graphic features work similarly to line graphic
[61]:
# half of the first cloud's points to red
scatter_graphic.colors[:n_points:2] = "r"
[62]:
fig_scatter.canvas.snapshot()
[62]:
[63]:
# set the green value directly
scatter_graphic.colors[n_points:n_points * 2, 1] = 0.3
[64]:
fig_scatter.canvas.snapshot()
[64]:
[65]:
# set color values directly using an array
scatter_graphic.colors[n_points * 2:] = np.repeat([[1, 1, 0, 0.5]], n_points, axis=0)
[66]:
fig_scatter.canvas.snapshot()
[66]:
[67]:
# change the data, change y-values
scatter_graphic.data[n_points:n_points * 2, 1] += 15
[68]:
fig_scatter.canvas.snapshot()
[68]:
[69]:
# set x values directly but using an array
scatter_graphic.data[n_points:n_points * 2, 0] = np.linspace(-40, 0, n_points)
[70]:
fig_scatter.canvas.snapshot()
[70]:
ipywidget layouts¶
This just plots everything from these examples in a single output cell
[71]:
# row1 = HBox([plot.show(), plot_v.show(), plot_sync.show()])
# row2 = HBox([plot_l.show(), plot_l3d.show(), plot_s.show()])
# VBox([row1, row2])
More subplots¶
[72]:
# Figure of shape 2 x 3 with all controllers synced
figure_grid = fpl.Figure(shape=(2, 3), controller_ids="sync")
# Make a random image graphic for each subplot
for subplot in figure_grid:
# create image data
data = np.random.rand(512, 512)
# add an image to the subplot
subplot.add_image(data, name="rand-img")
# Define a function to update the image graphics with new data
# add_animations will pass the gridplot to the animation function
def update_data(f):
for subplot in f:
new_data = np.random.rand(512, 512)
# index the image graphic by name and set the data
subplot["rand-img"].data = new_data
# add the animation function
figure_grid.add_animations(update_data)
# show the gridplot
figure_grid.show()
[72]:
Slicing GridPlot¶
[73]:
# positional indexing
# row 0 and col 0
figure_grid[0, 0]
[73]:
unnamed: Subplot @ 0x7fd04fa34990
parent: Figure @ 0x7fd04fbdbbd0
Graphics:
'rand-img': ImageGraphic @ 0x7fd04fa73d10
You can get the graphics within a subplot, just like with simple Plot
[74]:
figure_grid[0, 1].graphics
[74]:
(<weakproxy at 0x7fd04faea2f0 to ImageGraphic at 0x7fd04fab9b90>,)
and change their properties
[75]:
figure_grid[0, 1].graphics[0].vmax = 0.5
more slicing with GridPlot
[76]:
# you can give subplots human-readable string names
figure_grid[0, 2].name = "top-right-plot"
[77]:
figure_grid["top-right-plot"]
[77]:
top-right-plot: Subplot @ 0x7fd04fa06850
parent: Figure @ 0x7fd04fbdbbd0
Graphics:
'rand-img': ImageGraphic @ 0x7fd04fae7950
[78]:
# view its position
figure_grid["top-right-plot"].position
[78]:
(0, 2)
[79]:
# these are really the same
figure_grid["top-right-plot"] is figure_grid[0, 2]
[79]:
True
Indexing with subplot name and graphic name
[80]:
figure_grid["top-right-plot"]["rand-img"].vmin = 0.5
Figure subplot customization¶
[81]:
# 2 rows and 3 columns
shape = (2, 3)
# pan-zoom controllers for each view
# views are synced if they have the
# same controller ID
controller_ids = [
[0, 3, 1], # id each controller with an integer
[2, 2, 3]
]
# you can give string names for each subplot within the gridplot
names = [
["subplot0", "subplot1", "subplot2"],
["subplot3", "subplot4", "subplot5"]
]
# Create the grid plot
figure_grid = fpl.Figure(
shape=shape,
controller_ids=controller_ids,
names=names,
)
# Make a random image graphic for each subplot
for subplot in figure_grid:
data = np.random.rand(512, 512)
# create and add an ImageGraphic
subplot.add_image(data=data, name="rand-image")
# Define a function to update the image graphics
# with new randomly generated data
def set_random_frame(gp):
for subplot in gp:
new_data = np.random.rand(512, 512)
subplot["rand-image"].data = new_data
# add the animation
figure_grid.add_animations(set_random_frame)
figure_grid.show()
[81]:
Indexing the gridplot to access subplots
[82]:
# can access subplot by name
figure_grid["subplot0"]
[82]:
subplot0: Subplot @ 0x7fd04f98ce50
parent: Figure @ 0x7fd04f95f750
Graphics:
'rand-image': ImageGraphic @ 0x7fd04fb85a90
[83]:
# can access subplot by index
figure_grid[0, 0]
[83]:
subplot0: Subplot @ 0x7fd04f98ce50
parent: Figure @ 0x7fd04f95f750
Graphics:
'rand-image': ImageGraphic @ 0x7fd04fb85a90
subplots also support indexing!
this can be used to get graphics if they are named
[84]:
# can access graphic directly via name
figure_grid["subplot0"]["rand-image"]
[84]:
<weakproxy at 0x7fd04f976e30 to ImageGraphic at 0x7fd04fb85a90>
[85]:
figure_grid["subplot0"]["rand-image"].vmin = 0.6
figure_grid["subplot0"]["rand-image"].vmax = 0.8
positional indexing also works event if subplots have string names
[86]:
figure_grid[1, 0]["rand-image"].vim = 0.1
figure_grid[1, 0]["rand-image"].vmax = 0.3