Using the twin in-process#
from de_twin import DigitalTwin
twin = DigitalTwin("Dense Au on holey C", camera="DE16", holder="sim-heating")
twin.column.set("Magnification", 50_000)
twin.column.move_stage(x=12.5, y=-3.0) # um, waits for the stage
twin.column.set_defocus_um(-1.5)
img = twin.snap(1.0) # dark/gain-corrected electrons, like DE-Server
raw = twin.raw_frame(0.025) # one raw uint16 frame: offset, noise, defects
for frame, meta in twin.frames(twin.request(frame_time_s=0.01, total_frames=100)):
... # raw frames + metadata (scan point, state, dose)
truth = twin.flux(twin.request()) # noiseless e-/px/s, for scoring automation
Requests#
twin.request(...) builds an AcquisitionRequest: exposure mode (Dark, Gain,
Normal, …), frame time, frame count, hardware ROI and binning, and a ScanRequest for
STEM. The same request type comes from DE-Server over shared memory or from deapi
properties, so code written against one path works on the others.
References#
twin.processor measures dark references through the twin, the way an operator would,
and provides a well-dosed gain reference. twin.snap() and processor.acquire() apply
them. Acquisitions in ExposureMode.DARK or GAIN measure references frame by frame, so
under-dosed or saturated references behave as they do on a real camera.
Clocks#
Every time-dependent part (stage motion, holder ramps, in-situ evolution, corrector drift, frame pacing) reads one clock:
Clock(): real time (the default).Clock(time_scale=20): 20 simulated seconds per wall second.ManualClock(): time moves only when you advance it (or when paced code sleeps), so runs are deterministic and instant.
Existing duck types#
Code written for de_autopilot or de_ground_crew keeps working:
de_twin.column.ColumnAdapter(twin.column) looks like their SimColumn, and
twin.holder looks like autopilot’s SimHeater / ImpulseHeater.