This lab is adapted from Phil Chodrow, who partially adapted it from an activity created by Evan Peck (Bucknell University).

In this lab, we’ll practice working with images using loops and the RGB color model. Throughout, we’ll be using the middimage module for manipulating image. To use middimage, you should install numpy and pillow through the package manager in Thonny.

Activity 0

Lab Partnership Planning

Before starting today’s lab assignment, exchange contact information with your partner and find at least two hours in your schedule during which you can meet with your partner to finish the lab. If you are genuinely unable to find a time, please come speak with me!

Reminder: you and your partner are a team! You should not move forward to one activity until you are both comfortable with the previous activity.

Introduction

A Totoro is a friendly forest spirit native to charming rural villages in southern Japan. Our image for today depicts several Totoros.

Three Totoros in their natural habitat, accompanied by two young girls.

To begin the lab, download three files:

  • lab6.py: the main lab file. This is the only file you will modify in Thonny.
  • totoro.png: the image file that you will work with. This is a smaller version of the file you will see displayed on this page. Using a smaller file will make your program run faster.
  • middimage.py: the middimage module.

These files must be in the same folder on your computer.

Next, go through these setup steps:

  1. Open lab6.py in Thonny.
  2. Run the file in Thonny and check that the image is displayed. If the image is displayed, you are ready to go! Otherwise, you may need to ask the instructor for help or modify the privacy settings on your laptop.

What We’re Doing

Today’s lab is a series of exercises in which we’ll modify this image by writing Python functions that:

  1. Make a copy of the image.
  2. Modify the pixels of the copy, usually in a for-loop.
  3. Return the copy.

Here’s an example. The boost_red() will return a copy of this picture that is…more red.

The result of calling boost_red(im, 100).

def boost_red(im, level):
    # make a copy of the image, so that we can still use the original one
    im2 = im.copy()

    # loop through all the pixels
    for row in range(im2.height):     # im2.height is the number of rows
        for col in range(im2.width):  # im2.width is the number of columns

            # retrieve the current red level
            # RGB means that red is index 0 of the pixel
            red_level = im2[row, col, 0]

            # add level to the current red level, resulting in a new value
            new_red_level = red_level + level

            # ensures that the red level is never larger than 255
            if new_red_level > 255:
                new_red_level = 255

            # replace the old red level with the new level
            im2[row, col, 0] = new_red_level
    
    # return the modified image
    return im2

Activity 1

Take some time to ensure that you understand how this function works. Then, check that you are able to call boost_red(im, 100) and achieve the result shown in the example above.

  • How would you modify this function so that the user could also subtract red by entering a negative integer for level? Please consider what would happen if the new_red_level was less than 0, and how you would address this case. State in a sentence or two what your modification would be. You are not required to implement a function with your modification.
  • How would you modify this function to create a function boost_blue()? In the Activity 1 area, state in a sentence or two what change would be needed. You don’t have to implement the function.

Pixel-Wise Transformations

In this part of the lab, almost all of your functions are going to have a structure that is very similar to boost_red(). In each of the functions you’ll write here, your code should look something like this:

def my_function(im, level):
    im2 = im.copy()
    for row in range(im2.height):     
        for col in range(im2.width):  
            # do something to the pixel at im[row, col]
    return im2

Activity 2

Write a function called invert() that accepts a single argument: im, the image to be inverted. You should implement your function in the ACTIVITY 2 area of the lab file. The function should invert all three color values of every pixel. Inverting a color value means subtracting it from 255. For example, if a pixel has a red value equal to 180, then the inverted pixel should have a red value equal to 255 - 180 = 75.

For full credit, use an additional for-loop to loop through the three color channels. The ideal solution likely involves for color in range(3): or something very similar…

Once you’ve written your function, you can test it like this:

inverted = invert(im)
inverted.show()

Once you are satisfied with the result, please save your image. To do so, run

inverted.save("inverted.png")

The result of calling invert(im).

Now, submit to the autograder (upload lab6.py and inverted.png). If you pass the invert function tests, you are ready for the next activity. The autograder tests using both totoro.png and one other file. If you are failing the test that uses the other file, make sure that you are using im.width and im.height in your code, rather than hardcoding values.

Activity 3

Write a function called greyscale() that accepts a single argument: im, the image to be converted to greyscale. You should implement your function in the ACTIVITY 3 area of the lab file. The function should convert the image to greyscale. To convert a single pixel to greyscale:

  1. Calculate the mean value of each of the red, blue, and green color values. Call this value m.
  2. Set all three color values of the pixel equal to m.

Once you’ve written your function, you can test it like this:

grey = greyscale(im)
grey.show()

Once you are satisfied with the result, please save your image. To do so, run

grey.save("greyscale.png")

The result of calling greyscale(im).

Now, submit to the autograder (upload lab6.py, inverted.png, and greyscale.png). If you pass the greyscale function tests, you are ready for the next activity.

Selective Transformations

So far, we’ve written several functions to manipulate an entire image. Suppose we instead want to modify only part of an image.

Activity 4

Pick one of the the functions from Activities 2-3 and make a selective version. This version will apply the specified transformation, but only to a chosen rectangular region of the image. This function should have all the same arguments as the old function, plus four new arguments:

  • row1, the top row of the selected region.
  • col1, leftmost column of the selected region.
  • row2, the bottom row of the selected region.
  • col2, the rightmost column of the selected region.

Your function should apply your chosen transformation only within that region. If you choose to write a selective version of the greyscale function, the autograder will expect a function called selective_greyscale. If you choose to write a selective version of the invert function, the autograder will expect a function called selective_invert.

Please write your function definition in the ACTIVITY 4 area of the lab file. Please also save a copy of your image.

An example of using a selective version of the greyscale function from Activity 4.

Now, submit to the autograder (upload lab6.py, inverted.png, and greyscale.png). If you pass the selective function tests, you are ready for the next activity. You do not need to submit your image file for this activity. The tests will focus on confirming that your funciton works for multiple regions..

Tiling, Mirroring, and Framing

You can create a new, blank MiddImage like this:

blank = mi.new(width=100, height=200)

A common usage for creating blank images is so that we can “transplant” pixels from another image into them. For example, here’s a function that uses this approach to place only part of an image inside a large black space.

def transplant(im):
    im2 = mi.new(width = im.width, height = im.height)
    for row in range(50, 200):
        for col in range(200, 400):
            im2[row, col] = im[row, col]
    return im2 

The output of transplant(im).

Activity 5

Write a function called vertical_tile that creates a new image in which the old image is repeated twice, one under the other. To do this, create a blank MiddImage that is the same width as the original image and twice the height. Then, use for-loops to modify the pixels.

Please write your function definition in the ACTIVITY 5 area of the lab file.

Once you’ve written your function, you can test it like this:

tiled = vertical_tile(im)
tiled.show()

Please also save a copy of your image:

tiled.save("tiled.png")

The output of vertical_tile(im).

Now, submit to the autograder (upload lab6.py, inverted.png, greyscale.png, and tiled.png). If you pass the vertical_tile function tests, you are ready for the next activity.

Activity 6

Write a function called vertical_mirror that creates a new image in which the old image is repeated twice, one under the other, with the bottom one turned upside down. To do this, create a blank MiddImage that is the same width as the original image and twice the height. Then, use for-loops to modify the pixels.

Please write your function definition in the ACTIVITY 6 area of the lab file.

Once you’ve written your function, you can test it like this:

mirrored = vertical_mirror(im)
mirrored.show()

Please also save a copy of your image:

mirrored.save("mirrored.png")

The output of vertical_mirror(im).

Now, submit to the autograder (upload lab6.py, inverted.png, greyscale.png, tiled.png, and mirrored.png). If you pass the vertical_mirror function tests, you are ready for the next activity.

OPTIONAL: Do Something Creative!

This part of the lab is optional and ungraded! If you finish the rest of the lab during the lab period, please work on this activity.

Optional Activity

For this activity, it’s fine if you’d like to use a different image than the one I supplied.

Working with your partner, develop an idea for a creative function that combines some of the ideas from the previous parts, adding any new components that you’d like to incorporate.

Please write your function definition in the OPTIONAL ACTIVITY area of the lab file.

One example of a creative output, combining the inversion, framing, and mirroring functions that we developed in previous activities.

Submit Your Work

On Gradescope, please upload both your lab6.py file and all of the images that you generated while completing the lab. Only one lab partner needs to make a submission. Make sure to add the other partner’s name!!

Partner Feedback

Fill out the partner feedback form by the lab due date for participation points!