PlanPtycho#

Open in Colab

PlanPtycho checks multislice ptychography settings against a known crystal before the experiment. Give it a crystal (a CIF file, an ase.Atoms, or a Materials Project id) and the microscope settings; it shows where the beam goes through the specimen, what the reconstruction’s model window holds, what lands on the detector, and a list of checks that say what fits and what to change.

Hide crystal construction code

import quantem.widget as qw
qw.profile()

from ase import Atoms

# cubic perovskite SrTiO3, Pm-3m, a = 3.905 A
srtio3 = Atoms(
    "SrTiO3",
    scaled_positions=[(0, 0, 0), (0.5, 0.5, 0.5), (0.5, 0.5, 0), (0.5, 0, 0.5), (0, 0.5, 0.5)],
    cell=[3.905, 3.905, 3.905],
    pbc=True,
)
quantem.widget  0.0.1rc39
  install       editable checkout
quantem.gpu     0.0.1rc8
  install       published package
quantem         0.1.9
torch           2.14.1+cu130  device=cpu
python          3.12.15

Plan a thick, tilted crystal#

110 nm of SrTiO3 viewed along [001], tilted (3.2, -2.4) mrad off the zone axis, with the default microscope preset: 300 kV, a 30 mrad probe and the Arina (192 x 192 px) at a 91 mm camera length, 0.554 mrad per pixel. The probe is focused 17 nm below the entrance surface and the scan is 48 x 48 positions with 0.495 A steps.

  • Top: the projected crystal, smeared by the tilt across the thickness, the scan field (cyan), the probe at the scan centre with its beam at the view depth (yellow) and the reconstruction’s model window (dashed). Drag to move the probe.

  • Side: the specimen as a stationary slab. Drag the dashed focus line to move the focus; the labels show how much of the specimen lies above and below it. Drag elsewhere to set the view depth.

  • Probe: the probe on the model window at the view depth, over the crystal at that depth (Sample switch). A beam wider than the window wraps around, as it does in the reconstruction.

  • Detector: the bright-field disk, the Bragg disks, the zone axis moved by the tilt, and the detector edge.

Every slider updates all four panels and the checks. The wheel zooms a panel; double-click resets it. The menus in the title bar apply a microscope preset or the recommended settings for 20-70 nm.

from quantem.widget import PlanPtycho

plan = PlanPtycho(srtio3, thickness_nm=110, tilt_mrad=(3.2, -2.4), c10_nm=-17, scan_step_A=0.495, scan_size_px=48)
plan

The checks as a table#

report() returns the same checks as a DataFrame, for scripts and lab notebooks. Here the 57 A beam at the exit surface is wider than the 35 A model window, the 24 A scan is too small for the deep beam, and the columns lean 4.4 A across the thickness: all three are cautions, each with the evidence and what to change.

plan.report()
label status value rule note
id
window Beam fits the virtual window caution 57 Å beam in a 36 Å window widest beam (entrance or exit) ≤ wavelength / ... The beam wraps around the virtual window. Simu...
margin Scan margin for the spread beam caution 24 Å scan, beam radius 28 Å scan side ≥ 4 × widest beam radius The outer ~28 Å of the scan is lit from one si...
overlap Probe overlap at the entrance pass 96 % (step 0.495 Å, beam 11.0 Å) 1 − step / entrance beam diameter ≥ 60 %
reach Detector reach pass 53 mrad = 1.8 × semiangle detector edge ≥ 1.5 × semiangle
lean Column lean across the thickness caution 4.4 Å at 4.0 mrad tilt thickness × tan(tilt) ≤ object pixel The columns lean across more than an object pi...
split Focus splits the specimen pass 17.0 nm above, 93.0 nm below the focus 0 ≤ focus depth ≤ thickness
pixel Object pixel info 0.185 Å (192 px over 35.5 Å) window / detector pixels
depth Depth of field info 4.4 nm 2 × wavelength / semiangle²: the depth the pro...
beam Beam diameter info entrance 11.0 Å, focus 0.8 Å, exit 56.6 Å 2 × semiangle × |depth − focus| + 1.22 × wavel...
phase Column phase per nm info 1.29 rad (strongest column) interaction constant × projected potential of ...
holz First HOLZ ring info 100 mrad, beyond the detector √(2 × wavelength / lattice period along the beam)

A collaborator’s acquisition, presets, your own crystal#

To check whether settings someone else recorded can work for ptychography, pass them as reported. A sampling without a camera name makes the camera custom, and c10_nm is the defocus in the quantem sign (negative focuses into the specimen). In the widget, click any number to type a value.

PlanPtycho(srtio3, thickness_nm=40, voltage_kV=200, semiangle_mrad=24.5,
           detector_px=128, detector_mrad_per_px=0.9, scan_step_A=0.4, scan_size_px=256, c10_nm=-15)

apply_thickness sets the recommended focus and scan size for 20-200 nm, and apply_preset switches the microscope settings. For cryo and biological sections of 150-200 nm, the beam at 30 mrad outgrows the Arina’s 35.5 A window at 91 mm; a longer camera length widens the window but lowers the detector reach, and a smaller semiangle narrows the beam.

plan.apply_thickness(200)
plan.apply_preset("Arina · 300 kV · 21.4 mrad · 185 mm")
PlanPtycho("my_crystal.cif", zone_axis=(0, 1, 1), thickness_nm=40, preset="Arina · 300 kV · 25 mrad · 115 mm")

Plan the simulation cell around the entire scan#

A cell that fits a probe at the scan center may clip it at the scan edge. Use Simulation Cell to choose lateral repeats, potential pixels per cell, and a guard margin. The potential sampling is independent of the measured detector and the reconstruction’s object sampling.

Compare 40, 50, and 60 nm using the same crystal and acquisition settings. The method below is a geometric plan only: it does not launch abTEM, generate diffraction patterns, or prove boundary convergence. Its z_repeats_to_cover covers the requested depth; the last repeat may need truncation for an exact thickness.

import pandas as pd

coverage = []
for thickness in (40, 50, 60):
    candidate = PlanPtycho(srtio3, thickness_nm=thickness, c10_nm=-17)
    cell_plan = candidate.simulation_plan(repeats=(32, 32), guard_A=5)
    coverage.append({
        "thickness_nm": thickness,
        "cell_extent_A": cell_plan["extent_A"],
        "probe_diameter_A": cell_plan["widest_probe_A"],
        "edge_margins_A": cell_plan["geometric_margin_A"],
        "geometric_fit": cell_plan["geometric_fit"],
        "boundary_verified": cell_plan["boundary_convergence_verified"],
    })
pd.DataFrame(coverage)
thickness_nm cell_extent_A probe_diameter_A edge_margins_A geometric_fit boundary_verified
0 40 [124.96, 124.96] 14.600629 [23.429685305869057, 23.429685305869057] True False
1 50 [124.96, 124.96] 20.600629 [20.429685305869057, 20.429685305869057] True False
2 60 [124.96, 124.96] 26.600629 [17.429685305869057, 17.429685305869057] True False

Keep the camera fixed when enlarging the virtual window#

The camera defines the native window. The optional Virtual Window control at the bottom previews doubled support: a 192 × 192 measured detector with a 384 × 384 model wave. It doubles the real-space width while keeping the object pixel size and measured detector unchanged. It does not pad measured data or automatically change a reconstruction backend.

The API retains the name wave_window_factor. A compatible forward model must integrate its finer predicted diffraction intensities onto the measured pixels. Check propagated boundary power and compare larger simulation cells before accepting a simulation. The detector drawing in this planner is schematic, not a simulated DP.

window_comparison = []
for factor in (1, 2):
    plan.wave_window_factor = factor
    window_comparison.append({
        "virtual_factor": factor,
        "measured_detector_pixels": plan.detector_px,
        "model_wave_pixels": plan.wave_pixels,
        "virtual_width_A": plan.window_A,
        "object_sampling_A": plan.object_pixel_A,
    })
plan.wave_window_factor = 1  # leave the main demo at its original native setting
pd.DataFrame(window_comparison)
virtual_factor measured_detector_pixels model_wave_pixels virtual_width_A object_sampling_A
0 1 192 192 35.537198 0.18509
1 2 192 384 71.074397 0.18509

Inspect the same crystal before simulation#

Use ShowCIF for species, orientation, tilt, potential slabs, and expected projected phase. A small inspection patch keeps the preview responsive; it is not the full simulation supercell calculated above.

crystal = qw.ShowCIF(srtio3, repeats=(4, 4, 8), zone_axis=(0, 0, 1))
crystal