CSCI 150: Lab 4

Pictures and Images
Due: 10PM on Tuesday, March 6

The purpose of this lab is to:

  • Explore exceptions
  • Get a lot more practice using and creating functions
  • Express your creativity!
  • Get (much more) practice with nested loops
  • Implement a basic photo editor

Before you begin, please create a folder called lab04 inside your cs150 folder (for a refresher, here's how you did this on lab 02). This is where you should put all files made for this lab.

Part 1 - Using Exceptions

monte.py: 4 points.

Implement a Design

Copy your monte.py program from lab 03 to your lab04 directory and modify it to handle at least 2 exceptions (such as if the user enters n=0).

Handin:

Please handin what you have of your lab so far.

Part 2 - Primes

primes.py: 12 points, individual.

As you may know, a number x is said to be prime if x is at least 2, and the only proper factors of x are itself and 1. So the first few primes are 2, 3, 5, 7, 11, 13, 17, 19, 23, etc. 4 isn't prime, since it is divisible by 2. Same goes for 6 and 8. 9 is out thanks to 3. And so on. There are a lot of primes. More precisely, there are infinitely many primes. This can actually be shown pretty easily; ask if you're curious.

A twin prime is a pair of prime numbers that differ by exactly 2. So (3,5), (5,7), (11,13), (17,19) and (29, 31) are all twin primes. Note that not every prime is part of a twin prime. It is conjectured that there are infinitely many twin primes too, but no one knows for sure.

Describe the Problem

Write a program called primes.py that prints out some number of primes and the number of twin primes amongst them.

Input: A number n.
Output: The first n primes, and the number of twin primes amongst these n.

Understand the Problem

If the user enters 13 then the output should be

  The first 13 primes are:
  2 3 5 7 11 13 17 19 23 29 31 37 41
  Amongst these there are 5 twin primes.
                          
Note that (41, 43) is a twin prime, but we didn't count it since 43 wasn't amongst the first 13 primes.

Design an Algorithm

Write pseudocode to solve this problem. You should decompose your main algorithm into small manageable chunks. For example, you should:

  • design an algorithm that takes in an integer x and determines whether x is prime. For example, "isPrime(10)" would return false, while "isPrime(31)" would return true. You did this on your prelab.
  • make liberal use of this isPrime method to generate the first n primes.

Implement a Design

You may want to use a while loop as you search for primes, since you won't know ahead of time just how far you need to go.

Test the Program

Try your program with a variety of inputs n. Certainly you should try n=0,1,13 but you should also try n=14 to get that one extra twin prime, as well as others!

Handin

Please handin what you've completed thus far.

Part 3 - Image Manipulation

imageEdit.py: 25 points, partner allowed (and recommended).

If you choose to work with a partner, only one of you should submit a solution, but both of you should indicate your partner and who submitted a solution in your README files. You should also both read the Recurse Center's guide on pair programming before you begin. You may not simply split up the functions and complete them separately. All code should be written with both partners working together at a single machine. Do not commit to working with a partner until you're sure your schedules are compatible for working together outside of scheduled lab time.

In this (major) portion of the lab you'll create a nifty program that reads in an image and does a sequence of modifications to that image, as specified by the user. Things like inverting the image, mirroring it, increasing or decreasing the contrast, etc. In doing so, you'll get more practice with nested for loops, while loops, creating methods and using objects.

To be able to edit individual pixels, we will be using a different (though very similar) image module called picture2.

Describe the Problem:

Write a program called imageEdit.py that provides the user with image editing functionality. The user should first be prompted to enter a file name. That file should be loaded and displayed. Then the user should be presented with a list of filters that can be applied.

Understand the Problem:

Your program should be capable of the following operations:

  1. Make Negative
  2. Make Grayscale
  3. Flip Horizontally
  4. Scroll Horizontally
  5. Zoom
  6. Your Choice: (Pick one -- Blur, Posterize, Increase Contrast, etc.)
  7. Quit
Examples of these operations are given below.
Original Image:
original
Negative: Greyscale:
flip mirror
Flip: Scroll:
scroll negative
Zoom:
zoom
Other Ideas
Posterize: Blur:
poster scroll
Find Edges: Tiled:
find edges tiling
Shear: Bizarre:
shear bizarre
Contrast:
negative

Design an Algorithm:

Your program should begin by printing a welcome to the user, informing them just how fortunate they are to have stumbled upon your very own image editor. You should then prompt the user in the console to pick a file to load in. As will be standard from here on out, you'll want to be robust against bad user input, so if the user enters something that isn't a valid file and an exception is thrown, you can catch it and prompt them again for a file.

Once you have an image loaded, display it. Then use a while loop to repeatedly print a table of possible operations, prompt the user to select one of these operations to apply to their image, apply the selected operation (create a distinct function for each operation), and display the resulting image. Thus, the user might choose to reflect the image, then increase the contrast (of the now reflected image), and then blur (the now reflected and contrasty image). At each step, the user should be able to see the resulting image. Again, be sure to handle bad input from the user.

Of course, this still leaves out the details of each operation, for which you will want to write pseudocode before you start writing functions. Details for some of these operations are given below.

Negative: The negative of an image is creating by inverting each color channel. So if the red value of a pixel were 255, it should become 0. If it were 254, it should become 1, and so on, down to 0, which should become 255. Similarly for green and blue.

Grayscale: Shades of gray have the same red, green and blue value. To convert an image to grayscale, you want to set the red, green and blue values all to the average value of the three channels of the original pixel.

Scrolling: Scrolling should ask the user to specify some number of pixels, and should then shift the image that many to the right. Pixels that would fall off the edge of the image should wrap around to the other size. Modular arithmetic may come in handy here.

Zoom: This method result in an image of the same size as the original, but consist of the center of the image blown up by a factor of 2. So if the image has width w and height h, zooming should expand the middle w/2 by h/2 region to fill the whole picture.

Blur: When you blur an image, you set the color of each pixel to be the average of the 9 pixels in the 3 x 3 square centered at that pixel (i.e. the average of the original pixel and its original 8 neighbors). You'll probably want to create a new Picture object; otherwise, you'll be adjusting pixel values that you'll need for subsequent calculations. Be careful at the borders, not all pixels have 8 neighbors!

Posterize: A typical pixel can have one of 256 value for each color channel. In a posterized image, this number is drastically decreased. Each color channel value should be rounded to the nearest multiple of 32.

Increase Contrast: When increasing the contrast, color values at 128 should be unchanged. For any other value x, the difference between x and 128 should be scaled a factor of 2. For example, a color value of 129 (1 above 128) would become 130 (2 above 128). 125 (3 below 128) would become 122 (6 below 128). Just remember that you'll need to stay between 0 and 255.

Implement a Design:

This program will be a good deal larger than those you've created on previous labs. Making a single large function that does everything will most likely end in tears. As such, think carefully about how to break your program into logical and managable pieces using functions. It is very important that you test your code incrementally as you build your program -- don't try to write the whole thing before you start testing. It is also critical that you use comments to explain what each method you create does.

Since we're creating an image manipulation program, you'll want to save picture2.py into your working directory. To create a new picture object, first add an import picture2 statement at the top of your program. Then you'll be able to use


  pic = picture2.Picture("crayons.bmp")
                      
where pic is just a variable name for the picture object (we used canvas last time, but you can use whatever name you want).

This causes a new picture object called pic to be created, but rather than starting as a blank image, pic is initialized to match the image in the file crayons.bmp. To create a new blank image (which may be useful if you need to create a copy of the current image), you'll use almost the same syntax we had for picture.py, minus one set of parens. In particular,


  pic = picture2.Picture(w,h)
                      
will create a new blank image with width w and height h.

Whichever image file you use should be saved in your working directory. You can use crayons.bmp or an image of your choice, although I suggest sticking with images which are in .bmp format. If you're looking for files on Google images, you can add filetype:bmp to your query to restrict hits to this file type.

Some Important Methods

The following methods will be useful in completing this lab. For starters, you often won't know the height and width of the image you read in. To find out, the methods getHeight() and getWidth() can be used. Both return an integer. Remember that to call these methods, you'll use the object name, followed by a period, followed by the method invocation. For example, w = pic.getWidth().

Keep in mind that if the width of the image is w, then the x-coordinates of all pixels range from 0 to w-1. Trying to access or modify a pixel with an x-coordinate of w or greater will cause an error. Similarly for the height.

Since we'll be doing pixel-by-pixel modifications, we need to be able to read and set the three color channels of any given pixel. The method getPixelRed(x, y) returns the red value of the pixel at location (x,y). The method setPixelRed(x, y, v) assigns the pixel at (x,y) a red value of v. This method does not return a value.

The methods getPixelGreen(x,y), setPixelGreen(x,y,v), getPixelBlue(x,y) and setPixelBlue(x,y,v) behave as you'd expect. Keep in mind that when setting any color value, you must use an integer in the range from 0 to 255 (inclusive).

It'll often simplify your code to use getPixelColor(x,y) (which returns three integers, one per color channel) and setPixelColor(x,y,r,g,b).

As we've seen, the method display() can be used to pop open a window with the current contents of your Picture object.

Note About Copying Picture Objects

For some of the operations listed above, it's helpful (if not necessary) to create a duplicate of a Picture Object.

If you have a picture object pic containing your original image, the assignment statement picCopy = pic will not suffice to create a duplicate image. All this will do is give you two variable names pointing to the exact same picture object, and any changes to either will be reflected in both. Instead, you'll need to create a new Picture object of the same size, loop through all pixels of pic and copy the corresponding color channels into same pixel in picCopy. You may want to create a copy(pic) function specifically for this task, that takes in a picture object, makes a new picture object of the same size with the same pixel values, and returns it. Only call display on your original picture object pic. Any function that makes use of a copy of your picture should create a new variable, say, pic2, using your copy function. Then use that copy to modify pic. Don't assign pic2 to pic, and don't display pic2.

Test the Program:

This is a big one, so hopefully you've been testing as you go along. Make sure each individual operation does what it is supposed to, and then make sure that combinations work as well.

Handin

If you followed the Honor Code in this assignment, insert a paragraph attesting to the fact in a README file.

I affirm that I have adhered to the Honor Code in this assignment.

You now just need to electronically handin all your files. As a reminder

 
  % cd             # changes to your home directory
  % cd cs150       # goes to your cs150 folder
  % handin         # starts the handin program
                  # class is 150
                  # assignment is 4
                  # file/directory is lab04
  % lshand         # should show that you've handed in something
                

You can also specify the options to handin from the command line

 
  % cd ~/cs150     # goes to your cs150 folder
  % handin -c 150 -a 4 lab04
                

File Checklist


You should have submitted the following files:
  README
  monte.py
  picture.py   (for ease of grading)
  primes.py
  imageEdit.py (unless you worked with a partner and your partner submitted)
  picture2.py  (for ease of grading)
  crayons.bmp	(for ease of grading)
                


A. Eck, T. Wexler, A. Sharp.