Wednesday, March 25th, 2026¶

Last class, we started working on creating tartan pattern images.

In [1]:
import numpy as np
import matplotlib.pyplot as plt

Project 3: Tartans (cont.)¶

Last class, we worked with the following example tartan recipe.

Pattern :

B14 K6 B6 K6 B6 K32 OG32

where the colors B, K, and OG are given by the RGB triples:

B : [52, 80, 100]
K : [16, 16, 16]
OG : [92, 100, 40]

We used the given recipe to generate an array containing the vertical stripes.

In [2]:
B = (52, 80, 100)
K = (16, 16, 16)
OG = (92, 100, 40)

total_width = 14 + 6 + 6 + 6 + 6 + 32 + 32

vertical_stripes = np.zeros((102, 102, 3), dtype=int)
vertical_stripes[:, :14] = B
vertical_stripes[:, 14:20] = K
vertical_stripes[:, 20:26] = B
vertical_stripes[:, 26:32] = K
vertical_stripes[:, 32:38] = B
vertical_stripes[:, 38:70] = K
vertical_stripes[:, 70:102] = OG

We used the np.transpose function to "flip" the rows/columns of the vertical stripes array and create an array of corresponding horizontal stripes.

In [3]:
horizontal_stripes = np.transpose(vertical_stripes, axes=[1,0,2])

Let's check that the two arrays look correct.

In [4]:
plt.figure(figsize=(8,4))

plt.subplot(1,2,1)
plt.imshow(vertical_stripes)
plt.title('Vertical stripes')

plt.subplot(1,2,2)
plt.imshow(horizontal_stripes)
plt.title('Horizontal stripes')

plt.tight_layout()
No description has been provided for this image

Creating a tartan from vertical and horizontal arrays¶

For the project, we need to generate the authentic tartan pattern. As a warmup, can we generate a checkerboard pattern to interleave these horizontal/vertical stripes? To do so, we want to go row by row, column by column, and alternatingly select a color from the vertical_stripes and horizontal_stripes arrays.

Exercise: Create a checkerboard_tartan array that combines the vertical_stripes and horizontal_stripes arrays in checkerboard pattern.

In [ ]:
checkerboard_tartan = np.zeros((102, 102, 3), dtype=vertical_stripes.dtype)
In [5]:
checkerboard_tartan = np.zeros(vertical_stripes.shape, dtype=vertical_stripes.dtype)
In [ ]:
checkerboard_tartan = np.zeros_like(vertical_stripes)
In [7]:
checkerboard_tartan = np.zeros((total_width, total_width, 3), dtype=int)
nrows, ncols = checkerboard_tartan.shape[:2]

for row in range(nrows):
    for col in range(nrows):
        if row % 2 == 0 and col % 2 == 0:   # If row and column are even
            checkerboard_tartan[row,col] = vertical_stripes[row,col]
        elif row % 2 == 1 and col % 2 == 1: # If row and column are odd
            checkerboard_tartan[row,col] = vertical_stripes[row,col]
        else:                               # If row odd and column even or 
                                            # row even and column odd
            checkerboard_tartan[row,col] = horizontal_stripes[row,col]
In [14]:
plt.figure(figsize=(8,8))
plt.imshow(checkerboard_tartan)
Out[14]:
<matplotlib.image.AxesImage at 0x26e364e4f50>
No description has been provided for this image

Where to go from here:

  • We need a better way of generating the vertical_stripes array.
  • We need to generate the more authentic tartan pattern described in the project page.
  • We need to pad our tartan pattern to be 500 by 500 rows/columns.

Generating the vertical_stripes array algorithmically¶

As mentioned last class, we would like to come up with a better way to generate the vertical_stripes array. That is, we don't want to have to manually define each stripe line-by-line, and we don't want to have to calculate appropriate slices by hand.

As a first step, let's focus on converting the given tartan pattern into something type of data that we can iterate through.

Pattern :

B14 K6 B6 K6 B6 K32 OG32

where the colors B, K, and OG are given by the RGB triples:

B : [52, 80, 100]
K : [16, 16, 16]
OG : [92, 100, 40]

Can we code this information as some sort of Python list(s)?

In [15]:
B = [52, 80, 100]
K = [16, 16, 16]
OG = [92, 100, 40]

widths = [14, 6, 6, 6, 6, 32, 32]
colors = [B, K, B, K, B, K, OG]

Can we find the total width of the pattern without manually adding the widths by hand?

In [16]:
total_width = sum(widths)
print(total_width)
102

With the total width calculated, we can intialize the vertical_stripes array that we will then fill with colored stripes.

Exercise: Use a for loop to iterate through each width/RGB pair and add the corresponding stripe to the vertical_stripes array.

In [23]:
vertical_stripes = np.zeros((total_width, total_width, 3), dtype=int)

start = 0
for width, color in zip(widths, colors):
    end = start + width
    vertical_stripes[:, start:end] = color
    start = end
In [24]:
plt.imshow(vertical_stripes)
Out[24]:
<matplotlib.image.AxesImage at 0x26e384c0e10>
No description has been provided for this image

This is a huge improvement on our previous code. Can we do better? It would be nice if we could use Python to automatically process the "recipe" to generate the lists of widths and colors. We will talk next week about how to process strings in a way that will help with this task.

We also need to tackle the following challenges for the project:

  • We need to generate the more authentic tartan pattern described in the project page.
  • We need to repeat our tartan pattern to fill a 500 by 500 image.

Boolean NumPy arrays¶

A Boolean array is just a NumPy array that contains True and False values. That is, the dtype of the array is Bool. We can easily contruct Boolean arrays using Boolean expressions with arrays.

In [25]:
my_array = np.arange(10)
print(my_array)
[0 1 2 3 4 5 6 7 8 9]
In [26]:
my_bool_array = my_array % 3 == 1
print(my_bool_array)
[False  True False False  True False False  True False False]

We can use logical operators on Boolean arrays:

  • ~ will negate a Boolean array (that is, True becomes False and vice-versa),
  • & works like and for two Boolean arrays, and
  • | works like or for two Boolean arrays.
In [27]:
print(~my_bool_array)
[ True False  True  True False  True  True False  True  True]
In [29]:
my_bool_array2 = my_array % 2 == 0

print(my_bool_array)
print(my_bool_array2)
[False  True False False  True False False  True False False]
[ True False  True False  True False  True False  True False]
In [30]:
print(my_bool_array & my_bool_array2)
[False False False False  True False False False False False]
In [31]:
print(my_bool_array | my_bool_array2)
[ True  True  True False  True False  True  True  True False]

Boolean masks¶

One of the biggest strengths of using Boolean arrays is that they can be used as slicing tools for other arrays. For example, if a is an array (of any type) and mask is a Boolean array of the same shape, then a[mask] is a slice of the a array containing only the values where the corresponding value of mask is True.

We call this type of slicing masking, and the Boolean array the mask.

In [40]:
a = np.arange(25).reshape(5,5)
print(a)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]

Suppose we want to identify the elements of the a array that have remainder 1 after division by 3.

Note: When masking, the resulting array slice is flattened into a 1D array (regardless of the shape of the original array). However, since the changes to the slice propogate back to the original array, we can make changes to this slice while preserving the layout of the data in the original array.

For example, suppose we want to subtract 100 from each value of the a matrix which has remainder 1 after division by 3.

In [41]:
mask = (a % 3 == 1)
print(a)
print(mask)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]
[[False  True False False  True]
 [False False  True False False]
 [ True False False  True False]
 [False  True False False  True]
 [False False  True False False]]
In [42]:
print(a[mask])
[ 1  4  7 10 13 16 19 22]
In [43]:
a[mask] -= 100
print(a)
[[  0 -99   2   3 -96]
 [  5   6 -93   8   9]
 [-90  11  12 -87  14]
 [ 15 -84  17  18 -81]
 [ 20  21 -78  23  24]]

Back to tartans: Masking (optional)¶

Exercise: Write a function checkerboard_mask(n) that takes in an integer n and constructs an n by n Boolean array that alternates True/False everytime you move through a row or column. For example, checkerboard_mask(6) should return an array that looks like: $$ \begin{bmatrix} \text{True} & \text{False} & \text{True} & \text{False} & \text{True} & \text{False} \\ \text{False} & \text{True} & \text{False} & \text{True} & \text{False} & \text{True} \\ \text{True} & \text{False} & \text{True} & \text{False} & \text{True} & \text{False} \\ \text{False} & \text{True} & \text{False} & \text{True} & \text{False} & \text{True} \\ \text{True} & \text{False} & \text{True} & \text{False} & \text{True} & \text{False} \\ \text{False} & \text{True} & \text{False} & \text{True} & \text{False} & \text{True} \end{bmatrix}$$

In [44]:
def get_checkerboard_mask(n):
    checkerboard_mask = np.zeros((n,n), dtype=bool)
    for row in range(n):
        for col in range(n):
            if (row + col) % 2 == 0:
                checkerboard_mask[row, col] = True
    return checkerboard_mask
In [73]:
n = 6

mask = np.zeros((n,n),dtype=bool)
rows = np.arange(n)
cols = np.arange(n)

ROWS, COLS = np.meshgrid(rows,cols)
mask[(ROWS + COLS) % 2 == 0] = True
print(mask)
[[ True False  True False  True False]
 [False  True False  True False  True]
 [ True False  True False  True False]
 [False  True False  True False  True]
 [ True False  True False  True False]
 [False  True False  True False  True]]
In [71]:
COLS
Out[71]:
array([[0, 0, 0, 0, 0],
       [1, 1, 1, 1, 1],
       [2, 2, 2, 2, 2],
       [3, 3, 3, 3, 3],
       [4, 4, 4, 4, 4]])
In [60]:
block = np.array([[True, False],
                  [False, True]])
print(block)
[[ True False]
 [False  True]]
In [45]:
print(get_checkerboard_mask(5))
[[ True False  True False  True]
 [False  True False  True False]
 [ True False  True False  True]
 [False  True False  True False]
 [ True False  True False  True]]

Exercise: Use a Boolean array generated by the checkerboard_mask function as a mask to create the checkerboard tartan pattern from the vertical_stripes and horizontal_stripes arrays.

In [56]:
checkerboard_mask = get_checkerboard_mask(total_width)

checkerboard_tartan = np.zeros_like(vertical_stripes)

checkerboard_tartan[checkerboard_mask] = vertical_stripes[checkerboard_mask]
checkerboard_tartan[~checkerboard_mask] = horizontal_stripes[~checkerboard_mask]
In [57]:
plt.imshow(checkerboard_tartan)
Out[57]:
<matplotlib.image.AxesImage at 0x26e3867c050>
No description has been provided for this image

Exercise: Write a function authentic_mask(n) that takes in an integer n and constructs an n by n Boolean array consisting of the sequences:

  • [True, True, False, False, True, True, False, ...] in the first row,
  • [False, True, True, False, False, True, True, ...] in the second row,
  • [False, False, True, True, False, False, True, ...] in the third row,
  • [True, False, False, True, True, False, False, ...] in the fourth row,
  • ...

For example, authentic_mask(6) should return an array that looks like: $$ \begin{bmatrix} \text{True} & \text{True} & \text{False} & \text{False} & \text{True} & \text{True} \\ \text{False} & \text{True} & \text{True} & \text{False} & \text{False} & \text{True} \\ \text{False} & \text{False} & \text{True} & \text{True} & \text{False} & \text{False} \\ \text{True} & \text{False} & \text{False} & \text{True} & \text{True} & \text{False} \\ \text{True} & \text{True} & \text{False} & \text{False} & \text{True} & \text{True} \\ \text{False} & \text{True} & \text{True} & \text{False} & \text{False} & \text{True} \end{bmatrix}$$

Exercise: Use a Boolean array generated by the authentic_mask function as a mask to create the authentic tartan pattern from the vertical_stripes and horizontal_stripes arrays.

In [ ]: