# 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.
| 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.
## 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):
**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`.
#### 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!
## 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?
## 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 |