ShowEDS#

Open in Colab

ShowEDS is an interactive EDS/EELS spectrum-image explorer for cubes with shape (row, col, energy). It has two linked panels: a real-space base image with an element-map overlay, and a spectrum with a draggable energy band. Drag the energy band to update the map; drag or resize the real-space ROI to update the summed spectrum.

Use Elements to open the periodic table, select candidate elements, and snap the energy band to characteristic X-ray lines. Auto ID ranks nearby line candidates from the current ROI spectrum; treat those as suggestions, not chemistry proof.

This tutorial uses a small synthetic EDS cube so it runs anywhere without private data. ShowEDS is still experimental: the merged baseline supports synthetic/small cubes and the documented data-folder workflow, while direct native EMD sparse-stream loading remains under active real-data testing.

Tip

Run this exact notebook with the Colab badge above, or View or download this notebook on GitHub. For finished results, use HTML and file export to export interactive HTML or share a trusted notebook with widget state.

Hide synthetic data generation code

import numpy as np

rng = np.random.default_rng(7)
rows, cols, n_energy = 96, 96, 320
energy = np.linspace(0.12, 12.0, n_energy, dtype=np.float32)
rr, cc = np.mgrid[0:rows, 0:cols]

# Simple materials geometry: silicate background, calcium wedge, and gold particles.
silicate = ((rr + 0.55 * cc) < 96).astype(np.float32)
calcium = ((rr > 44) & (cc > 40)).astype(np.float32)
gold = np.zeros((rows, cols), dtype=np.float32)
for r0, c0, radius in [(28, 60, 10), (64, 34, 9), (68, 72, 12)]:
    gold += np.exp(-((rr - r0) ** 2 + (cc - c0) ** 2) / (2 * radius**2)).astype(np.float32)
gold = np.clip(gold, 0, 1)

base = 0.15 + 0.35 * silicate + 0.5 * calcium + 1.6 * gold
base += 0.03 * rng.standard_normal((rows, cols), dtype=np.float32)
base = np.clip(base, 0, None).astype(np.float32)
base = ((base - base.min()) / (np.ptp(base) + 1e-6)).astype(np.float32)

background = (1.5 + 18.0 * np.exp(-energy / 3.5)).astype(np.float32)
signal = background[None, None, :] * (0.45 + 0.75 * base[:, :, None])

def add_peak(center_keV, amplitude, spatial, sigma_keV=0.045):
    profile = np.exp(-0.5 * ((energy - center_keV) / sigma_keV) ** 2).astype(np.float32)
    signal[:] += amplitude * spatial[:, :, None] * profile[None, None, :]

add_peak(0.525, 38.0, 0.6 * silicate + 0.8 * calcium)  # O K
add_peak(1.740, 52.0, silicate)                         # Si K
add_peak(3.692, 72.0, calcium)                          # Ca K
add_peak(2.118, 90.0, gold, sigma_keV=0.035)             # Au M
add_peak(2.203, 48.0, gold, sigma_keV=0.035)             # Au M
add_peak(9.713, 40.0, gold, sigma_keV=0.060)             # Au L

cube = rng.poisson(np.clip(signal, 0, 65000)).astype(np.uint16)
cube.shape, cube.dtype, energy[[0, -1]]
((96, 96, 320), dtype('uint16'), array([ 0.12, 12.  ], dtype=float32))

Synthetic spectrum image#

The cube below is exact uint16 count data with real-space sampling attached to the widget. The widget keeps this small cube in notebook state, so after you save and reopen the notebook the widget can still render without rerunning the cell.

from quantem.widget import ShowEDS

widget = ShowEDS(
    cube,
    energy,
    base_image=base,
    sampling=(0.18, 0.18),
    units=("nm", "nm"),
    title="Synthetic EDS spectrum image",
    energy=2.123,
    width=0.18,
    element_label="Au M",
    candidate_elements=["O", "Si", "Ca", "Au"],
    selected_elements=["Au", "Ca"],
    panel_width_px=420,
    spectrum_width_px=650,
    spectrum_height_px=250,
    log_spectrum=True,
)
widget

Save, reopen, and share#

In JupyterLab, use Cmd+S after interacting. The saved notebook records the selected energy band, ROI, log toggle, overlay opacity, contrast range, and panel sizes. Reopening the notebook restores the saved view. Rerunning the cell recreates the widget from the original parameters.

For a small cube like this one, export_html writes a single-file exact interactive HTML file.

export_path = widget.export_html("showeds_synthetic.html")
widget.export_status = ""
export_path
PosixPath('showeds_synthetic.html')

Large real EDS data#

For a native multi-GB EMD file, use a data folder instead of embedding the whole cube into the notebook. The data folder stores exact prefix arrays that make band maps and ROI spectra fast in the browser while keeping notebook state small.

from pathlib import Path
from quantem.widget import ShowEDS

path = Path("0031-CaSIO3_134hr_exsitu_SI_1.85_Mx_53.9_nm_EDS_HAADF_Diffraction_Nano.emd")

widget = ShowEDS.from_emd(
    path,
    sidecar_dir="sidecars/eds_real_0031",
    title="Real gold EDS 0031",
    energy=8.04,
    width=0.24,
    element_label="Cu K",
    candidate_elements=["O", "Si", "Ca", "Cu", "Au"],
    panel_width_px=430,
    spectrum_width_px=650,
    spectrum_height_px=250,
    log_spectrum=True,
)
widget

For exact internal sharing, keep the .ipynb and the sidecars/eds_real_0031 data folder together. For public web demos, use the widget export menu or widget.export_html(..., downsample=4) to create a single count-preserving binned HTML file that is much smaller than the full-resolution data folder.