SerialEM calibrations#
from de_twin import DigitalTwin, ManualClock
from de_twin.optics import OpticsConfig
from de_twin.optics.realism import ColumnRealism
from de_twin.optics.serialem import from_serialem, to_serialem
# a twin's column, as the calibrations SerialEM would measure on it
to_serialem(ColumnRealism(seed=11), "DESim", "SerialEMcalibrations.txt", "rotation_and_pixel.txt")
# a microscope's calibrations, as a twin's column
column = from_serialem("SerialEMcalibrations.txt", "rotation_and_pixel.txt", camera="DESim")
twin = DigitalTwin(camera="DESim", clock=ManualClock(), optics_config=OpticsConfig(realism=column))
to_serialem writes a SerialEMcalibrations.txt file for a column (ColumnRealism or
ColumnDefinition):
Entry |
What it holds |
|---|---|
|
Camera pixels per image-shift unit, for each magnification index. |
|
Camera pixels per stage µm. |
|
The image shift that re-centres each magnification. |
|
Per spot size: microprobe (the twin’s TEM mode), then nanoprobe. |
|
Image scale and rotation at −50 to −300 µm defocus. |
|
Image displacement against defocus, per unit of beam tilt in the column’s units. |
With a properties path, it also writes the camera’s CameraProperties block of
RotationAndPixel lines.
from_serialem reads a microscope’s files into a ColumnDefinition. This is a
table-driven column that goes in OpticsConfig(realism=...), so the twin behaves like that
microscope. For each magnification it holds:
the rotation and true pixel size, from
StageToCameraMatrix, or fromRotationAndPixelwhere that is defined;the image-shift matrix;
the magnification offset.
It also holds a beam-shift matrix (from a real file’s BeamShiftCalibration), the crossovers
per spot and probe, and the high-defocus scale and rotation. Anything the files do not define
is ideal.
The formats are those of SerialEM’s own reader and writer (ParameterIO.cpp). The reader:
treats 999 as undefined;
reads only the requested camera’s
CameraPropertiesblock;skips line forms it does not know, rather than failing.
Conventions#
The conventions follow SerialEM’s help (“Image Rotation”) and ShiftManager.cpp:
Camera coordinates are right-handed, with x to the right and y up. The twin’s raster has y down.
Specimen coordinates are minus stage coordinates.
SpecimenToCamera = R(rotation) / pixel, soStageToCamera = −R(rotation) / pixel, and the image rotation isatan2(−ypx, −xpx).IStoCamera = SpecimenToCamera @ (specimen shift per image-shift unit).
The twin’s stage y runs opposite to SerialEM’s. With that, SerialEM’s image rotation is the
twin’s view rotation, and every matrix has a positive determinant, as on a real scope.
tests/test_serialem_calibrations.py checks the written stage and image-shift matrices
against image motion measured on the twin’s own images.
Magnification indices#
SerialEM numbers a scope’s magnifications from 1. The twin’s own numbering is its imaging
ladder, LowMAG then MAG1 (twin_mag_table()). For a real scope, pass its table as
mag_table={index: magnification} and its low-magnification ceiling as lm_max_mag.
A magnification that falls between table entries takes the nearest entry in its own range
(LM or not).
Not covered#
Refining a column definition from acquired data. That belongs to the tools that drive a microscope (Ground Crew, de_autopilot); de-twin only converts.
ComaVsISCaland the low-dose areas. These live in SerialEM’s settings file, not its calibrations.The image-shift to beam-shift calibration. In the twin, image shift never moves the beam off the imaged area, so none is written.
Absolute handedness. This follows SerialEM’s documentation; check it once against a real calibration file.