# -*- coding: utf-8 -*-
"""
Hyperspectral Imaging Explorers and GUI Tools
This module provides several interactive classes for exploring hyperspectral or volumetric image data, particularly for chemical imaging datasets.
It includes mouse-interactive tools for visualizing spectra, image slices, and intensity profiles. F
Classes:
- HyperSliceExplorer: Explore hyperspectral imaging data
- ImageSpectrumGUI: Explore hyperspectral imaging data
- InteractiveProfileExtraction: Extract 1D intensity profiles along a line in a 2D image
- InteractiveHyperProfileExtraction: Extract 1D and spectral profiles along a line in a hyperspectral volume
- ImageSpectrumFitGUI: Compare raw and fitted spectra from voxel data interactively
- chemimexplorer: A version of ImageSpectrumGUI which is used as embedded in jupyter notebooks
@author: Antony Vamvakeros
"""
import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage import map_coordinates
from matplotlib.lines import Line2D
[docs]
class HyperSliceExplorer():
"""
Interactive explorer for hyperspectral or volumetric imaging data.
Allows the user to click and move the mouse over a mean image view
to inspect voxel-level spectra and update an associated spectrum plot.
Users can visualize individual channel images and view spectral changes dynamically.
Particularly useful for visual inspection of 3D chemical imaging datasets.
"""
def __init__(self, data, xaxis=None, xaxislabel='Channels'):
"""
Initialize the HyperSliceExplorer object.
Sets up the internal state and prepares data structures for
interactive visualisation of 3D hyperspectral (or chemical imaging) data.
Parameters
----------
data : np.ndarray
A 3D array of shape (rows, cols, channels) representing the volumetric dataset
where each (x, y) pixel contains a spectrum.
xaxis : np.ndarray, optional
A 1D array representing the spectral axis (e.g., 2θ, q, or energy).
If None, defaults to a linear range [0, N) where N is the number of channels.
xaxislabel : str, optional
A string label for the x-axis used in spectrum plots. Defaults to 'Channels'.
Attributes
----------
data : np.ndarray
The input hyperspectral dataset.
xaxis : np.ndarray
Spectral axis used for plotting spectra.
xaxislabel : str
Label shown on spectrum plots.
cmap : str
Colormap used for image rendering.
imoi : np.ndarray
Mean image across all channels.
mdp : np.ndarray
Mean spectrum across all spatial positions.
sdp : np.ndarray
Summed spectrum across all spatial positions.
dproi : np.ndarray
Most recently selected spectrum (by click).
selectedChannels : list or int
Channel(s) currently selected for image display.
selectedVoxels : list
List of selected voxel positions.
map_fig, map_axes, plot_fig, plot_axes : matplotlib components
Handles to matplotlib figures and axes for image and spectral views.
"""
self.data = data
if xaxis is None:
self.xaxis = np.arange(0,data.shape[2])
else:
self.xaxis = xaxis
self.xaxislabel = xaxislabel
self.mapper = []
self.map_fig = []
self.map_axes = []
self.map_data = []
self.mapHoldState= 0;
self.plotter = []
self.plot_fig = []
self.plot_axes = []
self.currentCurve = 0;
self.plot = []
self.plotHoldState= 0;
self.selectedChannels = []
self.selectedVoxels = np.empty(0,dtype=object)
self.meanSpectrum = np.mean(np.mean(data, axis = 0), axis = 0)
self.cmap = 'jet'
self.imoi = np.mean(self.data,axis=2)
self.mdp = np.mean(np.mean(self.data,axis=1),axis=0)
self.sdp = np.sum(np.sum(self.data,axis=1),axis=0)
self.dproi = self.mdp
[docs]
def explore(self, cmap = 'jet'):
"""
Launch interactive exploration of hyperspectral data.
This method creates two interactive Matplotlib figures:
- A mean image view that supports mouse hovering and clicking to inspect voxel-level spectra.
- A spectrum plot that updates dynamically based on cursor position over the image or spectrum.
Users can click:
- On the image (left-click) to fix and label a specific voxel's spectrum.
- On the spectrum (right-click) to re-enable hover-based channel selection.
Parameters
----------
cmap : str, optional
Colormap used for image display. Default is 'jet'.
Notes
-----
- A vertical line (vCurve) indicates the current spectral channel under inspection.
- Uses Matplotlib event callbacks to manage interactive behavior.
- Updates `self.dproi`, `self.histogramCurve`, and `self.activeCurve` for spectrum display.
"""
self.cmap = cmap
self.mapper = plt;
self.map_fig = self.mapper.figure()
self.map_axes = self.map_fig.add_subplot(111)
self.map_data = self.mapper.imshow(self.imoi, cmap=self.cmap)
title = 'Mean image'
self.mapper.title(title, fontstyle='italic')
self.map_fig.canvas.mpl_connect('button_press_event', self.onMapClick)
self.map_fig.canvas.mpl_connect('motion_notify_event', self.onMapMoveEvent)
self.cb = self.map_fig.colorbar(self.map_data)
self.mapper.show()
self.mapper.draw()
self.plotter = plt
self.plot_fig = self.plotter.figure()
self.plot_axes = self.plot_fig.add_subplot(111)
self.plotter.legend()
self.plotter.title("Mean spectrum", fontstyle='italic')
self.plot_fig.canvas.mpl_connect('motion_notify_event', self.onPlotClick)
self.plot_cid = self.plot_fig.canvas.mpl_connect('motion_notify_event', self.onPlotMoveEvent)
self.plotter.show()
self.plotter.draw()
self.histogramCurve = self.plotter.plot(self.xaxis, self.mdp, label='Mean spectrum', color="b")
self.activeCurve = self.plotter.plot(self.xaxis, self.mdp, label='Mean spectrum', color="r")
########
self.vCurve = self.plotter.axvline(x=0, color="k")
#######
[docs]
def onMapClick(self, event): # SDMJ version
"""
Handle mouse click events on the image panel.
Left-click:
- Captures the coordinates of the clicked voxel (row, col).
- Extracts and displays the corresponding spectrum in the spectral plot.
- Updates the label to show voxel location.
Right-click:
- Hides the fixed spectrum curve.
- Rescales the y-axis to fit the last selected voxel spectrum.
Parameters
----------
event : matplotlib.backend_bases.MouseEvent
The mouse click event containing button info and coordinates.
Notes
-----
- Left-click outside the axes resets the spectrum plot to the mean spectrum.
- This function updates `self.dproi`, `self.histogramCurve`, and plot limits.
"""
if event.button == 1:
if event.inaxes:
self.col = int(event.xdata.round())
self.row = int(event.ydata.round())
self.dproi = self.data[self.row,self.col,:]
self.histogramCurve[0].set_data(self.xaxis, self.dproi)
self.histogramCurve[0].set_label(str(self.row)+','+str(self.col))
self.histogramCurve[0].set_visible(True)
self.plotter.legend()
self.plotter.show()
self.plotter.draw()
else:
self.selectedVoxels = np.empty(0,dtype=object)
self.plot_axes.clear() # not fast
self.plotter.plot(self.xaxis, self.mdp, label='Mean spectrum')
self.plotter.legend()
self.plotter.draw()
if event.button == 3:
if event.inaxes:
self.histogramCurve[0].set_visible(False)
self.plot_axes.set_xlim(self.xaxis[0],self.xaxis[-1])
self.plot_axes.set_ylim(0,np.max(self.dproi))
self.plotter.show()
self.plotter.draw()
[docs]
def onMapMoveEvent(self, event):
"""
Handle mouse movement over the image panel.
When the cursor is over the image axes:
- Displays the spectrum corresponding to the hovered voxel (row, col).
- Updates the red "activeCurve" line in the spectrum plot.
- Updates the legend to show voxel coordinates.
- Dynamically adjusts the y-axis limits based on the hovered spectrum.
When the cursor leaves the image axes:
- Hides the red "activeCurve".
- Resets y-axis to fit the last clicked spectrum (`self.dproi`).
Parameters
----------
event : matplotlib.backend_bases.MouseEvent
The mouse motion event with axis and coordinate info.
Notes
-----
This function provides real-time spectral feedback for hyperspectral data exploration.
"""
if event.inaxes:
col = int(event.xdata.round())
row = int(event.ydata.round())
dproi = self.data[row,col,:]
self.activeCurve[0].set_data(self.xaxis, dproi)
self.activeCurve[0].set_label(str(row)+','+str(col))
self.activeCurve[0].set_visible(True)
if np.max(dproi)>0:
self.plot_axes.set_ylim(0,np.max(dproi))
else:
self.activeCurve[0].set_visible(False)
self.activeCurve[0].set_label('')
self.plot_axes.set_ylim(0,np.max(self.dproi))
self.plotter.legend()
self.plotter.draw()
[docs]
def onPlotMoveEvent(self, event):
"""
Handle mouse movement over the spectral plot panel.
Updates the left-hand image panel in real-time to show the spatial image
corresponding to the spectral channel under the cursor.
Actions performed:
- Determines the nearest spectral channel (index `nx`) based on x-coordinate.
- Updates the image (`imoi`) to show the spatial slice at `nx`.
- Updates the title with channel and axis label (e.g., energy, 2θ).
- Replaces the colorbar and redraws the figure.
- Moves the vertical guide line (`vCurve`) on the spectrum plot to follow the cursor.
Parameters
----------
event : matplotlib.backend_bases.MouseEvent
The mouse motion event with axis and coordinate information.
"""
if event.inaxes:
nx = np.argmin(np.abs(self.xaxis-event.xdata))
if nx<0:
nx = 0
elif nx>len(self.xaxis):
nx = len(self.xaxis)-1
self.selectedChannels = nx
self.map_axes.clear() # not fast
self.imoi = self.data[:,:,nx]
self.map_data = self.map_axes.imshow(self.imoi, cmap = self.cmap)
title = "Channel = %d; %s = %.3f" % (nx, self.xaxislabel, self.xaxis[nx])
try:
self.cb.remove()
except:
pass
self.cb = self.map_fig.colorbar(self.map_data)
self.map_axes.set_title(title)
self.mapper.draw_all()
self.vCurve.set_xdata(event.xdata)
self.plotter.draw()
[docs]
def onPlotClick(self, event):
"""
Handle mouse click events on the spectral plot.
Left-click (button 1) disables channel hover updates by disconnecting
the motion event that controls image updates.
Right-click (button 3) re-enables channel hover updates by reconnecting
the motion event handler to update the image view based on cursor position.
Parameters
----------
event : matplotlib.backend_bases.MouseEvent
The mouse click event.
"""
if event.button == 1:
self.plot_fig.canvas.mpl_disconnect(self.plot_cid)
elif event.button == 3:
self.plot_cid = self.plot_fig.canvas.mpl_connect('motion_notify_event', self.onPlotMoveEvent)
self.plotter.show()
self.plotter.draw()
[docs]
def update(self):
"""
Refresh the left-hand image display based on the currently selected channels.
If no specific channels are selected, displays the mean image across all channels.
If a single channel is selected, displays that channel.
If multiple channels are selected, displays the mean across those channels.
Also updates the image title and redraws the figure with the current colormap.
"""
self.map_axes.clear() # not fast
# this bit is messy
self.imoi = np.mean(self.data, axis = 2)
if (not self.selectedChannels):
self.map_axes.imshow(self.imoi ,cmap=self.cmap)
title = 'Mean image'
else:
if self.selectedChannels.size == 1:
self.map_axes.imshow(self.data[:,:,self.selectedChannels],cmap=self.cmap)
title = "Channel = %d; %s = %.3f" % (self.nx, self.xaxislabel, self.xaxis[self.nx])
if self.selectedChannels.size > 1:
self.map_axes.imshow(np.mean(self.data[:,:,self.selectedChannels],2),cmap=self.cmap)
title = self.name+' '+'mean of selected channels'
self.map_axes.set_title(title)
self.mapper.show()
self.mapper.draw_all()
[docs]
class ImageSpectrumGUI:
"""
Interactive viewer for 2D hyperspectral images (rows x cols x channels).
The left pane shows an image for a chosen spectral band; the right pane
shows the spectrum at the current mouse position. Hovering over the image
updates the spectrum to the voxel (x, y) under the cursor. Hovering over
the spectrum updates the image to the spectral band under the cursor
(i.e., quick band scrubbing).
Right-click toggles “live” updates independently for each pane so you can
freeze either the image or the spectrum while inspecting the other.
If an optional boolean `mask` is provided, it is overlaid in color on the
image view to help assess segmentation quality (uses a semi-transparent
overlay). This is useful for QA of thresholding/segmentation against the
raw grayscale signal.
Notes
-----
- Expected input shape is (rows, cols, channels). Displayed images are
normalized for visualization only; underlying data are unchanged.
- The overlay `mask` should match `volume.shape[:2]` or `volume.shape`
(if channel-wise masks); when 3D it uses the slice `mask[:, :, band]`.
- Designed for quick exploratory analysis without precomputations; large
volumes are fine as only the needed slices/vectors are drawn on demand.
"""
def __init__(self, volume, cmap='jet', mask=None):
"""
Initialize the interactive viewer.
Parameters
----------
volume : ndarray
Hyperspectral data with shape (rows, cols, channels). The left
panel starts from the mean image over channels; the right panel
starts from the mean spectrum over (rows, cols). Moving the mouse
updates the other panel in real time.
cmap : str, optional
Matplotlib colormap for the image view (default: 'jet'). Ignored
when a color overlay is rendered by `mask` for a given band.
mask : ndarray of bool, optional
Segmentation mask to overlay on the image view for visual QA.
shape == (rows, cols, channels): the band-specific mask
`mask[:, :, band]` is shown when that band is displayed.
Interaction
-----------
- Hover over image: updates spectrum at cursor (x, y).
- Hover over spectrum: updates image to nearest spectral band index.
- Right-click on a panel: toggles live updates for that panel on/off.
"""
self.volume = volume
if mask is not None:
self.mask = mask
else:
self.mask = None
# Create main figure and subplots
self.fig, (self.ax_image, self.ax_spectrum) = plt.subplots(1, 2, figsize=(10, 5))
# Initialize with mean image and mean spectrum
mean_image = np.mean(volume, axis=2)
mean_spectrum = np.mean(volume, axis=(0, 1))
# Plot the mean image and mean spectrum
self.image = self.ax_image.imshow(mean_image.T, cmap=cmap)
self.spectrum, = self.ax_spectrum.plot(mean_spectrum, color='b')
# Set titles for image and spectrum
self.ax_image.set_title('Image')
self.ax_spectrum.set_title('Spectrum')
# Connect mouse hover events
self.fig.canvas.mpl_connect('motion_notify_event', self.update_plots)
self.fig.canvas.mpl_connect('button_press_event', self.toggle_real_time)
# Initialize real-time update flags
self.image_real_time_update = True
self.spectrum_real_time_update = True
[docs]
def update_plots(self, event):
"""
Handle mouse movement over the figure to update image or spectrum.
If hovering over the image panel and image updates are enabled,
it displays the spectrum of the voxel under the cursor.
If hovering over the spectrum panel and spectrum updates are enabled,
it displays the image corresponding to the spectral bin under the cursor.
Parameters
----------
event : matplotlib.backend_bases.MouseEvent
Event object containing cursor position and axis context.
"""
if event.inaxes == self.ax_image:
if not self.image_real_time_update:
return
x, y = int(event.xdata), int(event.ydata)
# Check if the mouse position is within the image dimensions
image_width, image_height, _ = self.volume.shape
if x >= 0 and x < image_width and y >= 0 and y < image_height:
# Get the spectrum from the volume
spectrum = self.volume[x, y, :]
# Update the spectrum plot
self.spectrum.set_ydata(spectrum)
self.ax_spectrum.relim()
self.ax_spectrum.autoscale_view()
# Set title with coordinates
self.ax_spectrum.set_title(f'Spectrum (x={x}, y={y})')
elif event.inaxes == self.ax_spectrum:
if not self.spectrum_real_time_update:
return
index = int(event.xdata)
# Get the image from the volume
image = self.volume[:, :, index]
if self.mask is not None:
image = label2rgb(self.mask[:, :, index], image=image,
bg_label=0, alpha=0.35,
colors=[(1, 0, 0)])
# Update the image display
if self.mask is not None:
self.image.set_data(image.transpose(1,0,2))
else:
self.image.set_data(image.T)
self.ax_image.relim()
self.ax_image.autoscale_view()
# Set title with bin
self.ax_image.set_title(f'Image (Bin={index})')
# Set color limits based on the current image
self.image.set_clim(np.min(image), np.max(image))
self.fig.canvas.draw()
[docs]
def toggle_real_time(self, event):
"""
Toggle real-time interactivity for image or spectrum on right-click.
Disables/enables automatic updates depending on the axis clicked.
Parameters
----------
event : matplotlib.backend_bases.MouseEvent
Mouse button press event. Right-click toggles interactivity.
"""
if event.button == 3:
if event.inaxes == self.ax_image:
self.image_real_time_update = not self.image_real_time_update
elif event.inaxes == self.ax_spectrum:
self.spectrum_real_time_update = not self.spectrum_real_time_update
[docs]
def bresenham(y0, x0, y1, x1):
"""
Generate pixel coordinates of a line between two points using Bresenham's algorithm.
This discrete algorithm is efficient for rasterizing lines in 2D arrays. It ensures that
only valid integer pixel coordinates are returned, which is useful for fast profile
extraction from images.
Parameters
----------
y0, x0 : int
Row and column of the starting point.
y1, x1 : int
Row and column of the ending point.
Returns
-------
coords : ndarray, shape (2, N)
Array of (x, y) coordinates along the line as two 1D arrays stacked vertically.
These can be used directly to index into 2D arrays: e.g., image[coords[1], coords[0]]
"""
dx = abs(x1 - x0)
dy = abs(y1 - y0)
steep = dy > dx
if steep:
x0, y0 = y0, x0
x1, y1 = y1, x1
if x0 > x1:
x0, x1 = x1, x0
y0, y1 = y1, y0
dx = x1 - x0
dy = y1 - y0
error = int(dx / 2.0)
ystep = 1 if y0 < y1 else -1
y = y0
points = []
for x in range(x0, x1 + 1):
coord = (y, x) if steep else (x, y)
points.append(coord)
error -= abs(dy)
if error < 0:
y += ystep
error += dx
return np.array(points).T
[docs]
class ImageSpectrumFitGUI:
"""
GUI for visual comparison between raw and fitted hyperspectral data.
Displays original and fitted spectra from a 3D volume on a voxel-by-voxel basis.
Supports dynamic inspection by mouse interaction. Useful for verifying
the quality of spectral fitting or peak decomposition across a sample.
"""
def __init__(self, volume, volfit):
"""
Initialize the ImageSpectrumFitGUI for raw vs. fitted spectrum comparison.
Parameters
----------
volume : ndarray
3D array of original hyperspectral data with shape (rows, cols, channels).
volfit : ndarray
3D array of fitted spectral data with the same shape as `volume`.
The GUI displays both raw and fitted spectra for user-selected voxels.
"""
self.volume = volume
self.volfit = volfit
# Create main figure and subplots
self.fig, (self.ax_image, self.ax_spectrum) = plt.subplots(1, 2, figsize=(10, 5))
# Initialize with mean image and mean spectrum
mean_image = np.mean(volume, axis=2)
mean_spectrum = np.mean(volume, axis=(0, 1))
mean_spectrum_fit = np.mean(volfit, axis=(0, 1))
# Plot the mean image and mean spectrum
self.image = self.ax_image.imshow(mean_image.T, cmap='gray')
self.spectrum, = self.ax_spectrum.plot(mean_spectrum, 'bo', label='Original Spectrum')
self.spectrum_fit, = self.ax_spectrum.plot(mean_spectrum_fit, 'r-', label='Fit Spectrum')
# Set titles for image and spectrum
self.ax_image.set_title('Image')
self.ax_spectrum.set_title('Spectrum')
# Add legend to the spectrum plot
self.ax_spectrum.legend()
# Connect mouse hover events
self.fig.canvas.mpl_connect('motion_notify_event', self.update_plots)
self.fig.canvas.mpl_connect('button_press_event', self.toggle_real_time)
# Initialize real-time update flags
self.image_real_time_update = True
self.spectrum_real_time_update = True
[docs]
def update_plots(self, event):
"""
Update the displayed spectrum and image based on mouse hover.
When the cursor hovers over the image panel, updates the spectrum
panel to show the corresponding raw and fitted spectra at that voxel.
When hovering over the spectrum panel, switches the image display
to show the spatial distribution at the selected spectral bin.
Parameters
----------
event : matplotlib.backend_bases.MouseEvent
The mouse event object containing position and axes info.
"""
if event.inaxes == self.ax_image:
if not self.image_real_time_update:
return
x, y = int(event.xdata), int(event.ydata)
# Check if the mouse position is within the image dimensions
image_width, image_height, _ = self.volume.shape
if x >= 0 and x < image_width and y >= 0 and y < image_height:
# Get the spectrum from the volumes
spectrum = self.volume[x, y, :]
spectrum_fit = self.volfit[x, y, :]
# Update the spectrum plot with both original and fit
self.spectrum.set_ydata(spectrum)
self.spectrum_fit.set_ydata(spectrum_fit)
self.ax_spectrum.relim()
self.ax_spectrum.autoscale_view()
# Set title with coordinates
self.ax_spectrum.set_title(f'Spectrum (x={x}, y={y})')
elif event.inaxes == self.ax_spectrum:
if not self.spectrum_real_time_update:
return
index = int(event.xdata)
# Get the image from the volume
image = self.volume[:, :, index]
# Update the image display
self.image.set_data(image.T)
self.ax_image.relim()
self.ax_image.autoscale_view()
# Set title with bin
self.ax_image.set_title(f'Image (Bin={index})')
# Set color limits based on the current image
self.image.set_clim(np.min(image), np.max(image))
self.fig.canvas.draw()
[docs]
def toggle_real_time(self, event):
"""
Toggle real-time updates for spectrum and image panels on right-click.
Right-clicking over the image panel enables/disables updates triggered by
mouse motion over it. Same applies for the spectrum panel.
Parameters
----------
event : matplotlib.backend_bases.MouseEvent
The mouse button press event.
"""
if event.button == 3:
if event.inaxes == self.ax_image:
self.image_real_time_update = not self.image_real_time_update
elif event.inaxes == self.ax_spectrum:
self.spectrum_real_time_update = not self.spectrum_real_time_update
[docs]
class chemimexplorer:
"""
Lightweight GUI for Jupyter-based exploration of chemical imaging volumes.
Provides linked spatial and spectral views for both raw and optionally
fitted datasets. Supports mouse-based interaction to inspect individual
spectra. Optimized for use within notebook environments.
"""
def __init__(self, volume, fitted=None):
"""
Initialize the chemimexplorer widget for use in notebooks.
Parameters
----------
volume : ndarray
2D hyperspectral imaging dataset with shape (rows, cols, channels).
fitted : ndarray, optional
Optional 3D array of fitted spectral data with the same shape as `volume`.
If provided, fitted spectra are displayed alongside raw spectra.
"""
self.volume = volume
self.fitted = fitted
# Create main figure and subplots
self.fig, (self.ax_image, self.ax_spectrum) = plt.subplots(1, 2, figsize=(10, 5))
# Initialize with mean image and mean spectrum
mean_image = np.mean(volume, axis=2)
mean_spectrum = np.mean(volume, axis=(0, 1))
if self.fitted is not None:
mean_spectrum_fitted = np.mean(self.fitted, axis=(0, 1))
# Plot the mean image and mean spectrum
self.image = self.ax_image.imshow(mean_image.T, cmap='gray')
if self.fitted is not None:
self.spectrum, = self.ax_spectrum.plot(mean_spectrum, 'b+')
self.spectrum_fitted, = self.ax_spectrum.plot(mean_spectrum_fitted, color='r')
else:
self.spectrum, = self.ax_spectrum.plot(mean_spectrum, color='b')
# Set titles for image and spectrum
self.ax_image.set_title('Image')
self.ax_spectrum.set_title('Histogram')
# Connect mouse hover events
self.fig.canvas.mpl_connect('motion_notify_event', self.update_plots)
self.fig.canvas.mpl_connect('button_press_event', self.toggle_real_time)
# Initialize real-time update flags
self.image_real_time_update = True
self.spectrum_real_time_update = True
[docs]
def update_plots(self, event):
"""
Update the displayed spectrum and image based on mouse hover.
- Hovering over the image updates the spectrum view for the selected voxel.
- Hovering over the spectrum panel updates the image view for the selected channel.
Parameters
----------
event : matplotlib.backend_bases.MouseEvent
The mouse event object containing position and axes info.
"""
if event.inaxes == self.ax_image:
if not self.image_real_time_update:
return
x, y = int(event.xdata), int(event.ydata)
# Check if the mouse position is within the image dimensions
image_width, image_height, _ = self.volume.shape
if x >= 0 and x < image_width and y >= 0 and y < image_height:
# Get the spectrum from the volume
spectrum = self.volume[x, y, :]
# Update the spectrum plot
self.spectrum.set_ydata(spectrum)
if self.fitted is not None:
spectrum_fitted = self.fitted[x, y, :]
self.spectrum_fitted.set_ydata(spectrum_fitted)
self.ax_spectrum.relim()
self.ax_spectrum.autoscale_view()
# Set title with coordinates
self.ax_spectrum.set_title(f'Histogram (x={x}, y={y})')
elif event.inaxes == self.ax_spectrum:
if not self.spectrum_real_time_update:
return
index = int(event.xdata)
# Get the image from the volume
image = self.volume[:, :, index]
# Update the image display
self.image.set_data(image.T)
self.ax_image.relim()
self.ax_image.autoscale_view()
# Set title with bin
self.ax_image.set_title(f'Image (Bin={index})')
# Set color limits based on the current image
self.image.set_clim(np.min(image), np.max(image))
self.fig.canvas.draw()
[docs]
def toggle_real_time(self, event):
"""
Toggle real-time updates for image and spectrum panels on right-click.
Right-click over the image disables/enables updates from hovering on it.
Same applies for the spectrum panel.
Parameters
----------
event : matplotlib.backend_bases.MouseEvent
Mouse click event used to toggle interactivity.
"""
if event.button == 3:
if event.inaxes == self.ax_image:
self.image_real_time_update = not self.image_real_time_update
elif event.inaxes == self.ax_spectrum:
self.spectrum_real_time_update = not self.spectrum_real_time_update