ScanImage Coordinate System Instances
This page is the map of the coordinate system tree that ScanImage® builds at startup: which
node exists, which object owns it, what its units are, and what its transformation to its
parent means. These are the nodes you name when you write
hPts.transform(...).
Run
hSI.hCoordinateSystems.plot();
on your own system to see the live tree - the branches below the Reference node depend on which scanners, cameras and SLMs are configured.
The tree
World hSI.hCoordinateSystems.hCSWorld
└── Reference space hSI.hCoordinateSystems.hCSReference
├── Focus [lock] hSI.hCoordinateSystems.hCSFocus
├── <Scanner> Z Affine Alignment hSI.hScan2D.hCSZAffineLut (one per Scan2D)
├── Camera - <name> hSI.hCameraManager.hCameraWrappers(k).hCSZAffineLut
└── Motor Root Coordinates hSI.hMotors.hCSCoordinateSystem
└── Motor Micron hSI.hMotors.hCSMicron
└── Motor Alignment hSI.hMotors.hCSAlignment
└── Motor Rotation [lock] hSI.hMotors.hCSRotation
└── Motor Scaling [lock] hSI.hMotors.hCSAxesScaling
└── Motor Axes Coordinates [lock] hSI.hMotors.hCSAxesPosition
└── Motor Anti Scaling [lock]
└── Motor Anti Rotation [lock]
└── Motor Anti Alignment [lock] hSI.hMotors.hCSAntiAlignment
└── Sample Absolute hSI.hMotors.hCSSampleAbsolute
└── Sample Relative hSI.hMotors.hCSSampleRelative
[lock] marks nodes whose lock property is true: they are computed at run time from
other nodes or from hardware, and are neither loaded from nor reset by the coordinate system
class data file.
Root and focus
Node |
Handle |
Meaning |
|---|---|---|
|
|
The root. It exists so that every other node has a common ancestor and any two nodes can therefore be related. Nothing is expressed in World directly. |
|
|
The common space. Units are optical degrees in X and Y and microns in Z. The origin is the ideal focal point of the objective with all scanners and FastZ at zero. Every scanner, camera and the stage chain hang off this node. See Reference Coordinates. |
|
|
Reference space translated in Z by the current FastZ position, so the origin of Focus
is always the point currently being focused on. ScanImage rewrites its
|
% the point currently in focus, in sample-relative microns
hPt = scanimage.mroi.coordinates.Points(hSI.hCoordinateSystems.hCSFocus,[0 0 0]);
hPt = hPt.transform(hSI.hCoordinateSystems.hCSSampleRelative);
Scanner nodes
Every scanimage.components.Scan2D owns exactly one coordinate system,
hCSZAffineLut, named '<scanner name> Z Affine Alignment'. It is a CSZAffineLut,
and it is attached to hCSReference in the scanner’s reinit().
hSI.hScan2D.hCSZAffineLut % the coordinate system of the active imaging system
hSI.hResScan.hCSZAffineLut % ... of a specific scanner, by its configured name
hSI.hLinScan.hCSZAffineLut
This node is the scanner space described in ScanImage® Coordinate Systems: units are optical degrees in X and Y as produced by that scanner’s X and Y scanners, and microns in Z as produced by its FastZ. Its lookup table is what the scannerToReference alignment writes, which is why the alignment can be different at every depth.
% 2D affine that maps this scanner into reference space at a given reference z
zRef = 20;
T = hSI.hScan2D.hCSZAffineLut.toParentLutEntries.makeZAffines(zRef);
% corner points of the current field of view, in reference space
pts = hSI.hScan2D.fovCornerPoints(zRef);
A freshly constructed Scan2D seeds the node with a single identity entry
(LUTEntry(0,0,eye(3)) on fromParentLutEntries), so an unaligned scanner behaves as if
scanner space and reference space were the same.
Note
Scan2D still carries a legacy scannerToRefTransform property, a single 3x3
affine. hCSZAffineLut supersedes it; new code should use the coordinate system.
Camera nodes
Each camera managed by hSI.hCameraManager is wrapped in a
scanimage.components.cameramanager.CameraWrapper, and each wrapper creates a
CSZAffineLut named 'Camera - <camera name>' parented directly to hCSReference.
hWrapper = hSI.hCameraManager.hSelectedCameraWrapper;
hWrapper.hCSZAffineLut
Camera space is defined so that the imaged square has corner points
[-.5 -.5], [.5 -.5], [.5 .5], [-.5 .5]; the wrapper’s
pixelToCameraTransform gets you from pixels into that square, and the coordinate system
gets you from there into reference space.
Motor nodes and the sample space
scanimage.components.Motors builds the longest chain in the tree. The chain exists so
that each physically distinct correction lives in its own node and can be calibrated,
inspected and reset independently. All of these properties are hidden but publicly readable
on hSI.hMotors.
Node |
Handle |
Meaning |
|---|---|---|
|
|
Attachment point of the whole stage chain to reference space. |
|
|
Converts reference-space optical degrees into microns. This is the objective
calibration: |
|
|
Rotation of the stage axes relative to the reference axes, as measured by the stage-to-scanner alignment. |
|
|
Additional deliberate rotation - a tilted objective, or a yaw applied by the user. Locked; written through the azimuth / elevation API. |
|
|
Applies |
|
|
The hardware boundary. Positions read from the motor controllers enter the tree here. Going up leads to reference space; going down leads to sample space. |
|
(private) |
Undoes |
|
(private) |
Undoes |
|
|
Undoes |
|
|
End of the chain. Microns, axes parallel to the reference axes, origin fixed in space. |
|
|
|
The three “anti” nodes are what make sample space well behaved. Each mirrors the
corresponding correction node, so the corrections cancel on the way down the chain: sample
space is expressed as though the stage had been perfect, in microns, with axes aligned to
the reference axes, while the corrections still apply between reference space and the
hardware. ScanImage keeps them synchronized through listeners on the changed event of
Motor Alignment, Motor Rotation and Motor Scaling; that is why they are locked.
% read the stage
hPtSample = hSI.hMotors.getPosition(hSI.hCoordinateSystems.hCSSampleRelative);
hPtRef = hSI.hMotors.getPosition(hSI.hCoordinateSystems.hCSReference);
% move the stage to a point defined in any coordinate system
hPt = scanimage.mroi.coordinates.Points(hSI.hCoordinateSystems.hCSSampleRelative,[10 0 -5]);
hSI.hMotors.move(hPt);
% how many microns per optical degree is the objective calibrated for?
hSI.objectiveResolution
Note
Because Motor Micron sits between the stage and reference space, changing the
objective calibration silently changes the size of everything expressed in microns. That
is the intent - it is a single place to describe the objective - but it also means the
calibration belongs in the coordinate system class data file for that objective.
SLM and photostimulation nodes
A scanimage.components.scan2d.SlmScan adds a chain of its own. The chain begins at the
paired linear scanner when one is configured, because the SLM deflects the beam that the
galvos are already pointing.
<LinScan> Z Affine Alignment (or the SlmScan's own identity Z Affine Alignment)
└── <SlmScan> Coordinate System hSlmScan.hCSCoordinateSystem
└── <SlmScan> SLM Offset from Scanner hSlmScan.hCSScannerOffset
└── <SlmScan> SLM Lut Z Alignment hSlmScan.hCSSlmNative
└── <SlmScan> SLM Z Alignment Lut 3D hSlmScan.hCSSlmZAlignmentLut3D
└── <SLM> Deflection and Vergence [lock] hSlmScan.hSlm.hCoordinateSystem
├── <SLM> Diffraction Efficiency hSlm.hCSDiffractionEfficiency
└── <SLM> Pixel coordinates [lock] hSlm.hCSPixel
Node |
Meaning |
|---|---|
|
Present so that |
|
Identity bridge whose parent is chosen at |
|
Origin moved to wherever the paired galvos are currently pointing. Units and axes are still those of reference space: optical degrees in XY, microns in Z. |
|
|
|
|
|
The SLM’s native space, and the space the SLM is actually commanded in. XY are deflection in optical degrees, Z is vergence in diopters (m-1); the origin produces a blank phase mask. Locked - it is an identity bridge, and the real alignment lives one level up. |
|
|
|
Pixel coordinates on the SLM panel. Useful when the SLM sits in a conjugated image plane. Locked. |
hSlmScan = hSI.hSlmScan;
% where would a reference-space point land on the SLM?
hPt = scanimage.mroi.coordinates.Points(hSI.hCoordinateSystems.hCSReference,[1 0.5 0]);
hPtSlm = hPt.transform(hSlmScan.hSlm.hCoordinateSystem);
fprintf('SLM deflection %.3f, %.3f deg, vergence %.4f diopters\n',hPtSlm.points);
Working with the tree from a script
Find a node whose handle you do not have
hCS = hSI.hCoordinateSystems.getCoordinateSystemByName('Motor Alignment');
Enumerate the whole tree
[~,nodes] = hSI.hCoordinateSystems.hCSWorld.getTree();
names = cellfun(@(n)n.name,nodes,'UniformOutput',false);
disp(names(:));
See how two spaces are related
[path,toParent] = hSI.hScan2D.hCSZAffineLut.getRelationship(hSI.hMotors.hCSSampleRelative);
cellfun(@(n)fprintf('%s\n',n.name),path);
React to an alignment change
hL = most.ErrorHandler.addCatchingListener( ...
hSI.hMotors.hCSAlignment,'changed',@(varargin)disp('stage alignment updated'));
Tip
Keep the units straight. Reference and scanner spaces are optical degrees in XY and
microns in Z; sample spaces are microns in all three; SLM-native space is SLM optical
degrees in XY and diopters in Z. The transform is what converts between them - reading
hPts.points without knowing hPts.hCoordinateSystem tells you nothing.