This page is DEPRECATED
Please refer to using the Transform object in python/EMAN2 for the latest. In fact this page should probably not exist
Contents
Transformations in EMAN2/SPARX
All 2D and 3D transformations as well as projection and backprojection operations are internally specified with the help of Transform3D Class (see below). The Class allows the user to operate using any commonly encountered convention of Eulerian angles as well as specify the order in which rotation, translation (shift) and scale (magnification) are applied. However, the transformations are used in EMAN2/SPARX in specific order and the details are given in what follows.
Conventions of Eulerian angles recognized are:
- EMAN
- SPIDER
- IMAGIC
- MRC
- QUATERNION
- SPIN
- XYZ
- SGIROT
and are set in python code by
1 convention = EULER_SPIDER
We distinguish three kinds of situations in which transformations are applied: operations on 2D images, operations of projection and backprojection (and more generally of 3D projection alignment), and operations on 3D maps. Each of these situations requires different set of transformation parameters, although each is implemented using the same general Transformation Class methodology. Note at this point scale is not implemented consistently.
The three kinds of transformations are stored in image headers both in core and in disk files.
Operations on 2D images require: one rotation angles, two translation parameters, one scale, and flag mirror indicating whether the image has to be mirrored about x-axis after application of the transformation. Header name: xform.align2d.
Operations of projection and backprojection require: three Eulerian angles (two specify projection direction, the third rotation of projection in plane), two translation parameters that specify in-plane shift of the 2D image (in this case, a projection). Header name: xform.projection.
Operations on 3D maps require: three Eulerian angles, three translation parameters, and one scale. Header name: xform.align3d.
Operations on 2D images
1 # set 2D transformation
2
3 alpha = 25.
4 sx = 7.5
5 sy = -22.2
6 scale = 1.718
7 interpol = "linear"
8 mirror = 0
9
10 convention = EULER_SPIDER
11
12 # set Transform3D object
13 t = Transform3D(convention,alpha,0,0)
14 t.set_scale(scale)
15 t.set_posttrans(sx, sy)
16
17 # print the content
18 print t.get_rotation(convention)
19 print t.get_scale()
20 print t.get_posttrans()
21
22 imi = test_image(size=(250,250))
23 dropImage(imi,"X1.hdf")
24
25 # apply transformation to an image
26 ima = rot_shift2D(imi, alpha, sx, sy, interpol, scale)
27 if mirror: ima.process_inplace("mirror", {"axis":'x'})
28 dropImage(ima,"X2.hdf")
29
30 # combine two 2D transformations
31
32 alpha1 = 25.
33 sx1 = 7.5
34 sy1 = -22.2
35 scale1 = 1.718
36 mirror1 = 0
37
38 alpha2 = 25.
39 sx2 = 7.5
40 sy2 = -22.2
41 scale2 = 1.718
42 mirror2 = 0
43
44
45 interpol = "linear"
46
47 convention = EULER.SPIDER
48
49 # This is a place holder. I do not have a function that would also include scale.
50 alpha3, sx3, sy3 , mirror3 = combine_params2(alpha1, sx1, sy1, mirror1, alpha2, sx2, sy2, mirror2)
51
52
53 # invert 2D transformation
54
55 alpha, sx, sy, scale = inverse_transform2(alpha, sx, sy, scale)
56 alpha, sx, sy ,junk = combine_params2(0.,0.,0., mirror, alpha, sx, sy, 0)
57
58 imo = rot_shift2D(ima, alpha, sx, sy, interpol, scale)
59 if mirror: imo.process_inplace("mirror", {"axis":'x'})
60 dropImage(imo,"X3.hdf")
61
62
63 t_inv = t.inverse()
64
65 print t_inv.get_rotation(convention)
66 print t_inv.get_scale()
67 print t_inv.get_total_posttrans()
68
69 ialpha = t_inv.get_rotation(convention)["psi"]
70 isx = t_inv.get_total_posttrans()[0]
71 isy = t_inv.get_total_posttrans()[1]
72 iscale = t_inv.get_scale()
73
74 print ialpha, isx, isy, mirror, iscale
75 imo = rot_shift2D(ima, ialpha, isx, isy, interpol, iscale)
76 if mirror: imo.process_inplace("mirror", {"axis":'x'})
77 dropImage(imo,"X3.hdf")
Operations of projection and backprojection
Operation of projection (mathematically ray-transform) generates a 2D projection image from a 3D density map. It requires two translation parameters (shift of projection in-plane) and three Eulerian angles (two specify projection direction, third rotation of projection in-plane). Conceptually, 3D map is first projected and then 2D image is translated.
Operation of backprojection is a part of an algorithm of reconstruction from set of projections. (Note mathematically backprojection is not an inverse operation of ray-transform, it is the reconstruction algorithm that approximates the inversion). Backprojection requires the same five orientation parameters that backprojection does.
Although strictly speaking transformation objects associated with projection and backprojection should be inverse of each other, for convenience we use the same transformation object for both and the operations themselves perform implicitly necessary inverse. The main thing the user has to know is that translation parameters associated with the projection/backprojection transformation object can be applied directly to 2D projection (without change of sign) in order to obtain centered image.
To set the projection transformation:
Using pseudocode, the projection and backprojection are realized as:
1 # projection
2 v = test_image_3d()
3
4 v.rotate(t) # uses only Eulerian angles
5 p = v.project_in_Zdirection()
6 p.shift(-t.get_posttrans()[0], -t.get_posttrans()[1])
7 p.set_attr('xform.projection', t)
8
9 # backprojection
10 b = p.copy()
11 t = b.get_attr('xform.projection')
12 b.shift(t.get_posttrans()[0], t.get_posttrans()[1])
13 r = EMData(size3D)
14 r.backproject(b,t) # uses only Eulerian angles, the inversion of rotation is implied in the backproject code
Operations on 3D maps
1 # set 3D transformation
2
3 phi = 25.9
4 theta = 118.3
5 psi = 77.3
6
7 sx = 7.5
8 sy = -22.2
9 sz = -12.9
10 scale = 0.7
11
12
13 t = Transform3D(EULER_SPIDER,phi, theta, psi)
14 t.set_scale(scale)
15 t.set_posttrans(sx, sy, sz)
16
17 print t.get_rotation(EULER_SPIDER)
18 print t.get_scale()
19 print t.get_posttrans()
20
21
22
23 # invert 3D transformation
24
25 print " INVERSE"
26
27 t_inv = t.inverse()
28
29 print t_inv.get_rotation(EULER_SPIDER)
30 print t_inv.get_scale()
31 print t_inv.get_total_posttrans()
32
33 iphi = t_inv.get_rotation(EULER_SPIDER)["phi"]
34 itheta = t_inv.get_rotation(EULER_SPIDER)["theta"]
35 ipsi = t_inv.get_rotation(EULER_SPIDER)["psi"]
36 isx = t_inv.get_total_posttrans()[0]
37 isy = t_inv.get_total_posttrans()[1]
38 isz = t_inv.get_total_posttrans()[2]
39 iscale = t_inv.get_scale()
40
41
42 print iphi, itheta, ipsi, isx, isy, isz, iscale
43
44 # combine transformations
45 print " COMBINE"
46 phi2 = 25.9
47 theta2 = 118.3
48 psi2 = 77.3
49
50 sx2 = 7.5
51 sy2 = -22.2
52 sz2 = -12.9
53 scale2 = 1.4
54
55 print compose_transform3(phi,theta,psi,sx,sy,sz,scale,phi2,theta2,psi2,sx2,sy2,sz2,scale2)
56
57
58 t2 = Transform3D(EULER_SPIDER,phi2, theta2, psi2)
59 t2.set_scale(scale2)
60 t2.set_posttrans(sx2, sy2, sz2)
61
62 tt = t*t2
63
64 print tt.get_rotation(EULER_SPIDER)
65 print tt.get_scale()
66 print tt.get_total_posttrans()
67
68
69 iphi = tt.get_rotation(EULER_SPIDER)["phi"]
70 itheta = tt.get_rotation(EULER_SPIDER)["theta"]
71 ipsi = tt.get_rotation(EULER_SPIDER)["psi"]
72 isx = tt.get_total_posttrans()[0]
73 isy = tt.get_total_posttrans()[1]
74 isz = tt.get_total_posttrans()[2]
75 iscale = tt.get_scale()
76 print iphi, itheta, ipsi, isx, isy, isz, iscale
Transformations between three levels of transformations: align2d, projection, and align3d
It is often necessary/desirable to merge transformations obtained at different stages of the project. As an example, imagine a 3D map of a complex has been determined using an ab initio computational structure determination method (for example common lines approach) and subsequently refined to resolution sufficient to make a comparison with X-ray crystallographic structures of some of its domain. At this point we realize that the hand of the 3D map is incorrect (as common lines method cannot determine it). Therefore, we would like to mirror (change the hand) of the 3D map (this is align3d transformation) but also appropriately change parameters associated with 2D projection data (these are projection transformations) such that when 3D reconstruction is repeated, the resulting 3D map has correct hand.
align2d -> projection
1 def params_2D_3D(alpha, sx, sy, mirror):
2 """
3 Convert 2D alignment parameters (alpha,sx,sy, mirror) into
4 3D alignment parameters (phi, theta, psi, s2x, s2y, mirror)
5 """
6 phi = 0
7 psi = 0
8 theta = 0
9 alphan, s2x, s2y, scalen = compose_transform2(0, sx, sy, 1, -alpha, 0, 0, 1)
10 if( mirror > 0 ) :
11 phi = (540.0 + phi)%360.0
12 theta = 180.0 - theta
13 psi = (540.0 - psi + alphan)%360.0
14 else:
15 psi = (psi + alphan)%360.0
16 return phi, theta, psi, s2x, s2y
projection -> align2d
1 def params_3D_2D(phi, theta, psi, s2x, s2y):
2 """
3 Convert 3D alignment parameters ( phi, theta, psi, s2x, s2y) # there is no mirror in 3D!
4 into 2D alignment parameters (alpha, sx, sy, mirror)
5 """
6 if (theta > 90.0):
7 alpha = (720.0 - psi + phi)%360.0
8 mirror = 1
9 else:
10 alpha = (720.0 - psi - phi)%360.0
11 mirror = 0
12 alphan, sx, sy, scalen = compose_transform2(0, s2x, s2y, 1.0, -psi-phi, 0, 0, 1.0)
13
14 return alpha, sx, sy, mirror
the following are missing
align3d -> projection
align3d -> align2d
Technical Details
Helpful Links
For a more information on the contents of 3D rotation matrices please consult the Sparx Euler Angles page.
The Transform3D Class
What Phil has to say
A Transform3D object is comprised of 16 floats, arranged as a 4 by 4 matrix. The (3,3) element is always one, and the upper 3 by 4 block is a representation of the group of affine transformations representing translation, rotation, mirroring and uniform scaling. The inverse of such a 3 by 4 matrix may also be represented as a similar sort of 3 by 4 matrix. Compositions of transformations of any numbers of such transformations can then be found as matrix multiplications of these 3 by 4 blocks. It remains then to describe the elements (3,0), (3,1) (3,2) elements of the matrix composing the Transform3D object, which are used for storage, and not matrix multiplication, and described directly below.
There is a convenience, at times, of knowing the sequence of operations constituting a Transform3D object, especially when it is composed of a sequence of a translation, then rotation, followed by further translation. The pre and post translations cannot be disentangled from knowledge of the upper 3 by 4 block. For that reason, the “post-translation” is stored in the first three elements of the last row. The pre and post translations can be accessed by simple methods:
- get_posttrans
which simply reads out the first three elements of the last row, and
- get_pretrans
which can be found by subtracting the post_trans from the total translation (stored in the last column), and then applying the inverse transformation of the upper 3 by 3 block to the resultant.
The pre and post translations can be set independently by
- set_posttrans
which keeps the pretrans intact and changes only the posttrans (and therefore also changes the cumulative transformation) and
- set_pretrans
which keeps the posttrans intact and changes only the posttrans (and therefore also changes the cumulative transformation).
It is also possible to keep the cumulative translation intact (and therefore the upper 3 by 4) and redistribute translation between post and pre translations.
Compositions of rotations keep the pretrans of the first Transform3D object to be applied, and calculate the posttrans accordingly.
Internal storage
We use the Transform3D class for storing/managing Euler angles and translations. At any time a Transform3D ($$T3D$$) object defines a group of 3 transformations of a rigid body that are applied in a specific order, namely
$$T3D \equiv T_{post} R T_{pre}$$
Where $$T_{pre} is a pre translation, $$R$$ is a rotation and $$T_{post} is a post translation. The Transform3D object stores these transformations internally in a 4x4 matrix, as is commonly the case in computer graphics applications that use homogeneous coordinate systems (i.e. OpenGL). In these approaches the 4x4 transformation matrix $$T3D$$ is constructed in this way
$$T3D = [[R,\mathbf{t}],[\mathbf{0}^T,1]]$$
Where R is a $$3x3$$ rotation matrix and $$\mathbf{t}=(dx,dy,dz)^T$$ is a post translation. In this approach a 3D point $$\mathbf{p}=(x,y,z)^T$$ as represented in homogeneous coordinates as a 4D vector $$\mathbf{p}_{hc}=(x,y,z,1)^T$$ and is multiplied by the matrix $$M$$ to produce the result of applying the transformation
$$ T3D \mathbf{p}_{hc} = ( (R\mathbf{p} + \mathbf{t})^T, 1 )^T $$
In this way the result of applying a Transform3D to a vector is literally a rotation followed by a translation. The Transform3D allows for both pre and post translation and stores the cumulative result internally
$$T3D = T_{post} R T_{pre} = [[I,\mathbf{t}_{post}],[\mathbf{0}^T,1]] [[R,\mathbf{0}],[\mathbf{0}^T,1]] [[I,\mathbf{t}_{pre}],[\mathbf{0}^T,1]] = [[R,R\mathbf{t}_{pre}+\mathbf{t}_{post}],[\mathbf{0}^T,1]]$$
Support for the mirroring operation in Transform3D
See * EMAN2/TransformConventions/Mirroring_Mirroring_in_the_Transform3D_object
Constructing a Transform3D object in Python
In Python you can construct a Transform3D object in a number of ways
1 from EMAN2 import Transform3D
2 t = Transform3D() # t is the identity
3 t = Transfrom3D(EULER_EMAN,25,45,65) # EULER_EMAN rotation convention uses the az, alt, phi
4 t = Transform3D(EULER_SPIDER,24,44,64) # EULER_SPIDER rotation convention uses the phi, theta, psi convention
5 t = Transform3D(25,45,65) # EULER_EMAN convention used by default, arguments are taken as az, alt, phi
6 t = Transform3D(Vec3f(1,2,3),25,45,65,Vec3f(4,5,6)) # Specify a pre trans, followed by EULER_EMAN convention rotations az, alt, phi, followed by the post trans
7 t = Transform3D(25,45,65,Vec3f(4,5,6)) # EULER_EMAN convention rotations az, alt, phi, followed by the post trans
8 t = Transform3D(1,0,0,0,1,0,0,0,1) # Explicitly setting the nine members of the rotation matrix, row wise.
9 s = Transform3D(t) # copy constructor
Setting Transform3D rotations and translation attributes in Python
You can set the pre and post translations, as well as the rotations, directly from Python
1 from EMAN2 import Transform3D
2 t = Transform3D()
3 # setting the rotations
4 t.set_rotation(25,45,65) # EULER_EMAN convention rotations az, alt, phi
5 t.set_rotation(EULER_SPIDER,24,44,64) # EULER_SPIDER rotation convention uses the phi, theta, psi convention
6 t.set_rotation(EULER_EMAN, {"az":25,"alt":45,"phi":65}) # Optional dictionary style approach
7 t.set_rotation(1,0,0,0,1,0,0,0,1) # Explicitly set the nine members of the rotation matrix, row wise.
8 # setting translations
9 t.set_pretrans(1,2,3)# pre translation dx, dy, dz
10 t.set_pretrans(Vec3f(1,2,3)) # also takes Vec3f argument
11 t.set_pretrans([1,2,3]) # also takes tuple argument
12 t.set_posttrans(4,5,6)# post translation dx, dy, dz
13 t.set_posttrans(Vec3f(4,5,6)) # also takes Vec3f argument
14 t.set_posttrans([4,5,6]) # also takes tuple argument
Retrieving transform3D rotations and translation attributes in Python
You can retrieve attributes using similar syntax to that employed for the setter methods
1 from EMAN2 import Transform3D
2 t = Transform3D(Vec3f(1,2,3),25,45,65,Vec3f(4,5,6)) # Specify a pre trans, followed by EULER_EMAN convention rotations az, alt, phi, followed by the post trans
3 # get rotations
4 dictionary = t.get_rotation(EULER_EMAN) # returns a dictionary with keys "az", "alt" and "phi"
5 dictionary = t.get_rotation(EULER_SPIDER) # returns a dictionary with keys "phi", "theta" and "psi"
6 # get translations
7 pre_trans = t.get_pretrans() # Returns Vec3f(1,2,3)
8 post_trans = t.get_posttrans() # Returns Vec3f(4,5,6)
9 total_post_trans = t.get_total_posttrans # returns the right translation vector from the transformation matrix (Rt_pre + t_post)
10 total_pre_trans = t.get_total_pretrans # returns a vector that can be applied as a pre translation, when when followed by the rotation in the Transform3D object produces the same result
Multiplication
Transform3D times a Transform3D
The main thing to consider when multiplying two Transform3D objects is what will be the ultimate result of asking for the pre_trans and post_trans vectors of the resulting Transform3D object ($$T3D_{rst}$$). To answer this question we look at the details
$$T3D_{rst} = T3D_{2} T3D_{1} = T_{2,post} R_{2} T_{2,pre} T_{1,post} R_{1} T_{1,pre} = T_{2,post} R_{2} T_{2,pre}[[R_{1},R_{1}\mathbf{t}_{1,pre}+\mathbf{t}_{1,post}],[\mathbf{0}^T,1]]$$
$$ = T_{2,post} R_{2} [[R_{1},R_{1}\mathbf{t}_{1,pre}+\mathbf{t}_{1,post}+\mathbf{t}_{2,pre}],[\mathbf{0}^T,1]]$$
The translation in right column ($$R_{1}\mathbf{t}_{1,pre}+\mathbf{t}_{1,post}+\mathbf{t}_{2,pre}$$) is now what will be returned when $$T3D_{rst}$$ is asked for its pre_translation vector from python (or C++). Similarly, the post translation vector of $$T3D_{2}$$ will now be returned by calling get_postrans on $$T3D_{rst}$$. To complete the details, internally the Transform3D object will look like
$$ T3D_{rst} = [[ R_{2}R_{1},R_{2}(R_{1}\mathbf{t}_{1,pre}+\mathbf{t}_{1,post}+\mathbf{t}_{2,pre})+\mathbf{t}_{2,post}],[\mathbf{0}^T,1]]$$
In Python the Transfrom3D x Transform3D operation can be achieved using the '*' operator
Transform3D times a 3D vector (Vec3f)
If v is a three dimensional vector encapsulated as a Vec3f then one can right multiply it by a Transform3D object and this achieves the following result
$$T3D \mathbf{v} = [[R,R\mathbf{t}_{pre}+\mathbf{t}_{post}],[\mathbf{0}^T,1]] \mathbf{v} $$
$$T3D \mathbf{v} = Rv+R\mathbf{t}_{pre}+\mathbf{t}_{post} $$
The vector v is treated implicitly as though it were an homogeneous point, but the last row of the matrix-vector multiplication is not performed.
In Python the Transfrom3D x Vec3f operation can be achieved using the '*' operator or by calling the Transform3D::transform(Vec3f) function
1 T = Transform3D(Vec3f(1,2,3),25,45,65,Vec3f(4,5,6)) # Specify a pre trans, followed by EULER_EMAN convention rotations az, alt, phi, followed by the post trans
2 v = Vec3f(1,2,3) # for example, pixel coordinates 1,2,3
3 v_dash = T*v
4 v_dash = T.transform(v) # Achieves the same result as calling T*v
Explicitly rotating a 3D vector (Vec3f)
If a Transform3D is represented as
$$T3D = [[R,\mathbf{t}],[\mathbf{0}^T,1]]$$
One can calculate
$$\mathbf{v}_R = R \mathbf{v}$$
in Python by doing
2D image alignment conventions
The xform.align2d header attribute
The "xform.align2d" EMData attribute stores a Transform3D object denoted here as $$T3D_{ali2D}$$, which represents the alignment of the 2D Image $$M(x,y)$$, as given by the following
$$ M(x,y)_{ali} = T3D_{ali2D} M(x,y) $$
Where $$ M(x,y)_{ali}$$ denotes the aligned image. The Transform3D object has been designed to allow for application of 2D transformations. The internal transformation matrix of Transform3D object that stores only 2D alignment parameters $$T3D_{ali2D}$$ appears just as any other Transform3D object
$$T3D_{ali2D} = T_{post} R T_{pre} = [[I,\mathbf{t}_{post}],[\mathbf{0}^T,1]] [[R,\mathbf{0}],[\mathbf{0}^T,1]] [[I,\mathbf{t}_{pre}],[\mathbf{0}^T,1]] $$
However the rotation and translations can be made 'psuedo-2D', more specifically
$$ R = [[cos phi,sin phi, 0],[-sin phi,cos phi,0],[0,0,1]], \mathbf{t}_{pre} = (dx_{pre},dy_{pre},0)^T, \mathbf{t}_{post} = (dx_{post},dy_{post},0)^T $$
Creating Transform3D objects that describe 2D transformations
To construct "pseudo-2D" Transform3D objects in Python you can use any of the following approaches
1 from EMAN2 import Transform3D
2 # set the rotation
3 t = Transform3D(0,0,25) # 25 is phi
4 t = Transform3D(EULER_SPIDER 0,0,24) # 24 is psi which is equivalent to setting phi
5 # set pre and post trans
6 t.set_pretrans(2,3) # pre translation dx and dy
7 t.set_pretrans(Vec2f(2,3)) # use Vec2f instead
8 t.set_posttrans(-1,-10) # post translation dx and dy
9 t.set_posttrans(Vec2f(-1,-10)) # use Vec2f instead
Transform3D times a 2D vector (Vec2f)
A Vec2f, an EMAN2 object that stores two values $$v_x$$ and $$v_y$$, may be right multiplied against a Transform3D object to efficiently calculate transformed 2D coordinates.
$$ T3D_{ali2D} \dot \mathbf(Vec2f) \equiv [[cos phi,sin phi, cos phi * dx_{pre} + sin phi * dy_{pre} + dx_{post}],[-sin phi,cos phi,-sin phi * dx_{pre} + cos phi * dy_{pre} + dy_{post}],[0,0,1]] ((v_x),(v_y),(1)) $$
The 2D coordinates are multiplied by the internal transformation matrix in the Transform3D to mimic 2D transformation. The Transform3D object is not checked to ensure it describes a single (phi) rotation or whether the current translations are purely 2D. This responsibility is left to the programmer.
Here is an example of doing Transform3D times Vec2f in Python
1 from EMAN2 import Transform3D
2 # make a Transform3D that can be used as a 2D transformation
3 t = Transform3D(0,0,25) # 25 is phi
4 t.set_pretrans(Vec2f(2,3)) # use Vec2f instead
5 t.set_posttrans(Vec2f(-1,-10)) # use Vec2f instead
6 v = Vec2f(2,3) # for example, pixel coordinate 2,3
7 v_trans = t*v # calculates the 2D transformation
3D backprojection alignment conventions
3D projection alignment is used here to denote the set of transformations that must be applied to a projection in order to backproject into a 3D volume, presumably as part of a 3D reconstruction routine.
Transformations and projections
Say the data model is a 3D map denoted M(x,y,z) and a projection is to be generated in a particular direction, possibly including pre or post translation (the latter is a default). The translation information along with the direction of the projection is to be stored in a Transform3D object $$T3D$$, and the projection is to be generated according to or equivalently to the following
$$p(x,y) = int_z T3D M(x,y,z) dz$$
That is, the projection operation can be thought of as first transforming the 3D map M by the Transform3D object, and by subsequently taking line integrals along z. The programmer is free to construct $$T3D_{ali3D}$$ using the guidelines of the Transform3D class.
Transformations and backprojections: the xform.projection header attribute
The "xform.projection" EMData attribute returns a specialized Transform3D $$T3D_{rec}$$ that stores a 2D translation that is to be applied to the projection $$p(x,y)$$ before it is backprojected into the 3D volume $$V(x,y,z)$$ in the orientation dictated by the Transform3D's rotation matrix (Euler angles). More specifically $$T3D_{rec}$$ consists of a rotation and a single post translation, i.e.
$$T3D_{rec} = T_{post,rec} R_{rec} = [[I,\mathbf{t}_{post,rec}],[\mathbf{0}^T,1]] [[R_{rec},\mathbf{0}],[\mathbf{0}^T,1]] = [[R_{rec},\mathbf{t}_{post,rec}],[\mathbf{0}^T,1]]$$
Where $$ t_{post,rec} $$ is defined as
$$ t_{post,rec} = (dx,dy,0)^T $$
[...]
$$ T_{post,rec} p(x,y) \approx int_z R_{rec} M(x,y,z) dz$$
3D map alignment conventions
The xform.align3d header attribute
The "xform.align3d" EMData attribute stores a Transform3D object denoted as $$T3D_{ali3D}$$, which represents the transformation of the 3D map $$M(x,y,z)$$, as given by the following
$$ M(x,y,z)_{ali} = T3D_{ali3D} M(x,y,z) $$
Where $$ M(x,y,z)_{ali}$$ denotes the transformed 3D map. The programmer is free to construct $$T3D_{ali3D}$$ using the guidelines of the Transform3D class.