Lab Exercise 4: Images

The main purpose of this lab is to give you the tools you need to complete an image-manipulation project.

Zelle Graphics Module reference

You will be using John Zelle's graphics package. Here is the reference guide.

Tasks

1. Getting started with PPM Images

  1. In your personal file space, make a folder called Project4. Then download the following files to it.
  1. Open a text editor (e.g Visual Studio Code). Create a new file called show.py. Put a comment at the top of the file with your name and the date. Save it in your Project4 folder.

  2. After the comment line, write the command to tell Python to import the graphics package and the display package.

  3. Download one of the following PPM images to use in the lab exercises. You should right-click on the Image and do "Save Link As..."

Miller Lovejoy
Mudd Geraniums
Lake Winter
FallFoliage Flowers

2. Read an image from a file and show it in a window

We're going to create a simple program that will read image data from a file and display it in a window.

Create a main function in your show.py file. The main function should do the following.

  1. Create a variable assigned to the name of PPM image that you downloaded and intend to display.
  2. Load the image data from the filename specified by the string. You can do this by calling the function graphics.Image() with a Zelle Point object as the first argument and the filename as the second argument. You want to assign the return value of the graphics.Image function to a variable.

    Note: Any time we need to store, retain, or assign information, we need to use an assignment statement. In this case, we want to store the Image object created by the graphics.Image() function call. Therefore, you need a variable on the left side of an assignment and the graphics.Image()function call on the right.
    Example: img = graphics.Image(graphics.Point(0, 0), filename)

  3. Use the displayImage function in the display module to create a window and display the Image. It takes two arguments: the Image to display and the title string to be displayed at the top of the window (anything you want). The displayImage function returns a reference to a window object, which you need to assign to a variable (e.g. win; see Note above).
  4. Finally, call the getMouse method on the window object (e.g. win). That means you type the name of the variable holding the window reference, followed by .getMouse() to call the method. This will wait for a mouse click in the window and then go on to the next instruction. If there is no next instruction, the program will terminate.

Below the main function, put a conditional call to it, using the method we learned last week.

if __name__ == "__main__":
    main()

Once complete, try out your show function. On the terminal, make sure you are in the correct working directory and then run your program with an image filename as a command line argument. Here's an example:

python3 show.py

3. Command line arguments

The goal of Task 3 and 4 is to allow the user to show different images without having to change any code (i.e. the filename you hard-coded as a string).

We'll use the command line to specify the filename of the image to view. For example, to show geraniums.ppm, you would run the following in the Terminal:

python3 show.py geraniums.ppm

To show the winter scene, you would simply enter the following in the Terminal:

python3 show.py Winter.ppm

Let's see how we can access data from the command line within our Python program.

  1. Create a new file called lab4.py
  2. Import the module sys.
  3. Add the following line of code to lab4.py.
print(sys.argv)

Save your file, cd to your working directory, and then run your lab4.py file. What do you see?

  1. Now type some additional things on the command line after python3 lab4.py. For example, try:
python3 lab4.py hello world 1 2 3
  1. Add the following three lines below the line print(sys.argv):
print(sys.argv[0])
print(sys.argv[3] * 3)
print(int(sys.argv[3]) * 3)

Then run the program using the following command.

python3 lab4.py three times 3

What is going on?

4. Choosing the image with command line arguments

Let's integrate command line arguments into show.py.

  1. Import sys.

  2. Modify main to take in a parameter called args.

  3. Modify your call to main in if __name__ == "__main__": to take in sys.argv as the argument.

  4. At the beginning of main: Test if there are at least two strings in args: the name of the python program and the filename of the image to open. If there are not at least two strings, print a usage message and exit using the exit() function. Use the len() function to test how many strings there are in the list.

  5. Replace the hard-coded image filename string from Task 2 with the appropriate string from the args list.

  6. Test your updated show.py with command line input. Assuming you downloaded geraniums.ppm and Winter.ppm into your working directory, the following Terminal commands should show the appropriate images:

python3 show.py geraniums.ppm
python3 show.py Winter.ppm

5. Manipulating pixels in an image

Now we're going to figure out how to manipulate pixels in an image. Each pixel in a color image has three values (r, g, b). Each pixel is addressed by its row and column. There are rows x columns pixels in an image. The Zelle graphics library (graphics.py) contains two functions that make it easy to get and set pixels if you have an Image object. The three lines below demonstrate how you would read in a Image from a file (which we did above) and then swap the red and blue channels of pixel 42 (x), 35 (y).

img = graphics.Image(graphics.Point(0,0), 'myImage.ppm')
(r, g, b) = img.getPixel(42, 35)
img.setPixel(42, 35, graphics.color_rgb(b, g, r))
  1. Create a new file called filter.py. Do the usual stuff of putting your name and a date at the top. Then import graphics and sys. Save it in your Project4 directory.

  2. Create a function named something like swapRedBlue that takes in one argument, which will be a Image object. The algorithm is written below as comments, properly indented. Read through them and make sure you understand the process. Then fill in the Python code. After the Python code has been written, please remove these comments. They do not make the code any more readable, as they are just the English version of the code.

    def swapRedBlue(src):
        # loop over each row row_idx
            # loop over each column col_idx
                # get the r, g, b values of the pixel indexed by (col_idx, row_idx)
                # set the pixel indexed by (col_idx, row_idx)  to the value (b, g, r)
    
  3. Create a new test function in filter.py (this is a different function than swapRedBlue) that takes in an argument which is the list of strings from the command line. You can use show.py as an example for your test function. As in that example, first test if there are enough arguments and print out a usage statement and exit if there are not. If there are enough arguments, then open the image and store the result in a variable. Pass the Image to the swapRedBlue function, then save the result to a file. If your Image variable were img, then you could save it to a file in your folder by using the following code. You can pass whatever filename name you want to the save function. We recommend not having spaces in the filename.

    img.save('myfilename.ppm')
    
  4. At the end of your filter.py file, put a call to your test function behind a conditional that tests the value of the name variable, as we did last week.

    if __name__ == "__main__":
        test(sys.argv)
    

    Note that this is the only place in your filter.py file where you should use the sys.argv variable. if you are using it in any of your functions, then you are not making proper use of the function parameters.

When you are done, run your filter.py program, giving it an image filename as the argument. Then look at the result with your show program.

Do you see why command line parameters are useful?

When you are done with the lab exercises, you may begin the project.

Appendix: ImageMagick

This section will help those interested in using their own images in this project, other than the ones provided above.

Converting JPG to PPM

The Zelle graphics library can open only PPM format images, not JPGs. JPG and PNG images are the formats for you will need to use for your reports or for web pages. You can change a JPG to a PPM using a program called ImageMagick. You can invoke ImageMagick to do the conversion via the convert command in the Terminal:

convert myImage.jpg myImage.ppm

The convert program is a very powerful application for manipulating images. If you want to find out more about it, check out the ImageMagick web site.

Installing ImageMagick (macOS)

ImageMagick is already installed on the lab machines so that you can adopt the above command to convert any of your own images. On your own computer, if you followed the instructions on the CS151 website to install Python 3 using the homebrew package manager, you can install ImageMagick by running the following Terminal command:

brew install imagemagick

Shrinking an image

You may want to make the images smaller for testing (i.e. your images take too long to process). The following ImageMagick command shrinks an image to 25% the original size.

convert myImage.jpg -scale 25% mySmallImage.jpg

Note that you can change the format and change the scale at the same time:

convert myImage.jpg -scale 25% myImage.ppm