Python101/Session 7 - Coordinate Tran.../Session 7 - Coordinate Tran...

293 KiB

<html> <head> </head>

Session 7 - Tranformation functions

Coordinate transformations can be performed by matrix operations. Some common ones are:

\begin{equation} \tag{Rotation about Origin} \begin{bmatrix} x_{\text{rotated}}\\y_{\text{rotated}}\\1 \end{bmatrix} = \begin{bmatrix} \cos(\theta) & \sin(\theta) & 0 \\ -\sin(\theta) & \cos(\theta) & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} \end{equation}\begin{equation} \tag{Translation} \begin{bmatrix} x_{\text{translated}}\\y_{\text{translated}}\\1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & \Delta x \\ 0 & 1 & \Delta y \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} \end{equation}\begin{equation} \tag{Scale about Origin} \begin{bmatrix} x_{\text{scaled}}\\y_{\text{scaled}}\\1 \end{bmatrix} = \begin{bmatrix} C_x & 0 & 0 \\ 0 & C_y & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} \end{equation}\begin{equation} \tag{Mirror about $y$-axis} \begin{bmatrix} x_{\text{mirrored}}\\y_{\text{mirrored}}\\1 \end{bmatrix} = \begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} \end{equation}
  • In the rotation matrix it is assumed that the angle $\theta$ is a clockwise rotation.
  • In the translation matrix $\Delta x$ and $\Delta y$ denote the absolute translation in the $x$- and $y$-direction, respectively.
  • In the scaling matrix $C_x$ and $C_y$ denote the scaling in the $x$- and $y$-direction, respectively.
  • The mirroring matrix has no inputs and can bee seen as a boolean operation. It can be done in exactly one way or not done at all.

See more here: https://upload.wikimedia.org/wikipedia/commons/2/2c/2D_affine_transformation_matrix.svg

Vectorization in numpy

numpy can perform calculation in a vectorized manner meaning that vector and matrix operations can be done on entire arrays at a time as opposed to value by value.

Vectorization can eliminate the use of for loops in many scenarios, which makes the code easier to read and write. As an added bonus, vectorized calculations are also much faster than their looping counterparts.

For the equations above, we can utilize vectorization by using a arrays (or lists) of values for $x$ and $y$ instead of single values. That implies that $1$ also must be an array of ones with the same size.

Thus, the vector $[x, y, 1]^T$ on the right hand side of the is actually an array of arrays.

The resulting vector $[x_{\text{transformed}}, y_{\text{transformed}}, 1]^T$ is of course also an array or arrays.

Unpacking values

A small example that demontrates unpacking of variables from a function output:

In [1]:
def f():
    '''
    Define function that takes no input and returns three values
    '''
    return 1, 2, 3

# Call function and save result in variable
result = f()
result
Out[1]:
(1, 2, 3)
  • Note: When returning multiple values from a function, they will by default be a tuple. This is basically the same as if the last line of the function would have been return (1, 2)

Unpacking the result to two variables:

In [2]:
# Unpack function output to two variables
a, b, c = f()
a
Out[2]:
1
In [3]:
b
Out[3]:
2
In [4]:
c
Out[4]:
3

As we see, if we do not unpack, all values are saved in a tuple.

But if we decide to unpack, we have to account for all outputs. If we try to unpack only 2 values when the function returns 3 we get this error:

In [5]:
# ValueError, function returns 3 values, but only 2 unpacked
h, i = f()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-393f2c6eb4f0> in <module>
      1 # ValueError, function returns 3 values, but only 2 unpacked
----> 2 h, i = f()

ValueError: too many values to unpack (expected 2)

If we try to unpack too many:

In [6]:
# ValueError, function returns 3 values, but 4 were unpacked
h, i, j, k = f()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-6809bb4f4bd8> in <module>
      1 # ValueError, function returns 3 values, but 4 were unpacked
----> 2 h, i, j, k = f()

ValueError: not enough values to unpack (expected 4, got 3)

Note: When unpacking, the variables of the left side of the equal sign must exactly match the number of outputs, so all unpacked elements are accounted for.

There are fancier ways to unpack which can handle cases where it is not known how many elements are to be unpacked. See for example https://www.python.org/dev/peps/pep-3132/.

Unpacking values for the tranformation examples

The resulting array of arrays will have the code structure

# Define an array of arrays
array_of_arrays = [ [x_values], [y_values], [ones] ]

Unpacking x-values and y_values to their own arrays can be done like this:

# Unpack an array of arrays
x_values, y_values, ones = array_of_arrays

In this case, we don't care about the array of ones. It is a quite common scenario that a function returns some values that are not needed. In Python, it is common comvention to unpack unused variables to an underscore _ like this:

# Convention for unpacking unused variables
x_transformed, y_transformed, _ = array_of_arrays

By following this convention, it is clear to anyvody reading the code that this value is not going to be used throughout the program.

Some numpy functions

numpy.matmul

This function will find the matrix product of two arrays:

In [7]:
import numpy as np

# Define a vector
a = np.array([1, 2, 3])

# Define a matrix
T = np.array([ [1, 1, 5], [3, 1, 1], [5, 0, 1] ])

# Compute the matrix product {T}x{a}
b = np.matmul(T, a) 
b
Out[7]:
array([18,  8,  8])

np.ones

Creates an array of ones of a specifies shape

In [8]:
# Create 1x5 vector of ones (1D array)
np.ones(5)
Out[8]:
array([1., 1., 1., 1., 1.])
In [9]:
# Create 5x5 matrix of ones (2D array)
np.ones([5, 5])
Out[9]:
array([[1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.]])

Exercise 1

Write a function that implements rotation of a array of coordinates x and y. The angle of rotation in clockwise direction should be an input parameter with default value of 90 degress. The function should return the rotated coordinates xr and yr.

Test the function with these arrays.

# Test arrays to transform
x = np.array[-5, 5, 5, 0.5, 0.5, 5, 5, -5, -5, -0.5, -0.5, -5, -5])
y = np.array[-8, -8, -6, -6, 6, 6, 8, 8, 6, 6, -6, -6, -8])

Exercise 2

Write a function that takes a pair of arrays of transformed coordinates xt and yt as input and plots them as a line plot. The function should be able to plot the orignal coordinates as well, but this should be optional and not plotted as default.

Plot the original shape from Exercise 1 together with the rotated shape.

A good way of setting default values for parameters that should not be present as default is by setting them equal to None in the function definition. The code inside the function can then check if they actual values were input

Exercise 3

Write a function that implements translation of an array of coordinates x and y. The translation shall be defined by values (not arrays) x_translate and y_translate, which are the distance the points will move in te $x$- and $y$-direction, respectively.

Test the function with the arrays given in Exercise 1. Plot the translation with the function written in Exercise 2.

Exercise 4

Implement the scaling transformation as a function. Test it by plotting it.

Exercise 5

Implement mirroring tranformation about the $y$-axis as a funcion.

Since the given coordinates in Exercise 1 are symmetic about the $y$-axis, the mirrored coordinates will lie on top of the orignal ones. Try to test it by plotting.

You can quickly make a more visible test by moving all the $x$-coordinates, say 20 units to the right. Since we are using numpy this can be done by simply adding 20 to the array itself x+20. This is a simple example of vectorization.

Exercise 6

Write a function that combines all of the above tranformations in one. When performing multiple transformations at once, the tranformation matrices can be multiplied by each other prior to multiplication with the orignal coordinates.

It could have a structure like this:

def tranform(x, y, rotatation=0, scaling=(1, 1), translation=(0, 0), mirroring=False)):
    '''
    Perform a combined coordinate tranformation according to given inputs. If no inputs are given, returns the unchanged coordinates.

    Args:
        x (array)                   : x-values to transform.
        y (array)                   : y-values to transform.
        rotate (float, optional)    : Clockwise rotation angle in [deg]. Defaults to no rotation.
        scale (float, optional)     : Scaling factor in axes directions (cx, cy). Defaults to no scaling.
        translate (tuple, optional) : Translation in axes directions (dx, dy). Defaults to no translation.
        mirror (bool, optional)     : Whether or not to mirror the coordinates, Defaults to no mirroring.
    '''

    # Code here
  • Remember that rotation and scaling are performed about the origin. So the order of operations will matter.
  • You can call the previously defined functions to perform the individual transformations if you want.
  • Return the tranformed coordinates xt, yt.

Addtional Exercise

Write a alternative function for translation where the translation input can be given as a distance that the points should move and the corresponding angle from the $x$-axis. This can often be useful instead of the one defined earlier where the distances are given parallel to the $x$- and $y$-axes.

</html>