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

ImageShiftMatrix

Camera pixels per image-shift unit, for each magnification index.

StageToCameraMatrix

Camera pixels per stage µm.

ImageShiftOffsets

The image shift that re-centres each magnification.

CrossoverIntensity

Per spot size: microprobe (the twin’s TEM mode), then nanoprobe.

HighFocusMagCal

Image scale and rotation at −50 to −300 µm defocus.

FocusCalibration

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 from RotationAndPixel where 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 CameraProperties block;

  • 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, so StageToCamera = −R(rotation) / pixel, and the image rotation is atan2(−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.

  • ComaVsISCal and 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.