Create a subfolder called lab9
within your
cs111
folder, and put all of the files for this lab in that
folder.
Consider the following buggy Python function:
def find_max_val(grid): max_val = grid[0][0] max_row = 0 max_col = 0 for r in range(len(grid[0])): for c in range(len(grid)): if max_val > grid[r][c]: max_val = grid[r][c] max_row = r max_col = c return [max_row, max_col]
This function is supposed to take a rectangular 2-D list of numbers
grid
(which we will assume has at least one row and one column) and
to return a list containing the row and column indices of the
largest number in grid
. For example, the function should behave
like this:
>>> find_max_val([[10, 5, 8], [2, 14, 20]]) result: [1, 2]
Note that this call should return the list [1, 2]
because the largest
value in the provided 2-D list (the 20
) is in row 1, column 2 of the list.
The current version of this function doesn’t work, and we want you to find and fix the bugs!
Download lab9task1.py
, saving
it in your lab9
folder, and open the file in Spyder.
Run the file and try entering the call above from the console. You should see an error message.
What type of error is it?
What line of code does the error message indicate has produced the error?
One good debugging technique is to add temporary print
statements
to your code. Let’s use that technique to eliminate this error.
Where should we put the print
statement?
What variables should it print?
Re-run the file and try the above call again. What values do the variables have just before the error message?
Based on the values that you see, make the changes needed to eliminate the error message and to allow the function to execute to completion.
Even after eliminating the error message, the function may still produce the wrong results because of a mistake in the logic of the code. For example:
>>> find_max_val([[10, 5, 8], [2, 14, 20]]) result: [1, 0]
Let’s add another print
statement and use it to diagnose and fix
this logic error. Don’t forget to re-run the file after every
change that you make! Keep making the necessary adjustments until
you get the correct result:
>>> find_max_val([[10, 5, 8], [2, 14, 20]]) result: [1, 2]
One test case is not enough to be confident that the function is correct! What other types of cases should we test?
In this lab and in Problem Set 7, we’re
going to manipulate images that are represented as 2-D lists of pixels
– i.e., as 2-D lists of [R,G,B]
triples.
Begin by downloading the following zip file: lab9image.zip
Unzip this archive, and you should find a folder named
lab9image
, and within it several files, including
lab9task2.py
. Open that file in Spyder, and put your work for
this problem in that file. You should not move any of the
files out of the lab9image
folder. Keeping everything together
will ensure that your functions are able to make use of the
image-processing module that we’ve provided.
At the top of lab9task2.py
, we’ve included an import
statement for
the hmcpng
module, which will allow you to convert a PNG image file
to a 2-D lists of pixels, and vice versa.
Notes:
In Spyder, you may see warning symbols next to the lines that import the functions. You can safely ignore these warnings.
If you receive a PermissionError
that stems from the import
statements, you may need to adjust your computer’s
settings.
To see how this module works, run lab9task2.py
in Spyder and enter the
following command from the console:
>>> pixels = load_pixels('spam.png')
After executing this statement, the variable pixels
is a reference to
a 2-D list in which:
each row corresponds to one row of pixels in the image spam.png
each column corresponds to one column of pixels in the image
spam.png
each element pixels[r][c]
of the 2-D list is an [R,G,B]
triple for the pixel at row r
, column c
in the image
spam.png
For example:
>>> len(pixels) # the number of rows (i.e., the height of the image) result: 334 >>> pixels[0][0] # the pixel in the upper-left corner of the image is red result: [255, 0, 0] >>> pixels[10][20] # the pixel at row 10, column 20 is white result: [255, 255, 255]
To see a simple example of modifying an image, enter the following:
>>> for r in range(len(pixels)): pixels[r][r] = [0, 0, 255] # make the pixels on the diagonal blue ([0, 0, 255]) >>> save_pixels(pixels, 'blue_diag_spam.png') blue_diag_spam.png saved.
After executing these commands, your lab9image
folder should now
include a file named blue_diag_spam.png
(although the .png
extension may or may not be visible). Double-clicking on that file
should show you the following image:
In lab9task2.py
, we’ve given you a function
create_uniform_image(height, width, pixel)
that creates
and returns a 2-D list of pixels with height
rows and
width
columns in which all of the pixels have the RGB values
given by pixel
. Because all of the pixels will have the same color
values, the resulting grid of pixels will produce an image with
a uniform color – i.e., a solid block of that color.
Try testing this function by entering the following from the console:
>>> pixels = create_uniform_image(100, 200, [255, 0, 0]) # all red >>> save_pixels(pixels, 'red.png') red.png saved.
Because [255,0,0]
represents the color red, you should obtain the
following solid red image:
We’ve also given you a function called invert()
that
inverts an image – changing the color of each pixel from its current
color [r, g, b]
to [255-r, 255-g, 255-b]
.
Review that function now to see how it works.
A few things worth noting:
invert()
takes a 2-D list of pixels. To use it, you need to
load the image ahead of time using load_pixels()
, and then
pass the resulting 2-D list of pixels to invert()
. For example:
>>> pixels = load_pixels('spam.png') >>> inverted = invert(pixels)
invert
creates and returns a new 2-D list of pixels for the
inverted image. To actually see the image, you need to store
the return value in a variable (as shown above), and then save
the image using save_pixels()
:
>>> save_pixels(inverted, 'inverted_spam.png') inverted_spam.png saved.
Rescuing Rhett
Students from a rival school (that shall not be named) have corrupted
an image of Rhett to produce the following image:
rhett_sad.png
:
We need to rescue this image by writing a function that takes a 2-D list of pixels and returns a new 2-D list of pixels in which
In lab9task2.py
, we have given you the beginnings of such a
function called rescue
. Note that we have given you variables
for the RBG values of the relevant colors:
maroon = [140, 0, 30] gold = [195, 175, 100] red = [255, 0, 0] black = [0, 0, 0]
You should add the code needed to create and return a “rescued” image,
using an approach that is similar to the one taken by invert
:
Start by creating a new 2-D list with the same dimensions as the
original one and assign it to the variable new_pixels
.
Next, write a nested loop that:
examines the pixel at row r
, column c
in pixels
assigns an appropriate pixel to row r
, column c
of
new_pixels
– one that is based on the color of the pixel
from the original image.
After processing the entire image, return new_pixels
.
You can test your function as follows:
>>> pixels = load_pixels('rhett_sad.png') >>> rescued = rescue(pixels) >>> save_pixels(rescued, 'rescued_rhett.png') rescued_rhett.png saved.
Find the file with that name in your lab9_image
folder.
Double-clicking on it should show you the following (happy!) image
of Rhett:
Trace the following program using a table, and determine the output:
for i in [0, 1, 2]: for j in [3, 5]: k = i + j print(k) print(i)
Here’s the beginning of the table:
i |
j |
k |
value printed |
---|---|---|---|
0 |
3 |
||
5 |
|||
... | |||
Remember that the inner loop executes in its entirety for each value of the outer loop.
Trace the following program using a table, and determine the output:
for i in [3, 2, 1]: for j in range(i): print(i * j) print(j)
Here’s the beginning of the table:
i |
range(i) |
j |
value printed |
---|---|---|---|
3 |
[0, 1, 2] |
0 |
|
... |
Because the sequence used by the inner loop (range(i)
) depends on
the value of the outer loop’s variable (i
), we’ve included a column
in the table for that sequence.
What is the output of the following Python program? Draw a memory diagram to help illustrate your answer.
def foo(values): for i in range(len(values)): values[i] *= 2 def bar(values): for x in values: x *= 2 a = [1, 2, 3] b = a c = b[:] foo(a) bar(c) print('a =', a) print('b =', b) print('c =', c)
If you look closely at our create_uniform_image
function in
lab9task2.py
, you’ll notice that we do not make a deep copy of
the 1-D list represented by pixel
. (To do so, we would have
needed to compute a full slice of pixel
, or to take some
comparable step.)
Failing to perform a deep copy was a deliberate choice on our part, because it allows us to illustrate the impact of Python’s use of references in a dramatic way.
Enter the following commands to create and save
a new image that is based on the pixels used to create red.png
:
>>> pixels[0][0] # the pixel at [0][0] (row 0, column 0) [255, 0, 0] >>> pixels[0][0][1] # the green component of the pixel at [0][0] 0 >>> pixels[0][0][1] = 255 >>> save_pixels(pixels, 'mystery.png') mystery.png saved.
Before viewing this image, try to predict what it will look like...
Now go ahead and view it!
Pretty amazing, isn’t it? There are 20000 cells in the 2-D list
pixels
, and this new image is the result of a single
assignment to the green component of pixels[0][0]
!
Blue light is known to interfere with the body’s sleep cycle, so
apps have been developed to reduce the amount of blue light that
your phone or laptop emits in the evening. Write a function
remove_blue(pixels)
that takes a 2-D list pixels
containing
pixels for an image, and that creates and returns a new 2-D list
of pixels for an image in which the blue component of every pixel
is set to 0.
For example:
>>> pixels = load_pixels('spam.png') >>> no_blue = remove_blue(pixels) >>> save_pixels(no_blue, 'no_blue_spam.png')
If your function is working correctly, you should end up with the
following result:
Last updated on November 4, 2024.