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.