Skip to content

Transforms

Eric Kerfoot edited this page Mar 9, 2017 · 23 revisions

This pages discusses 3D transforms and their definition in Eidolon. Transforms are present in image data to define where in space images are placed, whereas meshes are generally defined in world coordinates and so do not need associated transforms. Both image and mesh data can use transforms to define how they may be moved around in space.

In regards to Eidolon and visualizing the sort of data it is defined for, a transform represents a map from one coordinate space to another. Applying a transform to a point will move it from one position in 3D space to another, applying the inverse of the transform will have the opposite effect. Affine transforms are those which transform space uniformly, that is any point anywhere in space will be transformed in the same manner relative to the origin. Transforms can be multiplied together to produce another, however it is important to note that the product of affine transforms is not always affine itself.

The affine transforms to consider are translation (moving points linearly in space), rotation (rotating points around the origin by a certain angle in a certain axis), and scale (multiple all points by values). These can be represented as 4x4 matrices:

  • T(tx,ty,tz): Translation by vector (tx,ty,tz):
[1 0 0 tx]
[0 1 0 ty]
[0 0 1 tz]
[0 0 0  1]
  • S(sx,sy,sz): Scale by vector (sx,sy,sz):
[sx 0 0 0]
[0 sy 0 0]
[0 0 sz 0]
[0 0  0 1]
  • Rx(p): Rotation along X axis (pitch) by angle p:
[1     0      0 0]
[0 cos p -sin p 0]
[0 sin p  cos p 0]
[0     0      0 1]
  • Ry(r): Rotation along Y axis (roll) by angle r:
[cos r  0 sin r 0]
[0      1     0 0]
[-sin r 0 cos r 0]
[0      0     0 1]
  • Rz(y): Rotation along Z axis (yaw) by angle y:
[cos y -sin y 0 0]
[sin y  cos y 0 0]
[    0      0 1 0]
[    0      0 0 1]

Since transform values for an image or mesh are usually defined to move the object in space without distorting its shape, the expected transform given translation, scale, and rotational values would be T(tx,ty,tz)*Ry(r)*Rz(y)*Rx(p)*S(sx,sy,sz).

This will scale the object first, rotate it, then translate it. Other orderings are possible but will have the effect of warping the object in a non-physiological way. This transform is also affine.

Given this definition of the expected transform, we can specify such objects with the input values only: (tx,ty,tz,sx,sy,sz,y,p,r) . In this form, the identity transform which represents no change when applied points in space is (0,0,0,1,1,1,0,0,0) .

Internally in Eidolon transforms are represented by the vec3, rotator, and transform types. A vec3 object is a vector in 3-space having x, y and z components. Adding one vector to another represents translation and multiplying one vector by another represents scaling, thus instances of vec3 are applied in various places as these operations.

A rotator is a quaternion representing rotation around a given axis by a given angle. These can be specified in a number of ways depending on the constructor, but it suffices for now to state that they represent affine rotation. A quaternion can be converted to and from its 4x4 matrix form thus it can be used to represent the three rotation matrices in the above transform definition simultaneously.

A transform instance represents the above transform definition, accepting as arguments (tx,ty,tz,sx,sy,sz,y,p,r) plus a boolean value to indicate if it is an inverse transform or not. An equivalent set of arguments (vec3(tx,ty,tz),vec3(sx,sy,sz),rotator(y,p,r)) can also be provided.

The transform type internally stores the translation, scale, and rotation components separately rather than constructing an internal 4x4 matrix. This allows the components to be queried or modified individually, but consequently also means that transform objects are not strictly composable as matrix transforms are through matrix multiplication. Specifically if the scale components are not all (1,1,1) then composing transforms would introduce skew and therefore would not be affine. The transform type is inherently affine and thus cannot represent skew.

Mesh Transforms

Applying a transform to a mesh moves the mesh's nodes as described above. The other fields and topologies are unchanged since they are not typically spatial data. If a field is meant to represent vectors in world coordinates (ie. has 3 columns where each entry is a 3-vector) then these must be transformed as well.

All mesh scene representations in Eidolon have am applied transform which is the identity by default. Since the above definition for transforms is affine and does not introduce complex transformations such as skew, applying a different transform to a mesh will always result in a mesh whose geometry is relative to itself unchanged but in a different position, orientation, and size.

Image Transforms

Transforms are used to represent the relationship between the reference quad or cube and an image in space, depending on the image type. A 2D image in space can be conceived of as the reference quad (ie. the quad defined by points ((0,0,0),(1,0,0),(0,1,0),(1,1,0))) transformed to the size and position in space as dictated by the loaded data. Similarly a 3D image volume is the unit cube (ie. cube with minimum corner (0,0,0) and maximum corner (1,1,1)) transformed to a size and position in space.

The alternative conceptualization is that the transform maps image xi space to world space coordinates. A xi coordinate is one within the unit quad or cube, that is each component is within the range 0 to 1. Xi coordinate space characterizes all images in an uniform coordinate system allowing the same xi value to represent the same region in any image regardless of world coordinate space position. Applying the transform for a 2D image to the xi coordinate (0,0) will yield the position in space corresponding to the image's minimal corner, and applying the transform to (1,1) will yield the maximal corner. Similarly for a volume image the xi coordinate (0,0,0) maps to the minimal corner, and (1,1,1) to the maximal.

Almost all file formats contain the following spatial information necessary to define this transform:

  • Position (tx,ty,tz) -- Position in world coordinates where an image is defined to be placed at. This position may be the center of the image or one of its corners, converting between definitions is straight forward. Eidolon defines the position as being the coordinate in world space of the image's minimal corner.

  • Pixel/Voxel Size (px,py,pz) -- Size of each image component, pixels in 2D images and voxels in 3D images, and is therefore a 2-vector or a 3-vector.

  • Image Dimension (dx,dy,dz) -- Dimensions of the image itself, stated in terms of number of pixels/voxels in each dimension.

  • Rotation (y,p,r) -- Rotation of the image quad/cube around an origin, which like position is the center of the image, a specific corner, or a specified origin. A rotation of (0,0,0) is expected to represent an unrotated image aligned with the XY plane. Eidolon defines the origin of rotation as the minimal corner of the image.

Assuming that these four components are specified as expected by Eidolon, the final transform can be defined as (tx,ty,tz,px*dx,-py*dy,pz*dz,y,p,r). It is important to note that the y scale dimension is inverted since the y dimension of image data is interpreted as a down direction, thus a 2D image of dimensions (w,h) with no translation or rotation and unit size pixels is defined by the quad from (0,0,0) to (w,-h,0).

Dicom

Image series in Dicom format are composed of multiple 2D image files, each containing pixel data for 1 2D image and associated information in the form of tags. The transform information can be derived from these tags and used to define the image quad for each loaded file. The following is the list of tags by name needed to define a transform and the data they contain:

  • ImagePositionPatient (tx,ty,tz) -- stores a list of 3 numbers representing the 3-vector in world coordinates where the minimal corner of the image is positioned, default value is [0,0,0].

  • ImageOrientationPatient (rx,ry,rz,dx,dy,dz)-- stores 2 3-vectors defining the column (right) and row (down) directions of the image, default value is [1,0,0,0,-1,0]. A rotator in Eidolon can be constructed from these components as such:

rotator(vec3(rx,ry,rz).norm(),vec3(dx,dy,dz).norm(),vec3(1,0,0),vec3(0,-1,0))

  • Columns and Rows (r,c) -- stores single numbers stating how many columns and rows of pixels the image data contains.

  • PixelSpacing (px,py)-- a 2-vector defining the width and height of each pixel.

The given position can be used directly since the definition matches that in Eidolon. Similarly the scale factor is just (c*px,-r*py,0) since each Dicom file is a 2D image. Calculating the orientation is more complex since a quaternion must be defined representing the rotation of the vectors (1,0,0) and (0,-1,0) to match those in the tag. Eidolon's rotator type defines a constructor for calculating the rotation of an orthogonal pair of vectors to another such pair, thus values from the orientation tag can be used to directly construct the needed object.

A matrix form of this rotation is defined here, given that (cx,cy,cz) is the cross product of (rx,ry,rz) and (-dx,-dy,-dz):

[rx -dx cx 0]
[ry -dy cy 0]
[rz -dz cz 0]
[0  0   0  1]

The transform matrix relating the unit quad to the image's world coordinates is defined here:

[rx*px*c dx*py*r 0 tx]
[ry*px*c dy*py*r 0 ty]
[rz*px*c dz*py*r 0 tz]
[0       0       0 1 ]

Multiplying this matrix by the column vector [i j 0 1], where (i,j) is a xi coordinate will result in a world space coordinate corresponding to that relative location in the image.

An alternative matrix which relates pixel indices to world coordinates is defined as:

[rx*px dx*py 0 tx]
[ry*px dy*py 0 ty]
[rz*px dz*py 0 tz]
[0     0     0 1 ]

Multiplying this matrix by the column vector [i j 0 1], where (i,j) is an index in the image's pixel matrix, will result in a world space coordinate corresponding to the center of the pixel at that index.

NIfTI

NIfTI Header Definition

NIfTI file headers contain multiple ways to define transforms for backwards compatibility reasons. Usually however the needed transform components are stored in the following header components:

  • qoffset_x, qoffset_y, qoffset_z -- stores the transform values -tx, -ty, tz respectively
  • quatern_b, quatern_c, quatern_d -- stores the b, c, and d components of a normalized quaternion, form used by Eidolon is given by the expression

rotator(-c,b,(1.0-(b*b+c*c+d*d))**0.5,-d)*rotator(vec3.Z(),halfpi)

  • pixdim -- stores the values qfac, px, py, pz, such that cx=px, cy=py, and cz=qfac*pz
  • dim -- stores first number of dimensions followed by dimensions, so values 1-3 would equate to (dx,dy,dz)

Note the sign of the tx and ty components of the additional rotation applied to the quaternion value, this is to account for the "NIfTI Space" coordinate system. The field xyzt_units also stores which units the dimension and position values are in, as well as timing units. Refer to the NIfTI header definition and NiftiPlugin.py for further explanation. Other reference information can be found in the NiBabel documentation: Nifti Images and Coordinate Systems.

Clone this wiki locally