Imshow in Python

How to display image data in Python with Plotly.


New to Plotly?

Plotly is a free and open-source graphing library for Python. We recommend you read our Getting Started guide for the latest installation or upgrade instructions, then move on to our Plotly Fundamentals tutorials or dive straight in to some Basic Charts tutorials.

This tutorial shows how to display and explore image data. If you would like instead a logo or static image, use go.layout.Image as explained here.

Displaying RGB image data with px.imshow

px.imshow displays multichannel (RGB) or single-channel ("grayscale") image data.

In [1]:
import plotly.express as px
import numpy as np
img_rgb = np.array([[[255, 0, 0], [0, 255, 0], [0, 0, 255]],
                    [[0, 255, 0], [0, 0, 255], [255, 0, 0]]
                   ], dtype=np.uint8)
fig = px.imshow(img_rgb)
fig.show()

Read image arrays from image files

In order to create a numerical array to be passed to px.imshow, you can use a third-party library like PIL, scikit-image or opencv. We show below how to open an image from a file with skimage.io.imread, and alternatively how to load a demo image from skimage.data.

In [2]:
import plotly.express as px
from skimage import io
img = io.imread('https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Crab_Nebula.jpg/240px-Crab_Nebula.jpg')
fig = px.imshow(img)
fig.show()
In [3]:
import plotly.express as px
from skimage import data
img = data.astronaut()
fig = px.imshow(img, binary_format="jpeg", binary_compression_level=0)
fig.show()

Display single-channel 2D data as a heatmap

For a 2D image, px.imshow uses a colorscale to map scalar data to colors. The default colorscale is the one of the active template (see the tutorial on templates).

In [4]:
import plotly.express as px
import numpy as np
img = np.arange(15**2).reshape((15, 15))
fig = px.imshow(img)
fig.show()

Choose the colorscale to display a single-channel image

You can customize the continuous color scale just like with any other Plotly Express function. However, color_continuous_scale is ignored when using binary_string=True, since the image is always represented as grayscale (and no colorbar is displayed).

In [5]:
import plotly.express as px
import numpy as np
img = np.arange(100).reshape((10, 10))
fig = px.imshow(img, binary_string=True)
fig.show()

You can use this to make the image grayscale as well:

In [6]:
import plotly.express as px
import numpy as np
img = np.arange(100).reshape((10, 10))
fig = px.imshow(img, color_continuous_scale='gray')
fig.show()

Hiding the colorbar and axis labels

See the continuous color and cartesian axes pages for more details.

In [7]:
import plotly.express as px
from skimage import data
img = data.camera()
fig = px.imshow(img, color_continuous_scale='gray')
fig.update_layout(coloraxis_showscale=False)
fig.update_xaxes(showticklabels=False)
fig.update_yaxes(showticklabels=False)
fig.show()

Customizing the axes and labels on a single-channel image

You can use the x, y and labels arguments to customize the display of a heatmap, and use .update_xaxes() to move the x axis tick labels to the top:

In [8]:
import plotly.express as px
data=[[1, 25, 30, 50, 1], [20, 1, 60, 80, 30], [30, 60, 1, 5, 20]]
fig = px.imshow(data,
                labels=dict(x="Day of Week", y="Time of Day", color="Productivity"),
                x=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
                y=['Morning', 'Afternoon', 'Evening']
               )
fig.update_xaxes(side="top")
fig.show()

Display an xarray image with px.imshow

xarrays are labeled arrays (with labeled axes and coordinates). If you pass an xarray image to px.imshow, its axes labels and coordinates will be used for axis titles. If you don't want this behavior, you can pass img.values which is a NumPy array if img is an xarray. Alternatively, you can override axis titles hover labels and colorbar title using the labels attribute, as above.

In [9]:
import plotly.express as px
import xarray as xr
# Load xarray from dataset included in the xarray tutorial
airtemps = xr.tutorial.open_dataset('air_temperature').air.sel(lon=250.0)
fig = px.imshow(airtemps.T, color_continuous_scale='RdBu_r', origin='lower')
fig.show()

Display an xarray image with square pixels

For xarrays, by default px.imshow does not constrain pixels to be square, since axes often correspond to different physical quantities (e.g. time and space), contrary to a plain camera image where pixels are square (most of the time). If you want to impose square pixels, set the parameter aspect to "equal" as below.

In [10]:
import plotly.express as px
import xarray as xr
airtemps = xr.tutorial.open_dataset('air_temperature').air.isel(time=500)
colorbar_title = airtemps.attrs['var_desc'] + '<br>(%s)'%airtemps.attrs['units']
fig = px.imshow(airtemps, color_continuous_scale='RdBu_r', aspect='equal')
fig.show()

Display multichannel image data with go.Image

It is also possible to use the go.Image trace from the low-level graph_objects API in order to display image data. Note that go.Image only accepts multichannel images. For single-channel images, use go.Heatmap.

Note that the go.Image trace is different from the go.layout.Image class, which can be used for adding background images or logos to figures.

In [11]:
import plotly.graph_objects as go
img_rgb = [[[255, 0, 0], [0, 255, 0], [0, 0, 255]],
           [[0, 255, 0], [0, 0, 255], [255, 0, 0]]]
fig = go.Figure(go.Image(z=img_rgb))
fig.show()

Passing image data as a binary string to go.Image

The z parameter of go.Image passes image data in the form of an array or a list of numerical values, but it is also possible to use the source parameter, which takes a b64 binary string. Thanks to png or jpg compression, using source is a way to reduce the quantity of data passed to the browser, and also to reduce the serialization time of the figure, resulting in increased performance.

Note than an easier way of creating binary strings with px.imshow is explained below.

In [12]:
import plotly.graph_objects as go
from skimage import data
from PIL import Image
import base64
from io import BytesIO

img = data.astronaut()  # numpy array
pil_img = Image.fromarray(img) # PIL image object
prefix = "data:image/png;base64,"
with BytesIO() as stream:
    pil_img.save(stream, format="png")
    base64_string = prefix + base64.b64encode(stream.getvalue()).decode("utf-8")
fig = go.Figure(go.Image(source=base64_string))
fig.show()

Defining the data range covered by the color range with zmin and zmax

The data range and color range are mapped together using the parameters zmin and zmax of px.imshow or go.Image, which correspond respectively to the data values mapped to black [0, 0, 0] and white [255, 255, 255], or to the extreme colors of the colorscale in the case of single-channel data.

For go.Image, zmin and zmax need to be given for all channels, whereas it is also possible to pass a scalar value (used for all channels) to px.imshow.

In [13]:
import plotly.express as px
from skimage import data
img = data.astronaut()
# Increase contrast by clipping the data range between 50 and 200
fig = px.imshow(img, zmin=50, zmax=200)
# We customize the hovertemplate to show both the data and the color values
# See https://plotly.com/python/hover-text-and-formatting/#customize-tooltip-text-with-a-hovertemplate
#fig.update_traces(hovertemplate="x: %{x} <br> y: %{y} <br> z: %{z} <br> color: %{color}")
fig.show()
In [14]:
import plotly.express as px
from skimage import data
img = data.astronaut()
# Stretch the contrast of the red channel only, resulting in a more red image
fig = px.imshow(img, zmin=[50, 0, 0], zmax=[200, 255, 255])
fig.show()

Automatic contrast rescaling in px.imshow

When zmin and zmax are not specified, the contrast_rescaling arguments determines how zmin and zmax are computed. For contrast_rescaling='minmax', the extrema of the data range are used. For contrast_rescaling='infer', a heuristic based on the data type is used:

  • for integer data types, zmin and zmax correspond to the extreme values of the data type, for example 0 and 255 for uint8, 0 and 65535 for uint16, etc.
  • for float numbers, the maximum value of the data is computed, and zmax is 1 if the max is smaller than 1, 255 if the max is smaller than 255, etc. (with higher thresholds 216 - 1 and 232 -1).

These two modes can be used for single- and multichannel data. The default value is to use 'minmax' for single-channel data (as in a Heatmap trace) and infer for multi-channel data (which often consist of uint8 data). In the example below we override the default value by setting contrast_rescaling='infer' for a single-channel image.

In [15]:
import plotly.express as px
img = np.arange(100, dtype=np.uint8).reshape((10, 10))
fig = px.imshow(img, contrast_rescaling='infer')
fig.show()

Ticks and margins around image data

In [16]:
import plotly.express as px
from skimage import data
img = data.astronaut()
fig = px.imshow(img)
fig.update_layout(width=400, height=400, margin=dict(l=10, r=10, b=10, t=10))
fig.update_xaxes(showticklabels=False).update_yaxes(showticklabels=False)
fig.show()

Combining image charts and other traces

In [17]:
import plotly.express as px
import plotly.graph_objects as go
from skimage import data
img = data.camera()
fig = px.imshow(img, color_continuous_scale='gray')
fig.add_trace(go.Contour(z=img, showscale=False,
                         contours=dict(start=0, end=70, size=70, coloring='lines'),
                         line_width=2))
fig.add_trace(go.Scatter(x=[230], y=[100], marker=dict(color='red', size=16)))
fig.show()

Displaying an image and the histogram of color values

In [18]:
from plotly.subplots import make_subplots
from skimage import data
img = data.chelsea()
fig = make_subplots(1, 2)
# We use go.Image because subplots require traces, whereas px functions return a figure
fig.add_trace(go.Image(z=img), 1, 1)
for channel, color in enumerate(['red', 'green', 'blue']):
    fig.add_trace(go.Histogram(x=img[..., channel].ravel(), opacity=0.5,
                               marker_color=color, name='%s channel' %color), 1, 2)
fig.update_layout(height=400)
fig.show()