# Image Manipulation 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 `ImageManipulation` in the `CSCI051p-Workspace` folder that 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/ImageManipulation` 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. Let's use the `small.ppm` in the `files` folder as an illustration example. - 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 (e.g., the r, g, b values) after the header, each separated by whitespace. As a result, the image looks as follows: <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: ``` grey = 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 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: scale(image, row_scale, col_scale) This function takes an image body (a list of lists of `int`s) and two scaling factors, both `int`s. The output is a list of list of `int`s. Whatever `row_scale` is, the output should take every `row_scale`th item in `image`. So the list returned by `scale(image, 3, 1)` on the test file (small.ppm) should be: ``` [[255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255], [0, 255, 255, 255, 0, 255, 0, 0, 0, 255, 0, 0]] ``` Similarly, the output should take every `col_scale`th pixel in `image` (keeping in mind that each pixel is represented by 3 numbers - the r, g, and b values). For example, the list returned by `scale(image, 3, 2)` on the test file (small.ppm) should be: ``` [[255, 0, 0, 0, 0, 255], [0, 255, 255, 0, 0, 0]] ``` Hint: the rows that you want are those where the index % `row_scale` equals 0. We can use this function to scale ppm images. Scaling refers to reducing the width and/or height of an image. For example, if we wanted to scale the height by 2, then we would take every other row. If we wanted to scale the width by 2, then we would take every other pixel in a given row. To support this scaling functionality, you will also need to implement reading ppm file and writing ppm file, which should be done in the `main` function in the following section. When you believe your function is correct, add test cases to the test file `ppm_process_tester.py` to thoroughly test your function. 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 filename. - Ask the user for an output filename. - 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!). - If a user enters choice number 3 to scale the image, you need to ask the user to enter two more input, which are the height scaling factor and the width scaling factor. You can assume these two factors entered by user will be positive numbers. ##### Sample Run ``` input file name: test.ppm output file name: test_out.ppm modifications are: 1. negate 2. greyscale 3. scale 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 3 enter the height scaling factor 3 enter the width scaling factor 1 done ``` #### 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: Ethical Reflection With the advent of photo-editing software and digital photography, photos can now be distorted in trvial and non-trivial ways. Ethical concerns arise when photos are manipulated in subtle ways that trick the public into believing that they are seeing something real. Examples include photos where a person's skin color is manipulated to be darker or lighter and photos showing two people, who have never met, meeting. Should we have a code of ethics regarding digital photo manipulations? Should the viewer be informed that the photo was manipulated to look better? What is the threshold beyond which this is necessary? ## Part 4: 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`, `grey_scale`, and `scale`. - `ethics.txt` your short ethical reflection - `feedback.txt` a text file containing your feedback for this assignment. These should be submitted using [Gradescope](https://www.gradescope.com). 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 | `scale` | 10 | | Execution | `main` interaction as specified | 4 | | Execution | `main` reads ppm files correctly | 3 | | Execution | `main` writes ppm files correctly | 3 | | | | | | Testing | Thoroughly tests `negate` | 2 | | Testing | Thoroughly tests `grey_scale` | 2 | | Testing | Thoroughly tests `scale` | 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 | | | | | | Ethics | Completed ethical reflection file | 2 | | Feedback | Completed feedback file submitted | 2 |