Alignment Model
An alignment model defines the protocols for pre-transformation and alignment scoring.
Alignment Workflows
There are three types of alignment workflows.
Single template
If the alignment model is created with a single template image, the workflow is very simple. It masks and pre-transforms both the sub-volume and the template, and then aligns them.
graph LR
vol(sub-volume\nat <i>i</i>-th molecule)
tmp[[template image]]
vol_t(transformed\nsub volume)
tmp_t[[transformed\ntemplate]]
aln{alignment}
result[alignment results]
vol--masking &<br>pre-transformation-->vol_t-->aln
tmp--masking &<br>pre-transformation-->tmp_t-->aln
aln-->result
Multiple templates
If the alignment model is created with multiple template images, masking, pre-transformation and alignment are performed for each template separately. The alignment result with the best score will be considered as the optimal result.
graph LR
subgraph Subvolumes
vol(sub-volume\nat <i>i</i>-th molecule)
vol_t(transformed\nsub volume)
vol--masking &<br>pre-transformation-->vol_t
end
subgraph Templates
tmp0[[template image A]]
tmp1[[template image B]]
tmp2[[template image C]]
tmp0_t[[transformed\ntemplate A]]
tmp1_t[[transformed\ntemplate B]]
tmp2_t[[transformed\ntemplate C]]
tmp0--masking &<br>pre-transformation-->tmp0_t
tmp1--masking &<br>pre-transformation-->tmp1_t
tmp2--masking &<br>pre-transformation-->tmp2_t
end
subgraph Alignment
aln0{alignment}
aln1{alignment}
aln2{alignment}
end
result[best alignment results]
vol_t-->aln0
vol_t-->aln1
vol_t-->aln2
tmp0_t-->aln0
tmp1_t-->aln1
tmp2_t-->aln2
aln0-->result
aln1-->result
aln2-->result
Single template with rotation
Many alignment methods do not search for the optimal rotation of the template image. In this case, rotated templates will be generated and used for alignment. Essentially, it is the same as the multiple-template workflow.
graph LR
subgraph Subvolumes
vol(sub-volume\nat <i>i</i>-th molecule)
vol_t(transformed\nsub volume)
vol--masking &<br>pre-transformation-->vol_t
end
subgraph Templates
tmp[[template image]]
tmp_t[[transformed\ntemplate]]
tmp--masking &<br>pre-transformation-->tmp_t
tmp0[[template image A]]
tmp1[[template image B]]
tmp2[[template image C]]
rot{image rotation}
tmp_t-->rot
rot-->tmp0
rot-->tmp1
rot-->tmp2
end
subgraph Alignment
aln0{alignment}
aln1{alignment}
aln2{alignment}
end
result[best alignment results]
vol_t-->aln0
vol_t-->aln1
vol_t-->aln2
tmp0-->aln0
tmp1-->aln1
tmp2-->aln2
aln0-->result
aln1-->result
aln2-->result
Ready-to-use Models
There are now two alignment models that can be used directly.
ZNCCAlignmentModel that align subvolumes using ZNCC (Zero-mean Normalized Cross Correlation) score.
PCCAlignmentModel that align subvolumes using PCC (Phase Cross Correlation) score.
Both models are implemented with low-pass filtering, template rotation and missing wedge masking.
Model construction
from acryo.alignment import ZNCCAlignment
model = ZNCCAlignment(
template, # template image
mask, # mask image
rotations=[(10, 5), (4, 2), (8, 4)],
cutoff=0.5,
tilt=(-60, 60),
)
Shape of
maskmust be the same astemplate.template * maskandsubvolume * maskwill be used for alignment.rotationscan be three tuples or ascipy.spatial.transform.Rotationobject.If three tuples are given, each tuple defines the maximum rotation angle and the increment around z, y or x (external) axis. The unit is degree. For example, the first
(10, 5)means that the rotation angles -10, -5, 0, 5, 10 will be used for the rotation around z axis.If a
scipy.spatial.transform.Rotationobject is given, all the rotations in the object will be used for alignment. Make sure that the identity rotation is included.
cutoffis the relative cutoff frequency for low-pass filtering. The Nyquist frequency is \(0.5 \times \sqrt{3} = 0.866\) for 3D images.tiltis the tilt series model.If a (float, float) tuple is given, it will be interpreted as the minimum and maximum tilt angle in degree. The rotation axis is assumed to be the y axis.
You can use classes provided in
acryo.tiltto define more complex tilt series models. For instance,single_axis()for single-axis tilt series anddual_axis()for dual-axis tilt series.
Align images
The align() method is used to align a sub-volume to the template image of the model.
Note that this method does not actually transform the sub-volume to the template. It only
calculate the optimal shift/rotation parameters. To transform the sub-volume, use fit().
result = model.align(
img,
max_shifts,
quaternion,
pos,
backend,
)
imgis the sub-volume to be aligned. It must be a 3D array with the same shape as the template.max_shiftsis a tuple of maximum shifts in z, y and x direction. The unit is pixel but it can be a float number.quaternionis the rotation of the sub-volume in the original tomogram. It must be a (4,)numpy.ndarrayobject of quaternion. If you are usingacryo.Molecules, its quaternions can directly be used here. This is basically used to mask the missing wedge.posis the position of the sub-volume in the original tomogram. It must be a (3,)numpy.ndarrayobject. Default alignment models does not use this parameter.backendis the array API backend. It can be"numpy"or"cupy".
The return value result is a named-tuple AlignmentResult object. It contains the
following fields.
class AlignmentResult(NamedTuple):
label: int
shift: NDArray[np.float32]
quat: NDArray[np.float32]
score: float
labelis the integer label of the best alignment if multiple templates are used.shiftis the optimal shift in z, y and x direction.quatis the optimal rotation in quaternion.scoreis the alignment score of the best alignment.
Fit images
The fit() method is used to transform the sub-volume to fit the template image of the
model. It is essentially the same as calling align() for every rotation and then
Affine transform the sub-volume to the best alignment result, but fit() is faster
because it parallelizes the rotation and alignment processes.
result = model.fit(
img,
max_shifts,
cval=0.0,
backend=None,
)
imgandmax_shiftsis the same asalign().cvalis the constant value used for Affine transformations. 1% percentile will be used by default.
Correlation landscape
The word “correlation landscape” came from “energy landscape” in the context of protein folding. It is a 3D array of the correlation scores between the sub-volume and the template image.
arr = model.landscape(
img,
max_shifts: tuple[float, float, float],
quaternion: NDArray[np.float32] | None = None,
pos: NDArray[np.float32] | None = None,
upsample: int = 1,
backend: Backend | None = None,
)
imgis the sub-volume to be aligned. It must be a 3D array with the same shape as the template.max_shiftsis a tuple of maximum shifts in z, y and x direction. The unit is pixel but it can be a float number.quaternionis the rotation of the sub-volume in the original tomogram. It must be a (4,)numpy.ndarrayobject of quaternion. If you are usingacryo.Molecules, its quaternions can directly be used here. This is basically used to mask the missing wedge.posis the position of the sub-volume in the original tomogram. It must be a (3,)numpy.ndarrayobject. Default alignment models does not use this parameter.
Define Custom Alignment Model
In acryo.alignment, there are several abstract base classes that can be used to
efficiently define custom alignment models.
BaseAlignmentModel… The most basic one that provides the minimum interface. Need to override_optimize()andpre_transform().RotationImplemented… Rotated templates will be generated even if the optimization algorithm does not optimize the rotation. Need to override_optimize()andpre_transform().TomographyInput… Rotation, low-pass filtering and missing wedge masking is already implemented. Only need to override_optimize().
When you override methods, the following should be noted.
pre_transform()This method must have the following signature.
def pre_transform( self, image: NDArray[np.float32], backend=None, ) -> NDArray[np.complex64]: ...
The input image could be either the sub-volume or the template image. It is masked by the input mask image but is not masked by the missing wedge mask in
TomographyInput. The output image will be directly passed to the_optimize()method, so the data type depends on the implementation.backendis the backend array API object. You don’t have to use it unless you want to implement same method for GPU._optimize()This method must have the following signature.
def _optimize( self, subvolume: NDArray[T], template: NDArray[T], max_shifts: tuple[float, float, float], quaternion: NDArray[np.float32], pos: NDArray[np.float32], backend=None, ) -> tuple[NDArray[np.float32], NDArray[np.float32], float]: ...
This method is called for every set of sub-volume and template images.
subvolumeandtemplateis the images after pre-transformation. Thus, they could be Fourier transformed.max_shiftis directly passed fromalign()orfit()method.quaternionis the rotation of the sub-volume. This parameter can be used to mask the missing wedge.posis the position of the sub-volume in the original tomogram. Its unit is pixel. This parameter can be used for CTF correction of defocusing.backendis the backend array API object. You don’t have to use it unless you want to implement same method for GPU.The return value must be a tuple of
(shift, rotation, score).shiftis the optimal shift in z, y and x direction. More precisely,ndi.shift(img, -shift)will properly align the image to the template.rotationis the optimal rotation in quaternion. If the alignment model does not optimize the rotation, this value should bearray([0, 0, 0, 1]).scoreis the score of the alignment. Larger score means better alignment.