# PPM Processing For this assignment you will continue working with ppm image files (Note that you may want to review the description of these files from the previous assignment). The previous assignment required only transformations that could be done on a line by line pixel basis because we did not have a natural way to refer to previous lines. But now that we have nested lists (i.e., lists of lists), we can build up a data structure to hold the entire image. This will allow us to more naturally handle image manipulations that require working with multiple lines. | Part | Section | |---------------|-----------------------------------------------| | 1 (in-lab) | [Part 1: Processing Images](#part1) | | 1 (in-lab) | [Check-in](#checkin) | | 2 (lab/home) | [Part 2: Scaling Images](#part2) | | 2 (lab/home) | [Submission Instructions](#submission) | ## Getting Started Create a new project named `PPMProcess` 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](ppm2.zip). You should see a folder named `starter` that contains two files (`ppm_process.py` and `ppm_process_tester.py`) and one subfolder (`files`). Copy the two python files and the folder into the (recently created) `CSCI051p-Workspace/PPMProcess` 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_process.py`, `ppm_process_tester.py`, and `files`) should now be visible. <a name="part1"></a> ## Part 1: Processing Images For the first part of this assignment you will write a function called process(lines, rows, cols) that will store the body of an image as a nested list. This function takes 3 parameters. The first parameter `lines` is a list of strings representing the body of an image. The second parameter `rows` is an `int` representing the number of rows. The third parameter `cols` is an `int` representing the number of columns. The `process` function returns a list of lists. The length of this list should be `rows`. Each element of this list should be a list of length `3*cols` representing the r, g, b values for all the pixels in a given row in the image. As an example, consider the test file `small.ppm` from the last assignment. Ignoring the header, the file contained the numbers: ``` 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 ``` If we opened the file for reading as `file_in`, we could get all those lines by executing the single statement `lines = file_in.readlines()` after reading in the three header lines. In this case, the return value of `process(lines, 6, 4)` would be: ``` [[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]] ``` Note that here, each sub-list represents one row of pixels in the image because the image has 6 rows and 4 columns. Now, if instead we executed `process(lines, 4, 6)`, the return value would be: ``` [[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]] ``` __Reread this last part and make sure you understand how `process` should work!__ #### Hints and Suggestions - Make sure that you understand what process should do before you implement it. - Make sure you understand what the parameters to `process` are. That is, given that the following works, what exactly is the type of that first parameter? ``` file_in = open("files/small.ppm", "r") file_in.readline() file_in.readline() file_in.readline() process(file_in.readlines(), 4, 6) ``` - Think about the basic structure you'll want your `process` function to have. For example: what loops will you need? What conditionals will you need? What variables will you need? What are some of the cases you will encounter (e.g. what if a row of pixels is broken up over multiple lines in the input file?) - Sketch out pseudocode. Pseudocode is not meant to be complete code, rather it is supposed to capture the overall structure. It can be almost all comments. It can have for loops and if-statements. But it should be clear how you will translate the pseudocode to actual code. In other words, you should be able to semi-confidently type a first draft of the `process` function into pycharm based on your pseudocode. #### Testing Process When you believe your process function is correct, add test cases to `ppm_process_tester.py` to throughly test your function. <a name="checkin"></a> #### Checking In Before finding a TA or professor, make sure your process function has - appropriate docstrings - good algorithm comments - mnemonic variable names - good use of horizontal and vertical white space We will double check your code, ask you a few questions about it, answer any questions you have, and award your points for Part 1. This must be completed before leaving the lab. After that you should start working on Part 2. <a name="part2"></a> ## Part 2: Scaling Images For Part 2, you will implement a program that scales 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 functionality, you will implement four functions: `read_ppm`, `write_ppm`, `scale`, and `main`. #### Function 1: read_ppm(filename) This function takes a string as input, which is the name of a file in ppm format. The function opens the file for reading, reads it in, calls `process` with the correct parameters (note that the number of rows and columns will be contained in the header of the file), and returns the list of lists returned by `process`. #### Function 2: write_ppm(image, filename) This function takes as inputs a list of lists of interger numbers and a string (filename). Every `int` will have a value between 0 and 255 (inclusive); every sublist will have the same length. The function interprets this list of lists as an image and writes out a valid ppm file to an output file with name `filename`. Note that the function will need to first write out header information before writing the ints in the lists. #### Function 3: scale(image, row_scale, col_scale) This function takes an image (a list of lists of `int`s, as returned by the `process` function) 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. When you believe your function is correct, add test cases to the test file `ppm_process_tester.py` to thoroughly test your function. #### Function 4: main() The main function should ask the user for an input file name, an output filename, a height scaling factor, and a width scaling factor. It should enforce the restriction that both scaling factors must be positive integers. It should then read from the input file and create a new file with the specified name that contains a copy of the input file scaled down by the specified factors. #### General suggestions - Incremental development and testing will be helpful. The strategy we recommended (required) for the last assignment was to start by writing the function headers and docstrings for each function and then writing and testing 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! - You can reuse the test files that were provided for the previous assignment. This includes the 4-by-6 image from the Background section, which may be helpful for debugging. - If you open a `.ppm` file in pycharm it will show the image. If you open it in textedit you can see the r, g, b values. Alternatively, if you save it with a `.txt` extension then pycharm will open it as a text file. #### 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_process.py` a python file that contains the implementation of all the required functions as specified - `ppm_process_tester.py` a python file that tests `process` and `scale`. - `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 | `read_ppm` | 4 | | Execution | `write_ppm` | 4 | | Execution | `scale` | 8 | | Execution | `main` | 2 | | | | | | Testing | Thoroughly tests `scale` | 2 | | | | | | Style | Correct file submission | 1 | | 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 good style | 1 | | | | | | Feedback | Completed feedback file submitted | 2 |