ShowDiffraction#
ShowDiffraction is an interactive d-spacing analyzer for 2D diffraction patterns and 3D pattern stacks. It accepts a NumPy array, a PyTorch tensor, or a quantem Dataset. Auto runs center refinement, ring detection, phase calibration, fitting, and indexing, and Phase selects a library or custom phase.
Quickstart#
One command takes a pattern to an analyzed, shareable page:
# In Colab, run the installation cell below before using the CLI.
quantem showdiffraction pattern.npy --phase Au # your data: center, rings, calibration, hkl
quantem showdiffraction --demo # Fe3O4 SAED from the public hub, when uploaded
The deterministic spot and ring patterns below keep the core workflow executable without a network connection. The detection-denoise comparison later uses the public Fe3O4 nanoparticle SAED fixture when it is available and reports when it uses the deterministic magnetite control instead; measured diffraction arrays use the same API.
# @title Install QuantEM { display-mode: "form" }
if get_ipython().__class__.__module__.startswith("google.colab"):
!wget -q https://raw.githubusercontent.com/electronmicroscopy/quantem.widget/main/scripts/install_colab.py -O install_quantem.py
%run install_quantem.py
from quantem.widget import ShowDiffraction
Why ShowDiffraction#
Diffraction analysis is usually split across tools: hand measurement in an image viewer, a separate desktop program for indexing, a script for distortion or profiles. ShowDiffraction is the complete workflow in one widget that runs wherever Python runs, including Colab, and exports a self-contained HTML page that opens in any browser, including on a phone.
One-click pipeline: Auto chains center refinement, ring detection, phase calibration, profile fitting, and hkl indexing; every step is also a plain Python call.
Exact crystallography: d-spacings and interplanar angles from the full metric tensor for any crystal system, systematic absences for the common structure types, and a phase library of 100+ standards with cited lattice constants.
Honest identification: candidates are ranked by plain facts (matched lines, mean Δd, missing strong lines), and ranking stays a verification aid; nothing is applied silently.
Distortion-aware: a fitted elliptical distortion corrects every radius, profile, and calibration rather than being a separate pass.
Reproducible: JSON state save/load, CSV/JSON measurement tables, and the same analysis from the notebook, the exported HTML, or the
quantemCLI.
import numpy as np
from quantem.widget import ShowDiffraction
from quantem.widget import Phase
rng = np.random.default_rng(0)
size = 256
center = (size - 1) / 2
rows, cols = np.mgrid[0:size, 0:size]
def bragg_lattice(rotation_deg=0.0, spacing_px=28.0):
angle = np.deg2rad(rotation_deg)
cos_a, sin_a = np.cos(angle), np.sin(angle)
pattern = np.zeros((size, size), np.float32)
for h in range(-4, 5):
for k in range(-4, 5):
spot_row = center + (h * cos_a - k * sin_a) * spacing_px
spot_col = center + (h * sin_a + k * cos_a) * spacing_px
amplitude = 6.0 if h == 0 and k == 0 else 1.0 / (1 + 0.4 * (h * h + k * k))
pattern += amplitude * np.exp(-((rows - spot_row) ** 2 + (cols - spot_col) ** 2) / 8.0)
return rng.poisson(1000.0 * (pattern + 0.05)).astype(np.float32) # shot noise
single_crystal = bragg_lattice()
size_m = 512
center_m = (size_m - 1) / 2
m_rows, m_cols = np.mgrid[0:size_m, 0:size_m]
m_radius = np.hypot(m_rows - center_m, m_cols - center_m)
magnetite_pattern = 4.0 * np.exp(-(m_radius ** 2) / 80.0)
k_synth = 0.004
for d_ref, strength, width in [
(2.967, 1.7, 3.0),
(2.532, 1.3, 3.5),
(2.099, 1.0, 4.0),
(1.715, 0.8, 4.5),
(1.485, 0.55, 5.0),
]:
ring_radius = 1.0 / (d_ref * k_synth)
magnetite_pattern += strength * np.exp(-((m_radius - ring_radius) ** 2) / (2 * width ** 2))
magnetite_pattern = rng.poisson(100.0 * (magnetite_pattern + 0.05)).astype(np.float32)
Single-crystal spots#
Use Spots or click reflections. Add a custom cubic phase, then Index Spots to fill hkl and zone axis.
saed = ShowDiffraction(
single_crystal,
center=(center, center),
bf_radius=14,
k_pixel_size=0.018,
title="Single-crystal SAED",
offline=True,
verbose=False,
)
saed.detect_spots(max_spots=12)
saed.custom_phases = [{"name": "Cubic", "a": 1.984, "absences": "none"}]
saed.phase_name = "Cubic"
saed.index_spots(Phase.from_cubic("Cubic", 1.984, absences="none"))
saed
Polycrystalline rings#
Pick a phase, then press Auto. Profile, Azim, Mask View, and Quality in the side menu show the radial profile, azimuthal intensity, excluded-region overlay, and quality checks. Fit refines ring radius and width, Fit Ellipse measures distortion, and Identify ranks phase candidates; use element filters when chemistry is known.
magnetite = ShowDiffraction(
magnetite_pattern,
title="Magnetite-like rings",
offline=True,
verbose=False,
)
magnetite.phase_name = "Fe3O4"
magnetite.run_auto(max_rings=5)
magnetite.dp_colormap = "viridis"
magnetite
Verify the phase#
Identification works best as verification: rank the phases you expect with identify_phase (build candidates with library_phase or Phase.from_cubic, or pick them in the Phase menu and flip candidates only for Identify). Library-wide search_phases is the fallback when nothing is expected; filter by chemistry such as Fe, O. Five rings can keep related spinels close in the ranking.
from quantem.widget import library_phase
expected = [library_phase(n) for n in ("Fe3O4", "γ-Fe2O3", "α-Fe2O3 (hematite)", "α-Fe")]
verified = magnetite.identify_phase(expected)
for candidate in verified:
mean_err = candidate["mean_err"]
error_text = "n/a" if mean_err is None else f"{100 * mean_err:.2f}%"
missing = candidate["n_missing_strong"]
print(
f"{candidate['name']}: {candidate['matched']}/{candidate['n_obs']} lines, "
f"mean Δd {error_text}, "
f"missing strong {'n/a' if missing is None else missing}"
)
magnetite.identify_elements = "Fe, O"
candidates = magnetite.search_phases()
print(candidates[0]["name"])
Fe3O4: 6/6 lines, mean Δd 0.24%, missing strong n/a
γ-Fe2O3: 6/6 lines, mean Δd 0.73%, missing strong n/a
α-Fe2O3 (hematite): 4/6 lines, mean Δd 0.67%, missing strong n/a
α-Fe: 0/6 lines, mean Δd n/a, missing strong n/a
Fe3O4
Detection denoise#
The public Fe3O4 SAED fixture is used when available; otherwise this page reports that it is using the deterministic magnetite control generated above. The source is thinned to a median of 0.5 counts per pixel and analyzed twice side by side. Detection and Auto run on a matched-filter denoised view (detect_denoise, default auto; Anscombe on counting data) while ring fits and measurements stay on the raw counts. The right panel also sets the display-only denoise = "nlm" so the pattern itself is readable; show_detection_view shows what detection saw instead.
from ipywidgets import HBox, Layout
from quantem.widget.data import showdiffraction_fe3o4
try:
fe3o4_saed = showdiffraction_fe3o4(verbose=False)
except FileNotFoundError:
fe3o4_saed = magnetite_pattern
source_label = "deterministic magnetite control"
else:
source_label = "public Fe3O4 SAED fixture"
print(f"Detection-denoise source: {source_label}")
scale = 0.5 / np.median(fe3o4_saed[fe3o4_saed > 0])
sparse_saed = np.random.default_rng(2).poisson(np.clip(fe3o4_saed, 0, None) * scale).astype(np.float32)
def low_dose_auto(mode, title):
w = ShowDiffraction(sparse_saed, title=title, offline=True, verbose=False, panel_width_px=430)
w.detect_denoise = mode
if mode != "none":
w.denoise = "nlm"
w.phase_name = "Fe3O4"
w.run_auto()
indexed = sum(1 for r in w.rings if r.get("hkl"))
print(f"{title}: calibration rms {w.calibration_rms_px:.2f} px, {indexed}/{len(w.rings)} rings indexed")
return w
raw = low_dose_auto("none", "raw")
denoised = low_dose_auto("auto", "denoised")
HBox([raw, denoised], layout=Layout(overflow="auto"))
Detection-denoise source: deterministic magnetite control
raw: calibration rms 0.57 px, 6/6 rings indexed
denoised: calibration rms 0.57 px, 6/6 rings indexed
Save#
save writes JSON state. measurements_from_state rebuilds the table. export_html writes a standalone page.
magnetite.summary()
magnetite.save("magnetite_state.json")
ShowDiffraction.measurements_from_state("magnetite_state.json")[:2]
# HTML export
export_path = saed.export_html("showdiffraction_saed.html", title="Single-crystal SAED")
export_path.name
Magnetite-like rings
Frames: 1 (showing #0)
Detector: 512x512 (0.0040 1/Å/px)
Calibration: phase (rms 0.31 px)
Center: (255.5, 255.5) BF r=13.1 px
Spots: 0
Rings: 6
Phase: Fe3O4: 6/6 lines; next: γ-Fe2O3 (also 6/6), α-Fe2O3 (hematite)
Display: viridis | log
'showdiffraction_saed.html'