[Optional Extension] Assignment 5 - Image Processing

Due: Thursday, May 23, 2024, at 10pm

This assignment is completely optional. If you receive at least 80% of the points, you will gain one extra token. If you receive at least 50% of the points (but not quite 80%), you will gain half of an extra token.

You may work alone or with a partner, but you must type up the code yourself. You may also discuss the assignment at a high level with other students. You should list any student with whom you discussed each part, and the manner of discussion (high-level, partner, etc.) in a comment at the top of each file. You should only have one partner for an entire assignment.

If you worked with a partner for the base version of this assignment, you may not work with another partner for the extension unless neither of you plans to resubmit the base version.

You will be extending your original imageProcessing.py code. It would be a good idea to copy your original code to a imageProcessingExtension.py file, and work in that file.

Parts of this assignment:

Comments and collaboration

As with all assignments in this course, for each file in this assignment, you are expected to provide top-level comments (lines that start with # at the top of the file) with your name and a collaboration statement. For this assignment, you have multiple programs; each needs a similar prelude.

You need a collaboration statement, even if just to say that you worked alone.

Note on style:

The following style guidelines are expected:

  • Variable names should be clear and easy to understand, should not start with a capital letter, and should only be a single letter when appropriate (usually for i, j, and k as indices, potentially for x and y as coordinates, and maybe p as a point, c for a circle, r for a rectangle, etc.).
  • It’s good to use empty lines to break code into logical chunks.
  • Comments should be used for anything complex, and typically for chunks of 3-5 lines of code, but not every line.
  • Don’t leave extra print statements in the code, even if you left them commented out.
  • Make sure not to have code that computes the right answer by doing extra work (e.g., leaving a computation in a for loop when it could have occurred after the for loop, only once).

Note: The example triangle-drawing program on page 108 of the textbook demonstrates a great use of empty lines and comments, and has very clear variable names. It is a good model to follow for style.

Getting started

Here is an example image after you have completed all parts of this assignment. Make sure to try it out on an image of your own! (Note that it seems that the image has to be a .gif or maybe a .png file unless you install additional libraries.) Also, it’s a good idea to use fairly small images for this assignment, so you can view all eight versions of the image at once. An image no larger than 400x300 would be a good choice – you can use many freely available programs to resize an image.

<image: different image processing outputs>

The original .png image is here.

The file type .gif involves “lossy” compression, which means it takes less space to store the picture, but you lose some color detail. This can lead to interesting artifacts using the file therese.gif instead of therese.png, as shown below:

<image: different image processing outputs with .gif>

The original .gif image is here.

If your Python install isn’t working with .png files but you get the above output, your sharpening may well be working fine. Check with me if you’re concerned.

Part 1: Generalizing image processing

The get<Something>Color functions serve as examples of polymorphism: all take in an image and the x and y coordinates of a pixel and return a color. Replace your create<Something>Image functions with a single function that takes a getColorFunc function as a new parameter and returns the new image.

Make sure to always use the original image when calling the getColorFunc function!

Add this to your program, remove the existing create<Something>Image functions, and then “refactor” your program to call the createImage function instead:

def createImage(origImage, getColorFunc):
    """
    Creates a copy of the original image, and recolors the copy using
    getColorFunc for each pixel in the original image.

    origImage: an Image object to copy
    getColorFunc: the function to call for each pixel

    returns: the new image
    """
    return None # TODO: implement me!

Part 2: Sharpening an image

Now, you will implement a new get<Something>Color function to sharpen an image.

Add a function getSharpColor that sharpens an image by performing a weighted average (unlike your blurring approach from the original assignment). It should multiply the color at (x,y) by 9, and subtract each of the surrounding pixels’ colors. Make sure to ensure that each color value ends up in the range 0 to 255. (You can think of blurring as weighting each of the nine pixels by +1/9.)

Note: You do not have to handle borders. Similar to the original assignment, you should have a one-pixel border of a color of your choice (just pick one).

def getSharpColor(image, x, y):
    """
    Chooses a color to sharpen an image by taking a weighted
    average across each color channel (R/G/B) of the 3x3 square
    of pixels centered at (x,y).  The pixel (x,y) is weighted
    with +9, and the eight neighbors are weighted with -1.

    Note that if the pixel (x,y) is on the border of the image,
    it just returns a default color.
    
    returns: the result of color_rgb
    """
    # If this pixel is on a border, just return the default color

    # Weight this pixel with +9

    # Weight its eight neighbors with -1

    # Make sure each color value is in the range 0..255,
    # and return the color

    return None # TODO: implement this function!

You can change your main function to the following to try out this new functionality:

def main():
    # Load the original image
    image = Image(Point(0,0), "therese.png")
    width = image.getWidth()
    height = image.getHeight()

    # Create the window to display both images
    win = GraphWin("Image Processing", width*4, height*2)

    # Draw the original image
    image.move(width/2, height/2)
    image.draw(win)

    # Create the grayscale image
    grayscaleImage = createImage(image, getGrayscaleColor)
    grayscaleImage.move(0, height)
    grayscaleImage.draw(win)

    # Create the inverse image
    inverseImage = createImage(image, getInverseColor)
    inverseImage.move(width, 0)
    inverseImage.draw(win)

    # Create the sepia image
    sepiaImage = createImage(image, getSepiaColor)
    sepiaImage.move(width, height)
    sepiaImage.draw(win)

    # Create the blurry image
    blurryImage = createImage(image, getBlurryColor)
    blurryImage.move(width*2, 0)
    blurryImage.draw(win)

    # Create the blurred image
    blurrierImage = createImage(image, getBlurrierColor)
    blurrierImage.move(width*2, height)
    blurrierImage.draw(win)

    # Create the sharpened version of the blurred image
    deblurredImage = createImage(blurryImage, getSharpColor)
    deblurredImage.move(width, 0)
    deblurredImage.draw(win)

    # Create the sharpened version of the original image
    sharpenedImage = createImage(image, getSharpColor)
    sharpenedImage.move(width*3, height)
    sharpenedImage.draw(win)

    # Stick around until the user clicks the window
    win.getMouse()

Reflection

# You should be equipped to complete this part after finishing your assignment.

Were there any particular issues or challenges you dealt with in completing this assignment? How long did you spend on this assignment? Write a brief discussion (a sentence or two is fine) in comments at the bottom of your imageProcessingExtension.py file.

Grading

This assignment will be graded out of 100 points, as follows:

  • 5 points - submit a valid imageProcessingExtension.py file with the right name

  • 5 points - imageProcessingExtension.py file contains top-level comments with file name, purpose, author names, and collaboration statement

  • 10 points - code style enables readable programs

  • 50 points - Part 1 (generalize create<Something>Image functions to single createImage function; code still works otherwise)

  • 25 points - Part 2 (sharpen image using getSharpColor)

  • 5 points - reflection in the comments at the bottom of the imageProcessingExtension.py file

What you should submit

You should submit a single imageProcessingExtension.py file on Moodle.