ShowEDS#

Public import:

from quantem.widget import ShowEDS, load_eds

ShowEDS explores EDS/EELS spectrum images with shape (row, col, energy). The widget links a real-space map and a spectrum: dragging the energy band updates the element map, and dragging the rectangular real-space ROI updates the summed spectrum.

Viewer UI#

ShowEDS supports the shared ui_mode, show_title, show_controls, controls_collapsed, and show_scale_bar names. The saved-state trait remains scale_bar_visible for compatibility. See Viewer UI controls.

ShowEDS is still experimental. The merged baseline supports synthetic/small cubes and the documented data-folder workflow. Direct native EMD sparse-stream loading is under active real-data testing and should not be treated as finalized release behavior yet.

Canonical forms:

# Native EDS/EELS file: parse metadata and keep exact no-bin EMD data lazy.
# The returned SpectrumImage uses (row, col, energy) and feeds ShowEDS directly.
eds = load_eds("0031-CaSIO3_...EDS_HAADF_Diffraction_Nano.emd")
w = ShowEDS(
    eds,
    energy=8.04,
    width=0.24,
    pixel_size=0.138,
    pixel_unit="nm",
    element_label="Cu K",
    candidate_elements=["O", "Si", "Ca", "Cu", "Au"],
)

# Small or already-reduced cube: embeds the exact uint16/uint32/float32 cube in
# widget state, so notebook save/reopen and HTML export are self-contained.
w = ShowEDS(cube, energy_keV, base_image=haadf)

# Large EMD as a reusable data folder. The notebook stores startup state and a
# data-folder URL, not the multi-GB cube.
w = ShowEDS.from_emd(
    "0031-CaSIO3_...EDS_HAADF_Diffraction_Nano.emd",
    sidecar_dir="sidecars/eds_real_0031",
    energy=8.04,
    width=0.24,
    pixel_size=0.138,
    pixel_unit="nm",
    element_label="Cu K",
    candidate_elements=["O", "Si", "Ca", "Cu", "Au"],
    selected_elements=["Cu", "Au"],
)

# Shareable HTML. Small cubes can use single HTML with exact data. Large folder-backed
# cubes can use exact folder HTML, or count-preserving downsampled single HTML.
w.export_html("showeds_exact.html")
w.export_html("showeds_exact_folder.html", mode="folder")
w.export_html("showeds_downsample4.html", downsample=4)

For the real EDS 0031 dataset, the folder path keeps the live notebook small because the saved .ipynb contains widget state plus startup arrays, not the entire raw cube. The exact folder HTML mode is similarly small but must be served next to its companion data folder. downsample=2 or downsample=4 embeds sum-downsampled data into one HTML file, which is the better public tutorial and Hugging Face demo path.

Reference#

class quantem.widget.showeds.ShowEDS(*args: Any, **kwargs: Any)#

Bases: StaticFallbackMixin, AnyWidget

Explore an EDS/EELS spectrum image (row, col, energy).

For browser-backed widgets, the frontend keeps the spectrum cube resident in WebGPU. ShowEDS.from_emd(...) uses exact sparse browser-side streams by default for native Velox/RSCIIO EMD spectrum streams, keeping band/ROI interaction out of the notebook kernel. Pass backend="kernel" only when explicit lazy Python queries are required.

classmethod from_sidecar(sidecar_url: str, *, sidecar_dir: str | Path | None = None, initial_map: ndarray | None = None, initial_spectrum: ndarray | None = None, energy_keV: ndarray | list[float] | None = None, title: str = '', base_image: ndarray | None = None, energy: float | None = None, width: float | None = None, element_label: str = '', candidate_elements: list[str] | tuple[str, ...] | None = None, **kwargs: Any) ShowEDS#

Create a browser-only widget from a prepared data-folder URL.

classmethod from_emd(path: str | Path, *, backend: str = 'auto', sidecar_dir: str | Path | None = None, sidecar_url: str | None = None, rebuild_sidecar: bool = False, energy_chunk: int = 256, spatial_bin: int = 1, energy_bin: int = 1, max_sidecar_bytes: int | None = 12884901888, title: str | None = None, energy: float = 8.04, width: float = 0.24, element_label: str = 'Cu K', candidate_elements: list[str] | tuple[str, ...] | None = None, **kwargs: Any) ShowEDS#

Open a Velox/RSCIIO EMD spectrum image.

backend="auto" uses an existing data folder if one is present. Otherwise, exact no-bin Velox/RSCIIO spectrum streams are indexed into browser-side sparse buffers so band/ROI interaction does not block the notebook kernel. Pass backend="kernel" to force the older lazy Python query path, or backend="sidecar" / explicit binning for a portable prefix-cache data folder.

detect_elements(source: str = 'sum', *, elements: list[str] | tuple[str, ...] | None = None, energy_resolution_mnka: float = 130.0, min_significance: float = 4.0, max_candidates: int = 8, select: bool = False) list[dict[str, Any]]#

Detect candidate elements from spectrum peaks and rank them.

Estimates the continuum background, finds significant peaks, and ranks elements whose characteristic lines explain them (see quantem.widget.showeds.match_elements()). Results are advisory and fill the element_candidates trait shown in the periodic-table menu.

Parameters:
  • source"sum" uses the full sum spectrum, "roi" the current ROI.

  • elements – Restrict candidates to these symbols. Defaults to the constructor’s candidate_elements when given, else the full line table.

  • energy_resolution_mnka – Detector resolution at Mn Ka in eV.

  • min_significance – Minimum peak significance in sigma.

  • max_candidates – Maximum number of ranked candidates.

  • select – If True, write the candidate symbols to selected_elements.

Returns:

Ranked per-element reports, also stored in element_candidates.

Return type:

list[dict]

get_state(key=None, drop_defaults=False)#

Trait state for comm sync and notebook embedding.

ipywidgets calls this with key=None to snapshot the FULL state that gets written into the saved notebook’s metadata.widgets. When save_state is False we drop the heavy buffers from that snapshot so a plain ShowEDS does not bake the dense cube / sparse stream index into the .ipynb. Targeted syncs (key is a name or set, used by hold_sync / send_state during live rendering) are untouched, so the frontend still receives every buffer normally. save_state=True embeds everything so a reopened notebook restores the interactive widget without a kernel.

collapse_controls() Self#

Collapse controls behind the frontend Controls button.

expand_controls() Self#

Expand frontend controls when show_controls is enabled.

toggle_controls() Self#

Toggle whether frontend controls start collapsed.

export_html(path: str | Path | None = None, *, title: str | None = None, mode: str = 'single', encoding: str = 'full', downsample: int | None = None, binning: int | None = None) Path#

Write a standalone HTML explorer for this EDS/EELS widget.

mode="single" writes one HTML file. mode="folder" writes an HTML file that references an existing exact data folder. Use downsample=2 or downsample=4 for a smaller one-file export that sum-bins rows, columns, and energy channels. binning is kept as a compatibility alias for older notebooks.

class quantem.widget.showeds.SpectrumImage(cube: ~typing.Any | None, energy_keV: ~numpy.ndarray | list[float], base_image: ~typing.Any | None = None, title: str = '', candidate_elements: list[str] = <factory>, path: ~pathlib.Path | None = None, source_shape: tuple[int, int, int] | None = None, spatial_bin: int = 1, energy_bin: int = 1, backend: str = 'browser', sidecar_dir: ~pathlib.Path | None = None, sidecar_url: str | None = None, initial_map: ~numpy.ndarray | None = None, initial_spectrum: ~numpy.ndarray | None = None, sampling: float | tuple[float, float] | list[float] | None = None, units: str | list[str] | None = None, metadata: dict[str, ~typing.Any] = <factory>, stream_index: dict[str, ~typing.Any] | None = None)#

Parsed EDS/EELS spectrum image with energy on the last axis.

This is the small public handoff object returned by load_eds(). It is intentionally widget-local for now: users can pass it directly to ShowEDS, while large native EMD files stay lazy and exact instead of forcing a dense cube into notebook state.

property array: Any | None#

Return the dense or lazy (row, col, energy) cube, when available.

property shape: tuple[int, int, int] | None#

Return the spectrum-image shape (row, col, energy) when known.

show(**kwargs: Any) ShowEDS#

Create a ShowEDS widget from this parsed spectrum image.

quantem.widget.showeds.load_eds(path: str | Path, *, backend: str = 'auto', sidecar_dir: str | Path | None = None, sidecar_url: str | None = None, rebuild_sidecar: bool = False, energy_chunk: int = 256, spatial_bin: int = 1, energy_bin: int = 1, max_sidecar_bytes: int | None = 12884901888, candidate_elements: list[str] | tuple[str, ...] | None = None) SpectrumImage#

Parse an EDS/EELS spectrum image into a SpectrumImage.

The returned object always uses the (row, col, energy) convention and can be passed directly to ShowEDS:

from quantem.widget import ShowEDS, load_eds

eds = load_eds("scan.emd")
ShowEDS(eds)

For native Velox/RSCIIO .emd files, backend="auto" uses an existing data folder when present. Otherwise exact no-bin spectrum streams are indexed into browser-side sparse buffers so interactive band/ROI updates stay out of the notebook kernel. Pass backend="kernel" only when explicit lazy Python queries are required. If a data folder is requested, or if spatial_bin / energy_bin are larger than one, the existing exact ShowEDS sidecar path is used.

quantem.widget.showeds.bin_spectrum_image(cube: ndarray, energy_keV: ndarray | list[float] | None = None, *, base_image: ndarray | None = None, spatial_bin: int = 1, energy_bin: int = 1) tuple[ndarray, ndarray | None, ndarray | None]#

Sum-bin an EDS/EELS cube before sending it to the browser.

spatial_bin bins rows and columns; energy_bin bins adjacent energy channels. Summing preserves counts, which is usually the right behavior for spectrum-image exploration.

quantem.widget.showeds.prepare_spectrum_image_sidecar(cube: ndarray, energy_keV: ndarray | list[float], out_dir: str | Path, *, base_image: ndarray | None = None, energy_chunk: int = 256, max_sidecar_bytes: int | None = 12884901888) Path#

Write an exact ShowEDS data folder for fast EDS/EELS interaction.

The data folder keeps the raw count precision but stores summaries that make browser interaction proportional to screen size or energy channels, not to the full row * col * energy cube on every drag. Files are written as little-endian uint32:

  • energy_prefix_u32.bin: cumulative energy planes, shape (n_energy + 1, rows, cols). Band maps are exact plane differences.

  • spatial_prefix_u32.bin: summed-area spectra, shape (rows + 1, cols + 1, n_energy). ROI spectra fetch four contiguous spectra and subtract them exactly.

This is intentionally a preprocessing step. Once the files exist, the live widget can fetch them from the frontend without Python round trips.

This prefix-cache format is meant for small or deliberately spatial-binned portable viewers. Native vendor EDS files should usually remain the query source for no-bin work: the widget only needs the current energy window, ROI spectrum, or visible preview, not a fully expanded browser artifact. max_sidecar_bytes protects against accidentally creating a portable cache that is not appropriate for the source.

quantem.widget.showeds.load_emd_spectrum_image(path: str | Path, *, spatial_bin: int = 1, energy_bin: int = 1, candidate_elements: list[str] | tuple[str, ...] | None = None) dict[str, Any]#

Load a Velox/RSCIIO EDS spectrum image for ShowEDS.

Returns a dictionary containing cube, energy_keV, base_image, title, candidate_elements, and source_shape. The returned cube is always (row, col, energy). Optional spatial_bin and energy_bin perform count-preserving sum binning when explicitly requested.

quantem.widget.showeds.eds_line_hints(energy_min: float, energy_max: float, *, elements: list[str] | tuple[str, ...] | None = None, max_lines: int = 600) list[dict[str, Any]]#

Return line hints inside an energy range for frontend display.

Interactive controls#

With a small cube in single mode with exact data, all interaction runs in the browser. With a folder-backed EMD widget, startup state is embedded and the browser fetches exact prefix arrays from the data-folder URL, so dragging the band or ROI does not need a Python round trip.

Control

Trait

Expected effect

Energy band on spectrum

band_start, band_end

Real-space element map recomputes from the selected energy window

Band center drag

band_start, band_end

The selected energy window translates without changing width

Elements periodic table

selected_elements, auto_identify, line_hints

Select candidate elements, emphasize their line markers, and snap the band to characteristic X-ray lines

Detect button

element_candidates, detect_status

Runs detect_elements() in the kernel on the full sum spectrum and fills the Auto-ID candidate chips (advisory, click a chip to select)

Real-space ROI

roi_row, roi_col, roi_height, roi_width, roi_shape

Rectangular or circular ROI recomputes the summed spectrum from the selected real-space region

Spectrum log toggle

log_spectrum

Spectrum switches between linear and log display

Scroll zoom / pan

map_zoom, map_view_row, map_view_col, spectrum_view_start, spectrum_view_end

Real-space and spectrum views zoom around the cursor and reopen at the saved view

Real-space scale bar

pixel_size, pixel_unit, scale_bar_visible

Shows/hides a Show2D-style scale bar and zoom indicator without changing count data

Overlay slider

overlay_opacity

Element-map overlay fades against the base image

Auto / contrast range

map_vmin_pct, map_vmax_pct

Element-map display range updates without changing counts

Panel resize handles

panel_width_px, spectrum_width_px, spectrum_height_px

Real-space and spectrum panels resize independently

Export menu

export_request, export_payload, export_status

Generates exact folder or downsampled single HTML

Reset

multiple state traits

Restores default band, ROI, contrast, and panel sizing

State and sharing#

Saving a notebook stores the interactive widget state: current band, ROI, scroll zoom, scale-bar calibration, log toggle, overlay opacity, contrast limits, panel sizes, and export metadata. For small cubes stored with exact data, the saved notebook also contains the exact cube bytes. For large from_emd(...) widgets, the saved notebook contains only startup arrays and the data folder URL, so reopening stays lightweight and the data folder must remain available.

For collaborators in JupyterLab, share the full-state notebook plus any data folder it references. For GitHub’s native notebook preview, use the GitHub snapshot workflow instead of expecting live widgets. For a web page or tutorial, export HTML: exact folder HTML for full-resolution internal use, or downsampled single HTML for compact public demos.