Geometric transforms

../_images/topics_transforms_translate.svg ../_images/topics_transforms_rotate.svg ../_images/topics_transforms_scale.svg ../_images/topics_transforms_reflect.svg

The geometries introduced in the previous chapter can be moved and manipulated using geometric transforms. The transforms can perform actions such as translation and rotation. In this topic guide, we introduce the various transforms available in tomosipo and show how to use them.

Using transforms

The transform creation functions, such as ts.translate, do not perform an actual geometric transformation. Rather, they return an object that can be used to perform actual geometric transformations. In addition, the transform objects can be composed to create more complex motions. This design makes it possible to build complex systems from simple foundations. In this section, we explain how this works in practice.

Creating a transform

A Transform represents a geometric transformation by a 4-by-4 matrix using homogeneous coordinates. These matrices are commonly used in graphics programming and can represent translation, rotation, scaling, reflection, and a host of other transformations. The code below shows an example of such a matrix used for translation.

>>> import tomosipo as ts
>>> T = ts.translate((1, 2, 3))
>>> T
Transform(
    [[[1. 0. 0. 1.]
  [0. 1. 0. 2.]
  [0. 0. 1. 3.]
  [0. 0. 0. 1.]]]
)

Move an object

You can use the transformation to move an object as follows:

>>> vg = ts.volume(shape=1)
>>> T * vg
ts.volume(
    shape=(1, 1, 1),
    pos=(1.0, 2.0, 3.0),
    size=(1.0, 1.0, 1.0),
)

As you can see, the volume has moved from its original position on the origin to position (1, 2, 3). You can move any geometry in tomosipo by multiplying with a Transform.

Composing transforms

In addition to moving geometries, you can multiply transforms with each other to obtain a composed transform. In the code below, we create a translation to move the object back to the origin.

>>> T2 = ts.translate((-1, -2, -3))
>>> T2 * T
Transform(
    [[[1. 0. 0. 0.]
  [0. 1. 0. 0.]
  [0. 0. 1. 0.]
  [0. 0. 0. 1.]]]
)
>>> T2 * T * vg
ts.volume(
    shape=(1, 1, 1),
    pos=(0.0, 0.0, 0.0),
    size=(1.0, 1.0, 1.0),
)

As you can see, the composed transform T2 * T is represented by the identity matrix. Also, the volume is moved back to the origin.

Inverting transforms

In general, any transform can be undone by multiplying with its inverse:

>>> T * T.inv
Transform(
    [[[1. 0. 0. 0.]
  [0. 1. 0. 0.]
  [0. 0. 1. 0.]
  [0. 0. 0. 1.]]]
)

Movement over time

So far, we have used a single transformation. In practice, you probably want to apply multiple transformations over time. This is possible in tomosipo:

>>> T = ts.translate((1, 0, 0), alpha=[0.0, 1.0, 2.0])
>>> T.num_steps
3
>>> T * vg.to_vec()
ts.volume_vec(
    shape=(1, 1, 1),
    pos=array([[0., 0., 0.],
       [1., 0., 0.],
       [2., 0., 0.]]),
    w=array([[1., 0., 0.],
       [1., 0., 0.],
       [1., 0., 0.]]),
    v=array([[0., 1., 0.],
       [0., 1., 0.],
       [0., 1., 0.]]),
    u=array([[0., 0., 1.],
       [0., 0., 1.],
       [0., 0., 1.]]),
)

As you can see, the transformed volume now moves up starting at (0, 0, 0) and ending at (2, 0, 0). This was achieved by multipling the axis of translation by the three values of alpha. The number of time steps in a transform is accessible using the num_steps attribute.

Note

In the code sample, you can see that we have converted vg to a vector geometry before multiplying with T. A normal volume geometry cannot move over time, and a vector volume geometry can. Thus the output must necessarily be a volume vector geometry. If we had not explicitly converted to a vector geometry, tomosipo would have performed the conversion implicitly, while issuing a warning that the conversion had taken place behind the scenes. In general, it is advised to perform these conversions explicitly to aid readers of your code.

Composing multiple transforms over time

Any transform can multiplied by a single-time-step transform. Multiplying two multi-time-step transforms requires that their number of steps is equal. This mirrors the broadcasting rules for numpy arrays.

>>> T1 = ts.translate((1, 0, 0))
>>> T2 = ts.translate((1, 0, 0), alpha=[0.0, 1.0])
>>> T3 = ts.translate((1, 0, 0), alpha=[0.0, 1.0, 2.0])
>>> (T1 * T2).num_steps
2
>>> (T1 * T3).num_steps
3
>>> (T3 * T3).num_steps
3
>>> (T2 * T3).num_steps  # Not allowed
Traceback (most recent call last):
...
ValueError: Cannot multiply transforms with different number of time steps. Got steps: 2 and 3

Overview of transforms

Tomosipo provides six convenience functions to create commonly used transforms. We discuss them here.

Translate

../_images/topics_transforms_translate.svg

As discussed above, the ts.translate function creates a translation transform. The animation you see is generated by the code below.

import tomosipo as ts
import numpy as np
vg = ts.volume(size=1.0).to_vec()

t = np.linspace(0, 1, 5)
T = ts.translate(axis=(0, 1, 0), alpha=t)
ts.svg(T * vg).save("./doc/img/topics_transforms_translate.svg")

Rotate

../_images/topics_transforms_rotate.svg

The ts.rotate function creates a rotation transform. The animation you see is generated by the code below.

import tomosipo as ts
import numpy as np
vg = ts.volume(size=1.0).to_vec()

t = np.linspace(0, 1, 5)

R = ts.rotate(pos=0, axis=(1, 0, 0), angles=np.pi * t)
ts.svg(R * vg).save("./doc/img/topics_transforms_rotate.svg")

Scale

The ts.scale function creates a scaling transform. The animation you see is generated by the code below.

import tomosipo as ts
import numpy as np
vg = ts.volume(size=1.0).to_vec()

t = np.linspace(0, 1, 5)

S = ts.scale((1, 1, 1), alpha=1 + t)
ts.svg(S * vg).save("./doc/img/topics_transforms_scale.svg")

Reflect

The ts.reflect function creates a reflection transform. The animation you see is generated by the code below.

import tomosipo as ts
import numpy as np
vg = ts.volume(size=1.0).to_vec()

t = np.linspace(0, 1, 5)

mirror = ts.volume(pos=(0, 2, 0), size=(2, 0, 2))
M = ts.reflect(pos=mirror.pos, axis=(0, 1, 0))
ts.svg(
  vg,
  M * vg,
  mirror,
).save("./doc/img/topics_transforms_reflect.svg")

To / from perspective

../_images/topics_transforms_perspective1.svg ../_images/topics_transforms_perspective2.svg

The functions ts.to_perspective and ts.from_perspective create perspective transforms. The animation you see are generated by the code below.

import tomosipo as ts
import numpy as np
vg = ts.volume(size=0.5).to_vec()
pg = ts.cone(angles=5, size=2, src_orig_dist=2, src_det_dist=4).to_vec()

ts.svg(vg, pg).save("./doc/img/topics_transforms_perspective1.svg")

P = ts.from_perspective(vol=pg.to_vol())

ts.svg(P * vg, P * pg).save("./doc/img/topics_transforms_perspective2.svg")