Wednesday, October 21st, 2026¶

Last class, we worked on constructing tartan images as NumPy arrays. In particular, we looked at the following 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]

In [1]:
import numpy as np
import matplotlib.pyplot as plt
In [18]:
vertical_stripes = np.zeros((102, 102, 3))  # 3D array with the third dimension storing RGB values

vertical_stripes[:, 0:14] = [52/255, 80/255, 100/255]     # First 14 columns have RGB color B = [52, 80, 100]
vertical_stripes[:,14:20] = [16/255, 16/255, 16/255]      # Next 6 columns have RGB color K = [16, 16, 16]
vertical_stripes[:,20:26] = [52/255, 80/255, 100/255]     # Next 6 columns have RGB color B
vertical_stripes[:,26:32] = [16/255, 16/255, 16/255]      # Next 6 columns have RGB color K
vertical_stripes[:,32:38] = [52/255, 80/255, 100/255]     # Next 6 columns have RGB color B
vertical_stripes[:,38:70] = [16/255, 16/255, 16/255]      # Next 32 columns have RGB color K
vertical_stripes[:,70:102] = [92/255, 100/255, 40/255]    # Next 32 columns have RGB color OG = [92, 100, 40]
In [19]:
plt.imshow(vertical_stripes)
Out[19]:
<matplotlib.image.AxesImage at 0x15ecfaddd10>
No description has been provided for this image

Generating the vertical_stripes array algorithmically¶

As mentioned, 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 [4]:
B = [52/255, 80/255, 100/255]
K = [16/255, 16/255, 16/255]
OG = [92/255, 100/255, 40/255]

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

Can we find the total width of the pattern without manually adding the widths by hand? The sum function will add up all elements of a list.

In [5]:
total_width = sum(widths)
In [6]:
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 [7]:
vertical_stripes = np.zeros((total_width, total_width, 3))  # 3D array with the third dimension storing RGB values


for width, color in zip(widths, colors):
    print(width, color)
14 [0.20392156862745098, 0.3137254901960784, 0.39215686274509803]
6 [0.06274509803921569, 0.06274509803921569, 0.06274509803921569]
6 [0.20392156862745098, 0.3137254901960784, 0.39215686274509803]
6 [0.06274509803921569, 0.06274509803921569, 0.06274509803921569]
6 [0.20392156862745098, 0.3137254901960784, 0.39215686274509803]
32 [0.06274509803921569, 0.06274509803921569, 0.06274509803921569]
32 [0.3607843137254902, 0.39215686274509803, 0.1568627450980392]
In [ ]:
 
In [ ]:
 

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 later 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.
In [ ]:
def get_stripes(widths, colors):
    ...
    horizontal_stripes = np.transpose(vertical_stripes, axes=[1,0,2])
    return vertical_stripes, horizontal_stripes
In [ ]:
def get_checkerboard_tartan(vertical_stripes, horizontal_stripes):
    ...
    return checkerboard_tartan

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 [20]:
a = np.ones(10, dtype=bool)
print(a)
[ True  True  True  True  True  True  True  True  True  True]
In [21]:
a = np.arange(10)
print(a)
[0 1 2 3 4 5 6 7 8 9]
In [24]:
b = a > 3
print(a)
print(b)
[0 1 2 3 4 5 6 7 8 9]
[False False False False  True  True  True  True  True  True]

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 [25]:
print(~b)
[ True  True  True  True False False False False False False]
In [27]:
c = (a % 2 == 0) & (a % 3 == 0)
print(a)
print(c)
[0 1 2 3 4 5 6 7 8 9]
[ True False False False False False  True False False False]
In [29]:
d = ((a % 2 == 0) & (a % 3 == 0)) | (a % 4 == 0)
print(a)
print(d)
[0 1 2 3 4 5 6 7 8 9]
[ True False False False  True False  True False  True False]
In [ ]:
 

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. Let's test this out on the array a defined below.

In [30]:
a = np.arange(25).reshape(5,5)
In [31]:
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.

In [32]:
mask = (a % 3 == 1)
print(mask)
[[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 [33]:
a[mask]
Out[33]:
array([ 1,  4,  7, 10, 13, 16, 19, 22])

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 [34]:
a[mask] -= 100
In [35]:
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]]
In [ ]:
 

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 [37]:
horizontal_stripes = np.transpose(vertical_stripes,axes=[1,0,2])
In [38]:
checkerboard_tartan = np.zeros((total_width, total_width, 3))

for row in range(total_width):
    for col in range(total_width):
        if row % 2 == 0 and col % 2 == 0:
            checkerboard_tartan[row, col] = vertical_stripes[row, col]
        elif row % 2 == 0 and col % 2 == 1:
            checkerboard_tartan[row, col] = horizontal_stripes[row, col]
        elif row % 2 == 1 and col % 2 == 0:
            checkerboard_tartan[row, col] = horizontal_stripes[row, col]
        else:
            checkerboard_tartan[row, col] = vertical_stripes[row, col]
In [39]:
plt.imshow(checkerboard_tartan)
Out[39]:
<matplotlib.image.AxesImage at 0x15ef95b8b90>
No description has been provided for this image
In [45]:
checkerboard_mask = np.zeros((total_width, total_width), dtype=bool)

for row in range(total_width):
    for col in range(total_width):
        if row % 2 == 0 and col % 2 == 0:
            checkerboard_mask[row, col] = True
        elif row % 2 == 0 and col % 2 == 1:
            checkerboard_mask[row, col] = False
        elif row % 2 == 1 and col % 2 == 0:
            checkerboard_mask[row, col] = False
        else:
            checkerboard_mask[row, col] = True
In [46]:
print(checkerboard_mask[:10, :10])
[[ True False  True False  True False  True False  True False]
 [False  True False  True False  True False  True False  True]
 [ True False  True False  True False  True False  True False]
 [False  True False  True False  True False  True False  True]
 [ True False  True False  True False  True False  True False]
 [False  True False  True False  True False  True False  True]
 [ True False  True False  True False  True False  True False]
 [False  True False  True False  True False  True False  True]
 [ True False  True False  True False  True False  True False]
 [False  True False  True False  True False  True False  True]]
In [ ]:
 

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 [50]:
checkerboard_tartan = np.zeros((total_width, total_width,3))

checkerboard_tartan[ checkerboard_mask] =   vertical_stripes[ checkerboard_mask]
checkerboard_tartan[~checkerboard_mask] = horizontal_stripes[~checkerboard_mask]
In [51]:
plt.imshow(checkerboard_tartan)
Out[51]:
<matplotlib.image.AxesImage at 0x15ef98f0410>
No description has been provided for this image
In [53]:
base_grid = np.array([[True, False], 
                      [False, True]], dtype=bool)

checkerboard_mask = np.zeros((total_width, total_width), dtype=bool)

for row in range(total_width):
    for col in range(total_width):
        checkerboard_mask[row,col] = base_grid[row % 2, col % 2]
In [54]:
print(checkerboard_mask[:10,:10])
[[ True False  True False  True False  True False  True False]
 [False  True False  True False  True False  True False  True]
 [ True False  True False  True False  True False  True False]
 [False  True False  True False  True False  True False  True]
 [ True False  True False  True False  True False  True False]
 [False  True False  True False  True False  True False  True]
 [ True False  True False  True False  True False  True False]
 [False  True False  True False  True False  True False  True]
 [ True False  True False  True False  True False  True False]
 [False  True False  True False  True False  True False  True]]
In [ ]:
 

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}$$

In [ ]:
 
In [ ]:
 
In [ ]:
 

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 [ ]:
 
In [ ]:
 
In [ ]:
 

Next steps¶

We've discussed better strategies for constructing the vertical_stripes array, which we can then use to construct the horizontal_stripes, checkerboard_tartan, and/or authentic_tartan arrays.

  • The project asks you to produce a 500 by 500 tartan image based on your assigned pattern. The assigned pattern length is less than 500, so you will need to think about how to repeat the pattern enough times to fill in the full 500 by 500 image.
  • It would be good to create functions that perform the tasks of creating these various arrays. For example, a function get_vertical_stripes could take in the tartan recipe as inputs and return the corresponding vertical_stripes array.
  • While we have made big improvements in how we generate the vertical_stripes array, there still was some amount of manual work on our part. Can we do better?