Skip to content

Replace the py4DSTEM TEM diffraction component with the quantEM browser simulator - #550

Merged
minhsueh merged 3 commits into
materialsproject:mainfrom
cophus:quantem-diffsim
Nov 6, 2026
Merged

minhsueh merged 3 commits into
materialsproject:mainfrom
cophus:quantem-diffsim

Conversation

@cophus

@cophus cophus commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #547.

The TEM diffraction tab of the Materials Project website has failed since the site moved to NumPy 2, because every py4DSTEM release on PyPI, up to 0.14.18, builds its Crystal with dtype="float_", which NumPy 2.0 removed. py4DSTEM 0.14.18 also requires scikit-learn<1.5, and the newest allowed version (1.4.2) has no Python 3.13 wheels and cannot be built from source there, so the temdiff extra blocks the move to Python 3.13.

This PR replaces the component with DiffractionSim, the electron diffraction simulator from quantEM, packaged as quantem.diffsim on PyPI (electronmicroscopy/quantem.widget#245). The package has no runtime dependencies and ships one ES module (1.16 MB) with a few Python helpers. Every calculation runs in the browser, including the reflection list, the structure factors from the Lobato and Van Dyck scattering factors (Acta Cryst. A 70, 636 (2014)), and the Bloch wave intensities with the absorptive Weickenmeier and Kohl scattering factors and a Debye-Waller factor (Acta Cryst. A 47, 590 (1991)). The server only converts the structure to CIF text of its conventional standard cell, which is also the cell that py4DSTEM's Crystal.from_pymatgen_structure uses by default, so zone axes refer to the same lattice as before.

The simulator shows the crystal and its diffraction pattern side by side, with nanobeam, convergent beam electron diffraction (CBED) and Kikuchi modes, kinematical or Bloch wave intensities, and controls for orientation, thickness, convergence semiangle, precession and beam energy. It opens along [001] at 200 keV in nanobeam mode, which matches the beam direction and voltage defaults of the current tab. The current tab defaults to kinematical intensities, while the simulator starts with Bloch wave intensities for a 400 Å thick crystal. The simulator hides its structure menu when a structure is passed in, and shows a "Powered by quantEM" link to the quantEM documentation in its lower right corner.

Changes

  • crystal_toolkit/components/diffraction_tem.py: TEMDiffractionComponent.layout() returns quantem.diffsim.dash.layout(self.id("quantem-diffsim")), which holds a dcc.Store for the settings and the html.Div that the simulator draws into. generate_callbacks calls quantem.diffsim.dash.register(app), which serves the bundle from the app's Flask server at _quantem_diffsim/diffraction-sim-0.1.0.js (under routes_pathname_prefix, versioned and cached for one year) and adds one clientside callback that draws the simulator. A single server callback sends quantem.diffsim.settings(structure, zone_axis=[0, 0, 1], mode="nanobeam", energy_keV=200) whenever the structure store changes. When quantem.diffsim is not installed, the component shows a message in place of the simulator, as it did for py4DSTEM. These changes reduce the file from 397 to 74 lines.
  • TEMDiffractionCalculator and its plotly figure are removed, along with the py4DSTEM and NumPy imports. Nothing else in Crystal Toolkit imports the calculator, and code that calls it directly, as the snippet in [Bug]: TEM diffraction pattern with py4DSTEM #547 does, will need to use py4DSTEM itself.
  • pyproject.toml: temdiff = ["quantem.diffsim>=0.1"] replaces py4DSTEM>=0.13.11.
  • crystal_toolkit/apps/examples/diffraction_tem.py: a dropdown switches between Si, SrTiO3 and wurtzite GaN, so that the example also tests redrawing.
  • tests/test_diffraction_tem.py: the test checks the settings sent for Si (zone axis, mode, energy, formula and space group in the CIF), the PreventUpdate for an empty structure store, and the bundle route, and is skipped when quantem.diffsim is not installed.

Dependencies

Testing

  • Tested with quantem.diffsim 0.1.0 from PyPI alone: in a new Python 3.12 environment, pip install -e ".[temdiff]" on this branch installs the PyPI wheel into site-packages, with no local copy, tests/test_diffraction_tem.py passes, and the example app draws Si, SrTiO3 and GaN with no console errors.
  • With Python 3.12, NumPy 2.5.4, Dash 3.4.0, pymatgen 2026.10.24 and quantem.diffsim 0.1.0 installed from PyPI through the temdiff extra, pytest tests gives 16 passed, and ruff 0.14.10 check and format pass on the changed files.
  • Without quantem.diffsim, import crystal_toolkit.components works, TEMDiffractionComponent().layout() returns the message, and the new test is skipped.
  • On Python 3.13.3, pip install quantem.diffsim from PyPI installs no other package and writes a working standalone page.
  • In the example app (Chromium), with the bundle from the PyPI wheel, Si and SrTiO3 along [001] and GaN along [0001] draw with the expected reflections (220 and 400 for Si), each dropdown selection redraws the simulator in place, the bundle loads once, and the console shows no errors.
  • With flask-caching 2.5.1, the version in the lock files, the example apps on main do not start and the 4 tests in tests/test_utils.py fail, because CACHE_TYPE "simple" in crystal_toolkit/core/plugin.py no longer resolves to a cache backend. This PR does not touch that file. The fix-flask-caching-cache-types branch addresses this, and we ran the example app and the tests with flask-caching 2.3.1.
  • We have not tested the component inside the Materials Project website, where the tab layout and page styles differ from the example app.

Checklist

  • Doc strings have been added in the Google docstring format.
  • Type annotations have been added to the new methods.
  • Tests have been added for the new functionality.
  • Issues have been created for TODO items. The lock files are left for the automated dependency upgrade.

Written by Colin Ophus and Claude (Claude Code).

cophus and others added 3 commits November 3, 2026 18:20
TEMDiffractionComponent now shows DiffractionSim from the quantem.diffsim
package. The server converts the structure to CIF text of its conventional
standard cell, and the browser computes the reflections, structure factors
and Bloch wave intensities, so the component no longer imports py4DSTEM or
NumPy. The temdiff extra installs quantem.diffsim>=0.1, and the example app
switches between Si, SrTiO3 and GaN.

Fixes materialsproject#547.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test checks the settings that TEMDiffractionComponent sends to the
simulator and the bundle route, and skips when quantem.diffsim is not
installed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@minhsueh minhsueh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@minhsueh

minhsueh commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

@cophus Thanks for the contribution! Merged!

@minhsueh
minhsueh merged commit 07a78de into materialsproject:main Nov 6, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: TEM diffraction pattern with py4DSTEM

2 participants