# PPM Image Modifier The goal of this assignment is to give you practice working with lists by writing a program that manipulates image files in various ways. Note that this is **not** a pair-programming assignment; everyone will work on and turn in their own `ppm_modify.py` file. <!-- This assignment is based on the “PPM Image Modifier” assignment described <a href="https://www.engage-csedu.org/find-resources/ppm-image-modifier"> here</a>. Part 1 was loosely inspired by CS 50's I/O assignment ("Forensics"), described <a href="http://nifty.stanford.edu/2011/malan-bmp-puzzles/">here</a>. --> | Part | Section | |---------------|-----------------------------------------------| | 1 (in-lab) | [Part 1: Decoding a PPM file](#part1) | | 1 (in-lab) | [Check-in](#checkin) | | 2 (lab/home) | [Part 2: Modifying images](#part2) | | 2 (lab/home) | [Submission Instructions](#submission) | ## Getting Started Create a new project named `PPMModify` in the `CSCI051p-Workspace` you created on your Desktop. *Double check that you are creating the project in the right place, or you will likely have trouble finding your files later.* Then download the [starter code](ppm1.zip). You should see a folder named `starter` that contains two files (`ppm_modify.py` and `ppm_modify_tester.py`) and one subfolder (`files`). Copy the two python files and the folder into the (recently created) `CSCI051p-Workspace/PPMModify` folder. If you don't see all the new files, ask PyCharm to rescan that folder by clicking the triangle next to that folder (on the left-side list) to close and re-open it. The newly added stuff (`ppm_modify.py`, `ppm_modify_tester.py`, and `files`) should now be visible. <a name="part1"></a> ## Part 1: Decoding a PPM file Your task for Part 1 is to define the function `decode` in `ppm_modify.py` and then call that function on the file `files/part1.ppm` to decode an image. #### Background: PPM files The acroynym ppm stands for "Portable Pixel Map" and is a text format for storing images. It is incredibly inefficient: an image stored in ppm format will be much larger than the same image stored in, say, jpeg format. However, it is also relatively easy to read and write files in ppm format, which is why we're using it for this assignment. - The first three lines in a (basic) ppm file are called the _header_. They will look something like this: ``` P3 4 6 255 ``` The first line specifies the encoding. In this class, it will always be P3. The second line specifies the width and height of the image in pixels. In other words, this image will be 4 pixels wide and 6 pixels tall. Finally the third line specifies the maximum value for the red, green, and blue values. In this class, the value will always be 255. - After the header comes the _body_. If there are `r` rows and `c` columns, then the body will contain `3 * r * c` integers, each between 0 and 255 (inclusive). Each number will be separated by at least one whitespace character. The number of integers on each line will be a multiple of 3. For example, our file might contain the following: ``` 255 0 0 0 255 0 0 0 255 255 255 255 0 0 0 255 0 0 0 255 0 0 0 255 255 0 255 0 0 0 255 0 0 0 255 0 0 255 255 255 0 255 0 0 0 255 0 0 255 0 0 0 255 0 0 0 255 255 255 255 0 0 0 255 0 0 0 255 0 0 0 255 ``` The first 3 numbers give the r, g, b values for the pixel in the upper left hand corner of the picture. In this case the pixel in the upper left hand corner of the picture has a red value of 255, a green value of 0, and a blue value of 0 (i.e., that pixel is pure red). The next 3 numbers give the r, g, b values for the pixel to the right (pure green). And so on, pixel by pixel. The pixel in the lower right hand corner of the sample picture corresponds to the last three numbers, so it has a red value of 0, a green value of 0, and a blue value of 255 (i.e., it is pure blue). Note that the line breaks in the file don't necessarily correspond to the end of a row of pixels in the image; the only requirement is that the file must contain the correct total number of numbers after the header, each separated by whitespace. As a result, the image looks as follows (only much smaller): <img src="test.png" alt="test"> **Important: Make sure you understand the specification for the ppm format before continuing! If you aren't sure, ask.** #### Implementing decode The function `decode` should take two parameters (`in_filename` and `out_filename`, both strings). It will read from the file named `in_filename` and create a new file named `out_filename` that satisfies the following properties: 1. The file named `out_filename` will be a ppm file. 2. `out_filename` will have the same header as `in_filename` 3. The body of `out_filename` will be generated from the body of `in_filename` line-by-line using the following rules: * If a number modulo 3 is equal to 0, it will be replaced by the number 0. * If a number modulo 3 is equal to 1, it will be replaced by the number 153. * If a number modulo 3 is equal to 2, it will be replaced by the number 255. *Hint:* Remember that the modulo operator is `%` in Python. *Hint 2:* You might want to start by just copying over the exact contents of `in_filename` and then modify your code to do the decoding. #### Decoding part1.ppm In `main_part1`, run your function `decode` on the file `files/part1.ppm`. <a name="checkin"></a> #### Checking In Once this is working you can check in and get lab points, but you are strongly, strongly encouraged to keep working on the assignment. Make sure your part 1 code satisfies good style (including a docstring) before checking in! <a name="part2"></a> ## Part 2: PPM modify For Part 2, you will implement an image processing program. Your code must contain the following four functions: #### 1: negate(line) This function takes a single parameter `line` (of type `str`). The parameter line is guaranteed to contain a sequence of integers, each with value between 0 and 255 (inclusive), separated by whitespace. In addition, the number of integers will be a multiple of three. The function returns a string with the same number of values, but with every one negated. In other words, if the first value in `line` was 155, then the first value in the returned string should be 100 (since 100 = 255 - 155). As another example: ``` negate("1 2 3 200 100 150") ``` should return the string ``` "254 253 252 55 155 105" ``` When you believe your function is correct, add test cases to `ppm_modify_tester.py` to thoroughly test your implementation. #### 2: grey_scale(line) This function takes a single parameter `line` (of type `str`). The parameter `line` is guaranteed to contain a sequence of integers, each with value between 0 and 255 (inclusive), separated by whitespace. In addition, the number of integers will be a multiple of three. The function returns a string with the same number of values. However, each set of three r, g, b values is replaced by three grey values. The formula is: ``` gray = sqrt(r**2+g**2+b**2) ``` Recall that shades of grey are exactly those where the r, g, and b values are all equal, so you should set all three of those equal to the computed grey value. Also keep in mind that each value should be an integer and that you'll need to make sure that each value is no more than 255 (so anything greater than 255 should be set equal to 255). Finally, the `sqrt` function is contained in the math package so you will want to import that. When you believe your function is correct, add test cases to `ppm_modify_tester.py` to thoroughly test your implementation. #### 3: remove_color(line, color) This function takes two parameters. The first is a string, and is a single valid line of numbers, defined in the same way as for the previous two functions. The second parameter color is also a string but is required to be one of the words `red`, `blue`, or `green` (this means any error checking should happen before you call this function! This function may assume that the arguements it gets are valid). If, for example, the `color` is red, then every red component should be set to 0 in the output string while the green and blue components remain the same. Similarly, if the parameter passed is `blue`, then every blue component should be set to 0. As an example: ``` remove_color("1 2 3 200 100 150", "red") ``` should return the string ``` "0 2 3 0 100 150" ``` When you believe your function is correct, add test cases to `ppm_modify_tester.py` to thoroughly test your implementation. #### 4: main() To put this all together, your program's `main` function should: - Ask the user for an input file. - Ask the user for an output file. - List the possible image manipulation functions and ask the user to choose one of them. If they don't enter a valid choice, ask them again. - Perform the requested manipulation on the input file and write the result to the output file in ppm format (don't forget to write out the header information!). ##### Sample Run ``` input file name: test.ppm output file name: test_out.ppm modifications are: 1. negate 2. greyscale 3. remove red 4. remove blue 5. remove green enter the number of the desired modification 0 please enter a valid number enter the number of the desired modification 10 please enter a valid number enter the number of the desired modification 1 done ``` #### Going above and beyond Some suggestions if you want to do more: - Add extra manipulation functions. Possible options include a thumbnail generator, or a horizontal flip or vertical flip. (These are definitely not all equally difficult!) - Look up how to check if a file exists and add error checking to your code so that it tells the user if the input file is not valid. - In the ppm format, lines that start with a # are comment lines and can be ignored. Modify your code so that it ignores comment lines. Note that you should __not__ add new functionality to the 3 named functions above. Any extra functionality should be added as new functions with different names that do different things. Note: it is ok if your `main` function changes to allow the user to, for example, input options for new image manipulations. #### Details, Hints, and Suggestions - Incremental development and testing will be helpful. We strongly suggest that you start by writing docstrings for each function and the write and test them one at a time. - While you're debugging your code we suggest not worrying about having the user input the name of the input and output files every time you run your program; just have fixed strings in your code. This should save you some time! - There are some test files, including the 4-by-6 image from the Background section, available in the `files` subfolder in the usual location. All of these files satisfy the property that each row of the body contains a multiple of three numbers (that is, no pixels are split across lines). Remember that you will have to tell python where the files are located (e.g., to read from the file `small.ppm` in subfolder `files`, use the filename `files/small.ppm`) - If you open a .ppm file in pycharm it will show the image. If it doesn't display the image, there is probably something wrong with the contents of the file, and you should double check that. - One way to look at the test of a file is to open it in text processor such as TextEdit. Another way is to make a copy of the .ppm file in pycharm, and save the copy with an extension .txt instead of .ppm, and open the copy. - Make sure your parameter and return types match the specification! #### Coding Style Make sure that your program is properly commented: * You should have comments at the very beginning of the file stating your name, course, assignment number and the date. * Each function should have an appropriate docstring, describing: - the purpose of the function - the types and meanings of each parameter - the type and meaning of the return value(s) * Include other comments as necessary to make your code clear In addition, make sure that you have used good style. This includes: * Following naming conventions, e.g. all variables and functions should be lowercase. * Using good (mnemonic) variable names. * Proper use of whitespace, including indenting and use of blank lines to separate chunks of code that belong together. For more detailed descriptions, please review the [Python Coding Style Guidelines](../../python_style.html). ## Part 3: Feedback Create a file named `feedback.txt` that answers the usual questions: 1. How long did you spend on this assignment? 2. Any comments or feedback? Things you found interesting? Things you found challenging? Things you found boring? <a name="submission"></a> ## Submission For this lab you are required to submit two files: - `ppm_modify.py` a python file that contains the implementation of all the required functions. - `ppm_modify_tester.py` a python file that contains thorough test cases for the functions `negate`, `gray_scale`, and `remove_color`. - `feedback.txt` a text file containing your feedback for this assignment. These should be submitted using [submit.cs.pomona.edu](http://submit.cs.pomona.edu) as described in the general [submission instructions](../../submit.html). Note that we reserve the right to give you no more than half credit if your files are named incorrectly and/or your function headers do not match the specifications (including names, parameter order, etc). Please double check this before submitting! ## Grade Point Allocations | Part | Feature | Value | |-----------|-------------------------------------------|-----| | Lab | Check-in | 3 | | | | | | Execution | `negate` | 8 | | Execution | `grey_scale` | 8 | | Execution | `remove_color` | 8 | | Execution | `main` interaction as specified | 4 | | Execution | reads ppm files correctly | 3 | | Execution | writes ppm files correctly | 3 | | | | | | Testing | Thoroughly tests `negate` | 2 | | Testing | Thoroughly tests `negate` | 2 | | Testing | Thoroughly tests `negate` | 2 | | | | | | Style | Docstrings accurate, relevant, appropriate| 2 | | Style | Other comments accurate, relevant, appropriate | 2 | | Style | Good use of loops and conditionals. | 2 | | Style | Other good programming style (e.g., variables, whitespaces) | 2 | | Style | Misc | 2 | | | | | | Feedback | Completed feedback file submitted | 2 |