Data visualization¶
The object returned by load()
, a BaseSignal
instance, has a plot()
method that is powerful and
flexible tools to visualize n-dimensional data. In this chapter, the
visualisation of multidimensional data is exemplified with two experimental
datasets: an EELS spectrum image and an EDX dataset consisting of a secondary
electron emission image stack and a 3D hyperspectrum , both simultaneously
acquired by recording two signals in parallel in a FIB/SEM.
>>> s = hs.load('YourDataFilenameHere')
>>> s.plot()
if the object is single spectrum or an image one window will appear when calling the plot method.
Multidimensional spectral data¶
If the object is a 1D or 2D spectrum-image (i.e. with 2 or 3 dimensions when including energy) two figures will appear, one containing a plot of the spectrum at the current coordinates and the other an image of the data summed over its spectral dimension if 2D or an image with the spectral dimension in the x-axis if 1D:
To change the current coordinates, click on the pointer (which will be a line or a square depending on the dimensions of the data) and drag it around. It is also possible to move the pointer by using the numpad arrows when numlock is on and the spectrum or navigator figure is selected.When using the numpad arrows the PageUp and PageDown keys change the size of the step.
An extra cursor can be added by pressing the e
key. Pressing e
once
more will disable the extra cursor:
Sometimes the default size of the rectangular cursors used to navigate images
can be too small to be dragged or even seen. It
is possible to change the size of the cursors by pressing the +
and -
keys when the navigator window is selected.
key | function |
---|---|
e | Switch second pointer on/off |
Arrows | Change coordinates |
PageUp | Increase step size |
PageDown | Decrease step size |
+ |
Increase pointer size |
- |
Decrease pointer size |
h |
Launch the contrast adjustment tool (only for Signal2D) |
To close all the figures run the following command:
>>> import matplotlib.pyplot as plt
>>> plt.close('all')
Note
This is a matplotlib command. Matplotlib is the library that HyperSpy uses to produce the plots. You can learn how to pan/zoom and more in the matplotlib documentation
Multidimensional image data¶
Equivalently, if the object is a 1D or 2D image stack two figures will appear, one containing a plot of the image at the current coordinates and the other a spectrum or an image obtained by summing over the image dimensions:
The same keys can be used to explore an image stack.
Customising image plot¶
New in version 0.8.
The image plot can be customised by passing additional arguments when plotting. Colorbar, scalebar and contrast controls are HyperSpy-specific, however matplotlib.imshow arguments are supported as well:
>>> import scipy
>>> img = hs.signals.Signal2D(scipy.misc.ascent())
>>> img.plot(colorbar=True, scalebar=False,
... axes_ticks=True, cmap='RdYlBu_r', saturated_pixels=0)
New in version 1.1.2.
Same options can be passed to the navigator, albeit separately, by specifying
them as a dictionary in navigator_kwds
argument when plotting:
>>> import numpy as np
>>> import scipy
>>> im = hs.signals.Signal2D(scipy.misc.ascent())
>>> ims = hs.signals.BaseSignal(np.random.rand(15,13)).T * im
>>> ims.metadata.General.title = 'My Images'
>>> ims.plot(colorbar=False,
... scalebar=False,
... axes_ticks=False,
... cmap='viridis',
... navigator_kwds=dict(colorbar=True,
... scalebar_color='red',
... cmap='Blues',
... axes_ticks=False)
... )
New in version 0.8.1.
When plotting using divergent colormaps, if centre_colormap
is True
(default) the constrast is automatically adjusted so that zero corresponds to
the center of the colormap (usually white). This can be useful e.g. when
displaying images that contain pixels with both positive and negative values.
The following example shows the effect of centering the color map:
>>> x = np.linspace(-2 * np.pi, 2 * np.pi, 128)
>>> xx, yy = np.meshgrid(x, x)
>>> data1 = np.sin(xx * yy)
>>> data2 = data1.copy()
>>> data2[data2 < 0] /= 4
>>> im = hs.signals.Signal2D([data1, data2])
>>> hs.plot.plot_images(im, cmap="RdBu", tight_layout=True)
The same example with the feature disabled:
>>> x = np.linspace(-2 * np.pi, 2 * np.pi, 128)
>>> xx, yy = np.meshgrid(x, x)
>>> data1 = np.sin(xx * yy)
>>> data2 = data1.copy()
>>> data2[data2 < 0] /= 4
>>> im = hs.signals.Signal2D([data1, data2])
>>> hs.plot.plot_images(im, centre_colormap=False,
... cmap="RdBu", tight_layout=True)
Using Mayavi to visualize 3D data¶
Data files used in the following examples can be downloaded using
>>> from urllib.request import urlretrieve
>>> url = 'http://cook.msm.cam.ac.uk//~hyperspy//EDS_tutorial//'
>>> urlretrieve(url + 'Ni_La_intensity.hdf5', 'Ni_La_intensity.hdf5')
Note
See also the EDS tutorials .
Although HyperSpy does not currently support plotting when signal_dimension is greater than 2, Mayavi can be used for this purpose.
In the following example we also use scikit-image
for noise reduction. More details about
get_lines_intensity()
method can be
found in EDS lines intensity.
>>> from mayavi import mlab
>>> ni = hs.load('Ni_La_intensity.hdf5')
>>> mlab.figure()
>>> mlab.contour3d(ni.data, contours=[85])
>>> mlab.outline(color=(0, 0, 0))
Note
See also the SEM EDS tutorials .
Note
The sample and the data used in this chapter are described in P. Burdet, et al., Ultramicroscopy, 148, p. 158-167 (2015).
Plotting multiple signals¶
HyperSpy provides three functions to plot multiple signals (spectra, images or
other signals): plot_images()
,
plot_spectra()
, and
plot_signals()
in the utils.plot
package.
Plotting several images¶
New in version 0.8.
plot_images()
is used to plot several images in the
same figure. It supports many configurations and has many options available
to customize the resulting output. The function returns a list of
matplotlib axes,
which can be used to further customize the figure. Some examples are given
below. Plots generated from another installation may look slightly different
due to matplotlib
GUI backends and default font sizes. To change the
font size globally, use the command matplotlib.rcParams.update({'font
.size': 8})
.
A common usage for plot_images()
is to view the
different slices of a multidimensional image (a hyperimage):
>>> import scipy
>>> image = hs.signals.Signal2D([scipy.misc.ascent()]*6)
>>> angles = hs.signals.BaseSignal(range(10,70,10))
>>> image.map(scipy.ndimage.rotate, angle=angles.T, reshape=False)
>>> hs.plot.plot_images(image, tight_layout=True)
This example is explained in Signal iterator.
By default, plot_images()
will attempt to auto-label
the images based on the Signal titles. The labels (and title) can be
customized with the suptitle and label arguments. In this example, the
axes labels and the ticks are also disabled with axes_decor:
>>> import scipy
>>> image = hs.signals.Signal2D([scipy.misc.ascent()]*6)
>>> angles = hs.signals.BaseSignal(range(10,70,10))
>>> image.map(scipy.ndimage.rotate, angle=angles.T, reshape=False)
>>> hs.plot.plot_images(
... image, suptitle='Turning Ascent', axes_decor='off',
... label=['Rotation {}$^\degree$'.format(angles.data[i]) for
... i in range(angles.data.shape[0])], colorbar=None)
plot_images()
can also be used to easily plot a list
of Images, comparing different Signals, including RGB images. This
example also demonstrates how to wrap labels using labelwrap (for preventing
overlap) and using a single colorbar for all the Images, as opposed to
multiple individual ones:
>>> import scipy
>>> import numpy as np
>>>
>>> # load red channel of raccoon as an image
>>> image0 = hs.signals.Signal2D(scipy.misc.face()[:,:,0])
>>> image0.metadata.General.title = 'Rocky Raccoon - R'
>>>
>>> # load ascent into a length 6 hyper-image
>>> image1 = hs.signals.Signal2D([scipy.misc.ascent()]*6)
>>> angles = hs.signals.BaseSignal(np.arange(10,70,10)).T
>>> image1.map(scipy.ndimage.rotate, angle=angles,
... show_progressbar=False, reshape=False)
>>> image1.data = np.clip(image1.data, 0, 255) # clip data to int range
>>>
>>> # load green channel of raccoon as an image
>>> image2 = hs.signals.Signal2D(scipy.misc.face()[:,:,1])
>>> image2.metadata.General.title = 'Rocky Raccoon - G'
>>>
>>> # load rgb image of the raccoon
>>> rgb = hs.signals.Signal1D(scipy.misc.face())
>>> rgb.change_dtype("rgb8")
>>> rgb.metadata.General.title = 'Raccoon - RGB'
>>>
>>> images = [image0, image1, image2, rgb]
>>> for im in images:
... ax = im.axes_manager.signal_axes
... ax[0].name, ax[1].name = 'x', 'y'
... ax[0].units, ax[1].units = 'mm', 'mm'
>>> hs.plot.plot_images(images, tight_layout=True,
... colorbar='single', labelwrap=20)
Data files used in the following example can be downloaded using (These data are described in [Rossouw2015].
>>> #Download the data (1MB)
>>> from urllib.request import urlretrieve, urlopen
>>> from zipfile import ZipFile
>>> files = urlretrieve("https://www.dropbox.com/s/ecdlgwxjq04m5mx/"
... "HyperSpy_demos_EDS_TEM_files.zip?raw=1",
... "./HyperSpy_demos_EDX_TEM_files.zip")
>>> with ZipFile("HyperSpy_demos_EDX_TEM_files.zip") as z:
>>> z.extractall()
Another example for this function is plotting EDS line intensities see EDS chapter. One can use the following commands to get a representative figure of the X-ray line intensities of an EDS spectrum image. This example also demonstrates changing the colormap (with cmap),adding scalebars to the plots (with scalebar), and changing the padding between the images. The padding is specified as a dictionary, which is used to call subplots_adjust method of matplotlib (see documentation).
>>> si_EDS = hs.load("core_shell.hdf5")
>>> im = si_EDS.get_lines_intensity()
>>> hs.plot.plot_images(hs.transpose(im[0], im[1]),
... tight_layout=True, cmap='RdYlBu_r', axes_decor='off',
... colorbar='single', saturated_pixels=2, scalebar='all',
... scalebar_color='black', suptitle_fontsize=16,
... padding={'top':0.8, 'bottom':0.10, 'left':0.05,
... 'right':0.85, 'wspace':0.20, 'hspace':0.10})
Note
This padding can also be changed interactively by clicking on the button in the GUI (button may be different when using different graphical backends).
Plotting several spectra¶
plot_spectra()
is used to plot several spectra in the
same figure. It supports different styles, the default
being “overlap”. The default style is configurable in preferences.
In the following example we create a list of 9 single spectra (gaussian
functions with different sigma values) and plot them in the same figure using
plot_spectra()
. Note that, in this case, the legend
labels are taken from the individual spectrum titles. By clicking on the
legended line, a spectrum can be toggled on and off.
>>> s = hs.signals.Signal1D(np.zeros((200)))
>>> s.axes_manager[0].offset = -10
>>> s.axes_manager[0].scale = 0.1
>>> m = s.create_model()
>>> g = hs.model.components1D.Gaussian()
>>> m.append(g)
>>> gaussians = []
>>> labels = []
>>> for sigma in range(1, 10):
... g.sigma.value = sigma
... gs = m.as_signal()
... gs.metadata.General.title = "sigma=%i" % sigma
... gaussians.append(gs)
...
>>> hs.plot.plot_spectra(gaussians,legend='auto')
<matplotlib.axes.AxesSubplot object at 0x4c28c90>
Another style, “cascade”, can be useful when “overlap” results in a plot that is too cluttered e.g. to visualize changes in EELS fine structure over a line scan. The following example shows how to plot a cascade style figure from a spectrum, and save it in a file:
>>> import scipy.misc
>>> s = hs.signals.Signal1D(scipy.misc.ascent()[100:160:10])
>>> cascade_plot = hs.plot.plot_spectra(s, style='cascade')
>>> cascade_plot.figure.savefig("cascade_plot.png")
The “cascade” style has a padding option. The default value, 1, keeps the individual plots from overlapping. However in most cases a lower padding value can be used, to get tighter plots.
Using the color argument one can assign a color to all the spectra, or specific colors for each spectrum. In the same way, one can also assign the line style and provide the legend labels:
>>> import scipy.misc
>>> s = hs.signals.Signal1D(scipy.misc.ascent()[100:160:10])
>>> color_list = ['red', 'red', 'blue', 'blue', 'red', 'red']
>>> line_style_list = ['-','--','steps','-.',':','-']
>>> hs.plot.plot_spectra(s, style='cascade', color=color_list,
>>> line_style=line_style_list,legend='auto')
A simple extension of this functionality is to customize the colormap that is used to generate the list of colors. Using a list comprehension, one can generate a list of colors that follows a certain colormap:
>>> import scipy.misc
>>> fig, axarr = plt.subplots(1,2)
>>> s1 = hs.signals.Signal1D(scipy.misc.ascent()[100:160:10])
>>> s2 = hs.signals.Signal1D(scipy.misc.ascent()[200:260:10])
>>> hs.plot.plot_spectra(s1,
... style='cascade',
... color=[plt.cm.RdBu(i/float(len(s1)-1))
... for i in range(len(s1))],
... ax=axarr[0],
... fig=fig)
>>> hs.plot.plot_spectra(s2,
... style='cascade',
... color=[plt.cm.summer(i/float(len(s1)-1))
... for i in range(len(s1))],
... ax=axarr[1],
... fig=fig)
>>> axarr[0].set_xlabel('RdBu (colormap)')
>>> axarr[1].set_xlabel('summer (colormap)')
>>> fig.canvas.draw()
There are also two other styles, “heatmap” and “mosaic”:
>>> import scipy.misc
>>> s = hs.signals.Signal1D(scipy.misc.ascent()[100:160:10])
>>> hs.plot.plot_spectra(s, style='heatmap')
>>> import scipy.misc
>>> s = hs.signals.Signal1D(scipy.misc.ascent()[100:120:10])
>>> hs.plot.plot_spectra(s, style='mosaic')
For the “heatmap” style, different matplotlib color schemes can be used:
>>> import matplotlib.cm
>>> import scipy.misc
>>> s = hs.signals.Signal1D(scipy.misc.ascent()[100:120:10])
>>> ax = hs.plot.plot_spectra(s, style="heatmap")
>>> ax.images[0].set_cmap(matplotlib.cm.plasma)
Any parameter that can be passed to matplotlib.pyplot.figure can also be used with plot_spectra() to allow further customization (when using the “overlap”, “cascade”, or “mosaic” styles). In the following example, dpi, facecolor, frameon, and num are all parameters that are passed directly to matplotlib.pyplot.figure as keyword arguments:
>>> import scipy.misc
>>> s = hs.signals.Signal1D(scipy.misc.ascent()[100:160:10])
>>> legendtext = ['Plot 0', 'Plot 1', 'Plot 2', 'Plot 3',
... 'Plot 4', 'Plot 5']
>>> cascade_plot = hs.plot.plot_spectra(
... s, style='cascade', legend=legendtext, dpi=60,
... facecolor='lightblue', frameon=True, num=5)
>>> cascade_plot.set_xlabel("X-axis")
>>> cascade_plot.set_ylabel("Y-axis")
>>> cascade_plot.set_title("Cascade plot")
>>> plt.draw()
The function returns a matplotlib ax object, which can be used to customize the figure:
>>> import scipy.misc
>>> s = hs.signals.Signal1D(scipy.misc.ascent()[100:160:10])
>>> cascade_plot = hs.plot.plot_spectra(s)
>>> cascade_plot.set_xlabel("An axis")
>>> cascade_plot.set_ylabel("Another axis")
>>> cascade_plot.set_title("A title!")
>>> plt.draw()
A matplotlib ax and fig object can also be specified, which can be used to put several subplots in the same figure. This will only work for “cascade” and “overlap” styles:
>>> import scipy.misc
>>> fig, axarr = plt.subplots(1,2)
>>> s1 = hs.signals.Signal1D(scipy.misc.ascent()[100:160:10])
>>> s2 = hs.signals.Signal1D(scipy.misc.ascent()[200:260:10])
>>> hs.plot.plot_spectra(s1, style='cascade',
... color='blue', ax=axarr[0], fig=fig)
>>> hs.plot.plot_spectra(s2, style='cascade',
... color='red', ax=axarr[1], fig=fig)
>>> fig.canvas.draw()
Plotting several signals¶
plot_signals()
is used to plot several signals at the
same time. By default the navigation position of the signals will be synced,
and the signals must have the same dimensions. To plot two spectra at the
same time:
>>> import scipy.misc
>>> s1 = hs.signals.Signal1D(scipy.misc.face()).as_signal1D(0).inav[:,:3]
>>> s2 = s1.deepcopy()*-1
>>> hs.plot.plot_signals([s1, s2])
The navigator can be specified by using the navigator argument, where the different options are “auto”, None, “spectrum”, “slider” or Signal. For more details about the different navigators, see navigator_options. To specify the navigator:
>>> import scipy.misc
>>> s1 = hs.signals.Signal1D(scipy.misc.face()).as_signal1D(0).inav[:,:3]
>>> s2 = s1.deepcopy()*-1
>>> hs.plot.plot_signals([s1, s2], navigator="slider")
Navigators can also be set differently for different plots using the navigator_list argument. Where the navigator_list be the same length as the number of signals plotted, and only contain valid navigator options. For example:
>>> import scipy.misc
>>> s1 = hs.signals.Signal1D(scipy.misc.face()).as_signal1D(0).inav[:,:3]
>>> s2 = s1.deepcopy()*-1
>>> s3 = hs.signals.Signal1D(np.linspace(0,9,9).reshape([3,3]))
>>> hs.plot.plot_signals([s1, s2], navigator_list=["slider", s3])
Several signals can also be plotted without syncing the navigation by using sync=False. The navigator_list can still be used to specify a navigator for each plot:
>>> import scipy.misc
>>> s1 = hs.signals.Signal1D(scipy.misc.face()).as_signal1D(0)[:,:3]
>>> s2 = s1.deepcopy()*-1
>>> hs.plot.plot_signals([s1, s2], sync=False,
... navigator_list=["slider", "slider"])
Markers¶
New in version 0.8.
HyperSpy provides an easy access to the main marker of matplotlib. The markers can be used in a static way
>>> import scipy.misc
>>> im = hs.signals.Signal2D(scipy.misc.ascent())
>>> m = hs.plot.markers.rectangle(x1=150, y1=100,
... x2=400, y2=400, color='red')
>>> im.add_marker(m)
By providing an array of positions, the marker can also change position when navigating the signal. In the following example, the local maxima are displayed for each R, G and B channel of a colour image.
>>> from skimage.feature import peak_local_max
>>> import scipy.misc
>>> ims = hs.signals.BaseSignal(scipy.misc.face()).as_signal2D([1,2])
>>> index = np.array([peak_local_max(im.data, min_distance=100,
... num_peaks=4)
... for im in ims])
>>> for i in range(4):
... m = hs.plot.markers.point(x=index[:, i, 1],
... y=index[:, i, 0], color='red')
... ims.add_marker(m)
The markers can be added to the navigator as well. In the following example, each slice of a 2D spectrum is tagged with a text marker on the signal plot. Each slice is indicated with the same text on the navigator.
>>> s = hs.signals.Signal1D(np.arange(100).reshape([10,10]))
>>> s.plot(navigator='spectrum')
>>> for i in range(s.axes_manager.shape[0]):
... m = hs.plot.markers.text(y=s.sum(-1).data[i]+5,
... x=i, text='abcdefghij'[i])
... s.add_marker(m, plot_on_signal=False)
>>> x = s.axes_manager.shape[-1]/2 #middle of signal plot
>>> m = hs.plot.markers.text(x=x, y=s.isig[x].data+2,
... text=[i for i in 'abcdefghij'])
>>> s.add_marker(m)
New in version 1.2: Permanent markers.
These markers can also be permanently added to a signal, which is saved in
metadata.Markers
:
>>> s = hs.signals.Signal2D(np.arange(100).reshape(10, 10))
>>> marker = hs.markers.point(5, 9)
>>> s.add_marker(marker, permanent=True)
>>> s.metadata.Markers
└── point = <marker.Point, point (x=5,y=9,color=black,size=20)>
>>> s.plot()
Markers can be removed by deleting them from the metadata
>>> s = hs.signals.Signal2D(np.arange(100).reshape(10, 10))
>>> marker = hs.markers.point(5, 9)
>>> s.add_marker(marker, permanent=True)
>>> s.metadata.Markers
└── point = <marker.Point, point (x=5,y=9,color=black,size=20)>
>>> del s.metadata.Markers.point
>>> s.metadata.Markers # Returns nothing
To suppress plotting of permanent markers, use plot_markers=False when calling s.plot:
>>> s = hs.signals.Signal2D(np.arange(100).reshape(10, 10))
>>> marker = hs.markers.point(5, 9)
>>> s.add_marker(marker, permanent=True, plot_marker=False)
>>> s.plot(plot_markers=False)
If the signal has a navigation dimension, the markers can be made to change as a function of the navigation index. For a signal with 1 navigation axis:
>>> s = hs.signals.Signal2D(np.arange(300).reshape(3, 10, 10))
>>> marker = hs.markers.point((5, 1, 2), (9, 8, 1), color='red')
>>> s.add_marker(marker, permanent=True)
Or for a signal with 2 navigation axes:
>>> s = hs.signals.Signal2D(np.arange(400).reshape(2, 2, 10, 10))
>>> marker = hs.markers.point(((5, 1), (1, 2)), ((2, 6), (9, 8)))
>>> s.add_marker(marker, permanent=True)
This can be extended to 4 (or more) navigation dimensions:
>>> s = hs.signals.Signal2D(np.arange(1600).reshape(2, 2, 2, 2, 10, 10))
>>> x = np.arange(16).reshape(2, 2, 2, 2)
>>> y = np.arange(16).reshape(2, 2, 2, 2)
>>> marker = hs.markers.point(x=x, y=y, color='red')
>>> s.add_marker(marker, permanent=True)
New in version 1.2: markers
keyword arguments can take an iterable in addition to single
marker.
If you want to add a large amount of markers at the same time we advise to add them as an iterable (list, tuple, …), which will be much faster:
>>> from numpy.random import random
>>> s = hs.signals.Signal2D(np.arange(300).reshape(3, 10, 10))
>>> markers = (hs.markers.point(tuple(random()*10 for i in range(3)),
... tuple(random()*10 for i in range(3)),
... size=30, color=np.random.rand(3,1))
... for i in range(500))
>>> s.add_marker(markers, permanent=True)
This can also be done using different types of markers
>>> from numpy.random import random
>>> s = hs.signals.Signal2D(np.arange(300).reshape(3, 10, 10))
>>> markers = []
>>> for i in range(200):
... markers.append(hs.markers.horizontal_line(
... tuple(random()*10 for i in range(3)),
... color=np.random.rand(3,1)))
... markers.append(hs.markers.vertical_line(
... tuple(random()*10 for i in range(3)),
... color=np.random.rand(3,1)))
... markers.append(hs.markers.point(
... tuple(random()*10 for i in range(3)),
... tuple(random()*10 for i in range(3)),
... color=np.random.rand(3,1)))
... markers.append(hs.markers.text(
... x=tuple(random()*10 for i in range(3)),
... y=tuple(random()*10 for i in range(3)),
... text=tuple("sometext" for i in range(3))))
>>> s.add_marker(markers, permanent=True)
Permanent markers are stored in the HDF5 file if the signal is saved:
>>> s = hs.signals.Signal2D(np.arange(100).reshape(10, 10))
>>> marker = hs.markers.point(2, 1, color='red')
>>> s.add_marker(marker, plot_marker=False, permanent=True)
>>> s.metadata.Markers
└── point = <marker.Point, point (x=2,y=1,color=red,size=20)>
>>> s.save("storing_marker.hdf5")
>>> s1 = hs.load("storing_marker.hdf5")
>>> s1.metadata.Markers
└── point = <hyperspy.drawing._markers.point.Point object at 0x7efcfadb06d8>