PlanPtycho#
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.
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