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]
import numpy as np
import matplotlib.pyplot as plt
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]
plt.imshow(vertical_stripes)
<matplotlib.image.AxesImage at 0x15ecfaddd10>
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)?
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]
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.
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.
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]
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
500by500image.
def get_stripes(widths, colors):
...
horizontal_stripes = np.transpose(vertical_stripes, axes=[1,0,2])
return vertical_stripes, horizontal_stripes
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.
a = np.ones(10, dtype=bool)
print(a)
[ True True True True True True True True True True]
a = np.arange(10)
print(a)
[0 1 2 3 4 5 6 7 8 9]
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,TruebecomesFalseand vice-versa),&works likeandfor two Boolean arrays, and|works likeorfor two Boolean arrays.
print(~b)
[ True True True True False False False False False False]
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]
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]
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.
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.
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]]
a[mask]
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.
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}$$
horizontal_stripes = np.transpose(vertical_stripes,axes=[1,0,2])
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]
plt.imshow(checkerboard_tartan)
<matplotlib.image.AxesImage at 0x15ef95b8b90>
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
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]]
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.
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]
plt.imshow(checkerboard_tartan)
<matplotlib.image.AxesImage at 0x15ef98f0410>
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]
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]]
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.
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
500by500tartan image based on your assigned pattern. The assigned pattern length is less than500, so you will need to think about how to repeat the pattern enough times to fill in the full500by500image. - It would be good to create functions that perform the tasks of creating these various arrays. For example, a function
get_vertical_stripescould take in the tartan recipe as inputs and return the correspondingvertical_stripesarray. - While we have made big improvements in how we generate the
vertical_stripesarray, there still was some amount of manual work on our part. Can we do better?