A document from MCS 275 Spring 2023, instructor Emily Dumas. You can also get the notebook file.

MCS 275 Spring 2023 Homework 10 Solutions

  • Course Instructor: Emily Dumas
  • Contributors to this document: Johnny Joyce

Instructions:

  • Complete the problems below, which ask you to write Python scripts.
  • Upload your python code directly to gradescope, i.e. upload the .py files containing your work.

Deadline

This homework assignment must be submitted in Gradescope by Noon central time on Tuesday March 28, 2023. That's after spring break.

Collaboration

Collaboration is prohibited, and you may only access resources (books, online, etc.) listed below.

Content

This homework is about numpy.

Resources you may consult

Most relevant:

Less likely to be relevant, but also allowed:

Point distribution

This homework assignment has 2 problems, numbered 2 and 3. The grading breakdown is:

Points Item
3 Autograder
6 Problem 2
6 Problem 3
15 Total

The part marked "autograder" reflects points assigned to your submission based on some simple automated checks for Python syntax, etc. The result of these checks is shown immediately after you submit.

What to do if you're stuck

Ask your instructor or TA a question by email, in office hours, or on discord.

Problem 2: Zero border

Submit your answer to this problem in a file hwk10prob2.py.

Write a function zero_border(sh,k,val) that returns a matrix (2D numpy array) of shape sh (a tuple of two integers) in which all entries are filled with value val except for those that are k steps or fewer from the edges of the edges of the matrix.

In doing so, follow these rules:

  • Do not make any explicit for or while loops that involve a number of steps that depends on either of the entries in sh. That is, a call to
    zero_border( (100_000, 50_000), 3, 275)
    
    can't have any explicit for or while loops that run anywhere close to 50,000 times.
  • Do not create any arrays of shape other than sh.

The first of these rules is meant to force you to use numpy constructions in an appropriate way, rather than relying on slower Python-based looping constructions.

Here's some sample output from the function.

In [27]:
zero_border( (5,5), 1, 12)
Out[27]:
array([[ 0,  0,  0,  0,  0],
       [ 0, 12, 12, 12,  0],
       [ 0, 12, 12, 12,  0],
       [ 0, 12, 12, 12,  0],
       [ 0,  0,  0,  0,  0]])
In [28]:
zero_border( (5,5), 2, 12)
Out[28]:
array([[ 0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0],
       [ 0,  0, 12,  0,  0],
       [ 0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0]])
In [29]:
zero_border( (5,5), 3, 12)
Out[29]:
array([[0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0]])
In [30]:
zero_border( (5,5), 200, 12)
Out[30]:
array([[0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0]])
In [31]:
zero_border( (5,5), 0, 12)
Out[31]:
array([[12, 12, 12, 12, 12],
       [12, 12, 12, 12, 12],
       [12, 12, 12, 12, 12],
       [12, 12, 12, 12, 12],
       [12, 12, 12, 12, 12]])
In [32]:
zero_border( (6,8), 1, 77)
Out[32]:
array([[ 0,  0,  0,  0,  0,  0,  0,  0],
       [ 0, 77, 77, 77, 77, 77, 77,  0],
       [ 0, 77, 77, 77, 77, 77, 77,  0],
       [ 0, 77, 77, 77, 77, 77, 77,  0],
       [ 0, 77, 77, 77, 77, 77, 77,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0]])
In [33]:
zero_border( (6,8), 2, 77)
Out[33]:
array([[ 0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0, 77, 77, 77, 77,  0,  0],
       [ 0,  0, 77, 77, 77, 77,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0]])

Solution

Method 1: Fill an array with val, then add in the zeros:

In [ ]:
import numpy as np

def zero_border(sh,k,val):
    """Returns numpy array of size `sh` with zeros on border of size `k` and `val` elsewhere"""
    m = np.full(sh, val)
    m[:k] = 0 # Top
    m[-k:] = 0 # Bottom
    m[ : , :k ] = 0 # Left
    m[ : , -k:] = 0 # Right
    return m

Method 2: Fill an array with zeros, then put in the given value everywhere except the border:

In [ ]:
def zero_border(sh,k,val):
    """Returns numpy array of size `sh` with zeros on border of size `k` and `val` elsewhere"""
    m = np.zeros(sh, dtype=int)
    m[k:-k, k:-k] = val
    return m

Problem 3: Pixels brighter than the onion

Submit your answer to this problem in a file hwk10prob3.py.

Write a program that expects expects there to be two grayscale image files of the same dimensions in the current directory:

  • ws10_grayscale_onion.png from worksheet 10 and
  • photo.png

The program should load these images into numpy arrays, and then use numpy functions and features to answer this question (printing the answer to the terminal):

  • How many pixels in photo.png are brighter (larger grayscale value) than the corresponding pixel in ws10_grayscale_onion.png?
    • Note: Pixels of equal value don't count; you only care about pixels where photo.png is strictly brighter.
  • What percentage of the pixels in this image does this represent?

As with the previous problem, avoid explicit iteration (for or while loops) where the number of steps is one of the image dimensions.

The output should look like this (replacing the pixel count and percentage with the actual values):

txt
Comparing ws10_grayscale_onion.png and photo.png.
Result:
photo.png has 1591 pixels that are brighter than the corresponding ones in ws10_grayscale_onion.png.
That corresponds to 1.33% of the pixels in photo.png being brighter.

You may copy the image-array conversion functions introduced in worksheet 10 into your solution and use them. Here they are for reference:

In [2]:
import PIL.Image
import numpy as np

def load_image_to_array(fn):
    "Open image file with name `fn` and return as a numpy array`"
    img = PIL.Image.open(fn)
    return np.array(img)

def save_array_to_image(fn,A):
    "Save 2D array `A` with dtype `uint8` to file named `fn`"
    assert A.ndims == 2
    assert A.dtype == np.dtype("uint8")
    img = PIL.Image.fromarray(A)
    img.save(fn)

Solution

For this example solution, let's use the image we made in Problem 2 of Worksheet 10 where the average brightness of pixels on each row was taken:

ws10_grayscale_onion_rowavg

Using this image is convenient because it has the same dimensions as the original onion image. Let's save this as photo.png. Then we can use the following code:

In [3]:
fn1 = "ws10_grayscale_onion.png"
fn2 = "photo.png" # Row-average image
onion = load_image_to_array(fn1)
photo = load_image_to_array(fn2)

# Number of pixels in `photo` brighter than in `onion`
num_brighter = np.sum(photo > onion)

# Percentage of pixels brighter
percent_brighter = 100 * num_brighter / onion.size

# Output
print("Comparing {} and {}.".format(fn1, fn2))
print("Result:")
print("{} has {} pixels that are brighter than the corresponding ones in {}".format(fn2, num_brighter, fn1))
print("That corresponds to {:.2f}% of the pixels in {} being brighter.".format(percent_brighter, fn2))
Comparing ws10_grayscale_onion.png and photo.png.
Result:
photo.png has 72212 pixels that are brighter than the corresponding ones in ws10_grayscale_onion.png
That corresponds to 60.18% of the pixels in photo.png being brighter.

Revision history

  • 2023-03-16 Initial publication