Differences between revisions 137 and 142 (spanning 5 versions)
Revision 137 as of 2008-08-20 19:08:21
Size: 15385
Comment:
Revision 142 as of 2008-08-20 19:28:53
Size: 17750
Comment:
Deletions are marked like this. Additions are marked like this.
Line 7: Line 7:
Conventions of Eulerian angles recognized are:
 . EMAN
 . SPIDER
 . IMAGIC
 . MRC
 . QUATERNION
 . SPIN
 . XYZ
 . SGIROT

and are set in python code by
{{{#!python
convention = Transform3D.EulerType.SPIDER
convention = EULER_SPIDER # equivalent to the first line
}}}
Line 18: Line 34:

{{{#!python
# set 2D transformation

alpha = 25.
sx = 7.5
sy = -22.2
scale = 1.718
interpol = "linear"
mirror = 0

convention = Transform3D.EulerType.SPIDER

# set Transform3D object
t = Transform3D(convention,alpha,0,0)
t.set_scale(scale)
t.set_posttrans(sx, sy)

# print the content
print t.get_rotation(convention)
print t.get_scale()
print t.get_posttrans()

imi = test_image(size=(250,250))
dropImage(imi,"X1.hdf")

# apply transformation to an image
ima = rot_shift2D(imi, alpha, sx, sy, interpol, scale)
if mirror: ima.process_inplace("mirror", {"axis":'x'})
dropImage(ima,"X2.hdf")

# combine two 2D transformations

alpha1 = 25.
sx1 = 7.5
sy1 = -22.2
scale1 = 1.718
mirror1 = 0

alpha2 = 25.
sx2 = 7.5
sy2 = -22.2
scale2 = 1.718
mirror2 = 0


interpol = "linear"

convention = Transform3D.EulerType.SPIDER

# This is a place holder. I do not have a function that would also include scale.
alpha3, sx3, sy3 , mirror3 = combine_params2(alpha1, sx1, sy1, mirror1, alpha2, sx2, sy2, mirror2)


# invert 2D transformation

alpha, sx, sy, scale = inverse_transform2(alpha, sx, sy, scale)
alpha, sx, sy ,junk = combine_params2(0.,0.,0., mirror, alpha, sx, sy, 0)

imo = rot_shift2D(ima, alpha, sx, sy, interpol, scale)
if mirror: imo.process_inplace("mirror", {"axis":'x'})
dropImage(imo,"X3.hdf")


# what follows does not work. Phil will have to use the code he put in sparx (functions above) and integrate it with the Transform3D class.

t_inv = t.inverse()
invtrans = [ t_inv.at(0,3),t_inv.at(1,3),t_inv.at(2,3) ]

print t_inv.get_rotation(convention)
print t_inv.get_scale()
print t_inv.get_posttrans() # problem!! inverse function did not invert shifts properly
print invtrans

ialpha = t_inv.get_rotation(convention)["psi"]
isx = invtrans[0]
isy = invtrans[1]
iscale = t_inv.get_scale()

print ialpha, isx, isy, mirror, iscale
imo = rot_shift2D(ima, ialpha, isx, isy, interpol, iscale)
if mirror: imo.process_inplace("mirror", {"axis":'x'})
dropImage(imo,"X3.hdf")


}}}

TableOfContents

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 = Transform3D.EulerType.SPIDER
   2 convention = EULER_SPIDER # equivalent to the first line

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.reconstruct.

  • 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 = Transform3D.EulerType.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 = Transform3D.EulerType.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 #  what follows does not work.  Phil will have to use the code he put in sparx (functions above) and integrate it with the Transform3D class.
  64 
  65 t_inv    = t.inverse()
  66 invtrans =  [ t_inv.at(0,3),t_inv.at(1,3),t_inv.at(2,3) ]
  67 
  68 print t_inv.get_rotation(convention)
  69 print t_inv.get_scale()
  70 print t_inv.get_posttrans()  # problem!!  inverse function did not invert shifts properly
  71 print invtrans
  72 
  73 ialpha = t_inv.get_rotation(convention)["psi"]
  74 isx = invtrans[0]
  75 isy = invtrans[1]
  76 iscale = t_inv.get_scale()
  77 
  78 print  ialpha, isx, isy, mirror, iscale
  79 imo = rot_shift2D(ima, ialpha, isx, isy, interpol, iscale)
  80 if  mirror: imo.process_inplace("mirror", {"axis":'x'})
  81 dropImage(imo,"X3.hdf")

Operations of projection and backprojection

Operations on 3D maps

Technical Details

For a more information on the contents of 3D rotation matrices please consult the Sparx [http://macro-em.org/sparxwiki/Euler_angles Euler Angles] page.

The Transform3D Class

EMAN2 uses the [http://blake.bcm.edu/eman2/doxygen_html/classEMAN_1_1Transform3D.html 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]]$$

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

Getting transform3D rotations and translation attributes in Python

You can get these 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 vector = t.get_pretrans() # Returns a Vec3f object containing the translation
   8 vector = t.get_posttrans() # Returns a Vec3f object containing the translation

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

   1 T1 = 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 T2 = Transform3D(25,45,65,Vec3f(4,5,6)) # EULER_EMAN convention rotations az, alt, phi, followed by the post trans
   3 Trst = T2*T1

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