Skip to content

Commit

Permalink
Add (interactive) slice explorer in plotly. (#4953)
Browse files Browse the repository at this point in the history
This PR is an addition to the 3D image processing introduction tutorial, which landed with #4850.
It relates to #4850 (comment) and #4762.
I've made a slice explorer with Plotly so it renders even in the static HTML page.
In future iterations, I'll add a 3D visualization showing the position of the selected slice within the dataset.

I've chosen the density_heatmap function so I could readily use its animation_frame argument for the slider interaction (selection). This function is made for aggregation, which I'm bypassing by setting the number of bins to the data size...

Ideally, I wanted to use imshow to view the image slices (planes) but this wouldn't offer such a convenient abstraction in terms of slider interaction.

One issue when trying with this is that occasionally the Plotly plot would behave strangely when dragging the animation slider (the slider itself sometimes moved to a lower position on the screen mid-drag and the image would quit updating). Hitting the "home" button at top to reset the axes should restore it to a working state again. It might be somehow related to dragging too quickly as I never saw the issue if I just clicked around on different frames, only when dragging the slider.

Co-authored-by: Emmanuelle Gouillart <emma@plot.ly>
Co-authored-by: Juan Nunez-Iglesias <juan.nunez-iglesias@monash.edu>
Co-authored-by: Gregory R. Lee <grlee77@gmail.com>
  • Loading branch information
4 people committed Jan 25, 2023
1 parent 16bda70 commit c36570d
Showing 1 changed file with 49 additions and 21 deletions.
70 changes: 49 additions & 21 deletions doc/examples/applications/plot_3d_image_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
Explore 3D images (of cells)
============================

This tutorial is an introduction to three-dimensional image processing. Images
This tutorial is an introduction to three-dimensional image processing.
For a quick intro to 3D datasets, please refer to
:ref:`sphx_glr_auto_examples_data_plot_3d.py`.
Images
are represented as `numpy` arrays. A single-channel, or grayscale, image is a
2D matrix of pixel intensities of shape ``(n_row, n_col)``, where ``n_row``
(resp. ``n_col``) denotes the number of `rows` (resp. `columns`). We can
Expand Down Expand Up @@ -42,8 +45,13 @@
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
from scipy import ndimage as ndi

from skimage import exposure, io, util
import plotly
import plotly.express as px
from skimage import (
exposure, util
)
from skimage.data import cells3d


Expand Down Expand Up @@ -71,15 +79,18 @@
print(f'normalized spacing: {spacing}\n')

#####################################################################
# Let us try and visualize the (3D) image with `io.imshow`.
# Let us try and visualize our 3D image. Unfortunately, many image viewers,
# such as matplotlib's `imshow`, are only capable of displaying 2D data. We
# can see that they raise an error when we try to view 3D data:

try:
io.imshow(data, cmap="gray")
fig, ax = plt.subplots()
ax.imshow(data, cmap='gray')
except TypeError as e:
print(str(e))

#####################################################################
# The `io.imshow` function can only display grayscale and RGB(A) 2D images.
# The `imshow` function can only display grayscale and RGB(A) 2D images.
# We can thus use it to visualize 2D planes. By fixing one axis, we can
# observe three different views of the image.

Expand All @@ -101,20 +112,15 @@ def show_plane(ax, plane, cmap="gray", title=None):

#####################################################################
# As hinted before, a three-dimensional image can be viewed as a series of
# two-dimensional planes. Let us write a helper function, `display`, to
# display 30 planes of our data. By default, every other plane is displayed.

# two-dimensional planes. Let us write a helper function, `display`, to create
# a montage of several planes. By default, every other plane is displayed.

def display(im3d, cmap="gray", step=2):
_, axes = plt.subplots(nrows=5, ncols=6, figsize=(16, 14))

vmin = im3d.min()
vmax = im3d.max()

for ax, image in zip(axes.flatten(), im3d[::step]):
ax.imshow(image, cmap=cmap, vmin=vmin, vmax=vmax)
ax.set_xticks([])
ax.set_yticks([])
def display(im3d, cmap='gray', step=2):
data_montage = util.montage(im3d[::step], padding_width=4, fill=np.nan)
_, ax = plt.subplots(figsize=(16, 14))
ax.imshow(data_montage, cmap=cmap)
ax.set_axis_off()


display(data)
Expand All @@ -124,10 +130,12 @@ def display(im3d, cmap="gray", step=2):
# Jupyter widgets. Let the user select which slice to display and show the
# position of this slice in the 3D dataset.
# Note that you cannot see the Jupyter widget at work in a static HTML page,
# as is the case in the scikit-image gallery. For the following piece of
# code to work, you need a Jupyter kernel running either locally or in the
# cloud: see the bottom of this page to either download the Jupyter notebook
# and run it on your computer, or open it directly in Binder.
# as is the case in the online version of this example. For the following
# piece of code to work, you need a Jupyter kernel running either locally or
# in the cloud: see the bottom of this page to either download the Jupyter
# notebook and run it on your computer, or open it directly in Binder. On top
# of an active kernel, you need a web browser: running the code in pure Python
# will not work either.


def slice_in_3D(ax, i):
Expand Down Expand Up @@ -308,3 +316,23 @@ def plot_hist(ax, data, title=None):
)

display(clipped_data)

#####################################################################
# Alternatively, we can explore these planes (slices) interactively using
# `Plotly Express <https://plotly.com/python/sliders/>`_.
# Note that this works in a static HTML page!

fig = px.imshow(data, animation_frame=0, binary_string=True)
fig.update_xaxes(showticklabels=False)
fig.update_yaxes(showticklabels=False)
fig.update_layout(
autosize=False,
width=500,
height=500,
coloraxis_showscale=False
)
# Drop animation buttons
fig['layout'].pop('updatemenus')
plotly.io.show(fig)

plt.show()

0 comments on commit c36570d

Please sign in to comment.