{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# CSE 160 Homework 3: Image Blurring\n", "\n", "Due: Wednesday, January 30, 2019. \n", "Submit your blur_image.py file via [Canvas](https://canvas.uw.edu/courses/1254701/assignments/4615078?module_item_id=9104907). (Then fill out the [survey](https://canvas.uw.edu/courses/1254701/quizzes/1104498?module_item_id=9128455) (will be posted in the Canvas Week 4 Module))\n", "\n", "## Learning Objectives:\n", "\n", "- gain experience writing functions and using lists in Python\n", "- practice using loops and conditionals (if statements) in Python\n", "- become familiar with reading and writing files in Python\n", "- write Python code to blur an image\n", "\n", "Advice from previous students about this assignment: \n", "[14wi](https://canvas.uw.edu/courses/1254701/files/53467333)\n", "[15sp](https://canvas.uw.edu/courses/1254701/files/53467332)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem 1: Obtain the files, add your name\n", "\n", "Obtain the files you need by downloading the [CSE160_hw3.zip file]().\n", "\n", "Unzip the homework 3 zip file to create a homework3 directory/folder. You can do your work here.
\n", "The homework3 directory/folder contains:\n", "\n", "- *blur_image.py*, a partial Python program that you will complete\n", "- images, a directory which contains some sample black and white images for you to process.\n", "- test_grids, a directory which contains some very small sample black and white images stored as text files in CSV (comma-separated values) format.\n", "- *color_to_gray.py*, a Python program that will convert color images into black and white images.(This program is optional for you to use in converting your own images into black and white images and is described further in Problem 7.)\n", "\n", "You will do your work by __modifying__ *blur_image.py* and then submitting the modified version.
\n", "__Add your name to the top of this file.__
\n", "All the code you will write for this assignment will be in the modifications you make to this file." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem 2: Run the program\n", "\n", "Once you have downloaded your homework 3 files, you should make sure that you can access the files.
\n", "Try running the *blur_image.py* file on one of the test images.
\n", "Most likely, if you dowloaded the .zip file, the images are stored in an inner directory.
\n", "Use a command like the one shown below (on Mac):\n", "\n", "```\n", "!python ./homework3/blur_image.py ./homework3/images/Husky.png\n", "``` \n", "On Windows:\n", "```\n", "!python .\\homework3\\blur_image.py .\\homework3\\images\\Husky.png \n", "```\n", "\n", "You should get output that looks similar to the following (but possibly with a different path, depending on how you are storing all your files):\n", "\n", "```\n", "Welcome to the CSE 160 Image Blurring program!\n", "Reading image ./homework3/images/Husky.png\n", "Program done\n", "```\n", "
\n", "(Since you haven't written any code yet, the program currently doesn't do much.)\n", "\n", "**Note:** If you get a \"can't open file 'Husky.png'\" error or a \"No such file or directory\" error, then perhaps you are not in your homework3 directory, or you mistyped the file name.\n", "
\n", "Once you have completed the assignment (so NOT yet!), running the program as you just did should create two new files in your current directory:\n", "\n", "- *Husky_blur.png*, a blurred version of the original Husky.png\n", "- *Husky_blur_grid.txt*, a comma-separated values (CSV) file containing the integer contents of the blurred grid. This file will be useful for debugging your program. We have intentionally given it the file extension .txt (instead of the more common .csv) so that you can open it easily in a text editor .\n", "
\n", "The .png file can be opened in any image browser and should appear slightly blurred. (You can feed files to your program multiple times to blur them more.) The .txt file can be examined to determine if you are performing blurring correctly.\n", "\n", "That sounds great! Read on to learn how you can make all of this happen...
\n", "**There is no code to write for Problem 2.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Modify this command, if necessary to match your system and/or where the files are in your file system.\n", "# The path, as written, represents a relative path starting with the directory you are \n", "# currently working from. You can use pwd (Mac) or cwd (Windows) to determine your current directory.\n", "!python ./homework3/blur_image.py ./homework3/images/Husky.png" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem 3: The Big Picture\n", "\n", "Problem 3 asks you to take a look at several aspects of the program as a whole before you start writing any code. **There is no code to write for this problem**. You will probably want to read through this problem multiple times before proceeding to the next problem, since some of its descriptions rely on terms defined towards the end of the problem. A Word version of Problem 3 is provided [here](), in case you want to print out a copy for later reference.\n", "\n", "Take a look at *blur_image.py* focusing on the functions in the file (these all start with def function_name) and on the main program instructions listed at the end of the file. You might print [this file]() out so you can make notes on it and so you can easily see all parts of the program at once. You will notice that this file is about 200 lines long. Don't worry - you do not need to understand all of the code in this file! We will walk you through the modifications you need to make in Problems 4-7 below. In total you will implement **four short function bodies** (6-10 lines each) and add in some calls to those functions. For now let's just take a look at what we have given you." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### The Main Program\n", "\n", "Let's first take a look at the main program (look for the comment: \"The main program begins here\"). Read the comments in the code that describe what each piece of the given code does. The steps in the main program (see the comments: \"Step A\", \"Step B\", etc.) are roughly:\n", "

\n", "**A.** Process command line arguments - tell the user if they have not given the correct number of arguments to the program, otherwise get the name of the input_file from the user arguments.
\n", "**B.** Determine what type the input_file is based on its file extension (.txt, .png), and read the file contents into a grid of pixels stored as a list of lists of integers.
\n", "**C.** Generate the output file names - based on the name of the input_file, create the strings that will be used as the names for the output files. The program will write the blurred image to a file in two formats: as a .png image you can open in any image viewer and as a text file containing a comma separated list of the integer values in the grid, one row of the grid per line of the file. This text file will be useful for debugging your program.\n", "

\n", "Later, you will need to add code for the remaining two steps in the main program:\n", "

\n", "**D.** Apply the blur algorithm to the grid of pixels you read into the variable input_grid in step B.
\n", "**E.** Write the blurred grid of pixels to the two output file names you created in step C.\n", "

\n", "In order to complete steps D and E above you will need to call some of the functions in this file. Below is a brief guide to the functions in the file." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### The Functions\n", "\n", "Some of these functions are **given** to you and you do not need to modify them at all, some you need to **implement**.\n", "

\n", "_Functions you are given to read and write files:_

\n", "You do **NOT** need to modify these three functions.
\n", "You do NOT need to know _\"how\"_ these functions work, you just need to understand _\"what\"_ they do.\n", "
\n", "- **read_image(file_path)** - Reads the image file at file_path into a rectangular grid of pixels, represented as a list of lists of integers. Each element of the outer list is one row of pixels, where each pixel is an integer x such that 0 <= x < 256. Returns the grid of pixels.\n", "- **write_image(file_name, pixel_grid)** - Given pixel_grid as an image in a list of lists of integers format, write it to the filename file_name as an image.\n", "- **write_grid(file_name, pixel_grid)** - Given pixel_grid as an image in a list of lists of integers format, write it to the filename file_name in CSV format.\n", "
\n", "\n", "_Functions that you need to implement:_
\n", "- **get_pixel_at(pixel_grid, i, j)** - Returns the pixel in pixel_grid at row i and column j (zero-indexed). Returns 0 if there is no row i or no column j, or if i or j are negative.\n", "- **average_of_surrounding(pixel_grid, i, j)** - Returns the (unweighted) average of the values of the pixel at row i and column j and the eight pixels surrounding it in the given pixel_grid.\n", "- **blur(pixel_grid)** - Given pixel_grid (a rectangular grid of pixels), returns a new grid of pixels of the same size and shape, that is the result of blurring pixel_grid. In the output grid, each pixel is the (unweighted) average of that pixel and its eight neighbors in the input grid.\n", "- **read_grid(file_path)** - Reads the CSV file at file_path into a rectangular grid of pixels, **represented as a list of lists of integers**.
\n", "\n", "You may want to use the following functions in your read_grid implementation (See [here](https://docs.python.org/3/library/stdtypes.html) for more details):\n", "- split()\n", "- strip()\n", "- str()\n", "- int()\n", "\n", "This method should read any file written by the write_grid function. Returns the grid of pixels.\n", "
\n", "\n", "_Other functions:_

\n", "You will also notice a few more functions in the file. We will describe these in more detail later in Problems 4-7. You do NOT need to modify these three functions. You do NOT need to know *\"how\"* these functions work, you just need to understand *\"what\"* they do.\n", "\n", "- **csv_line_to_list(line)** - Given a CSV-formatted row of integers, returns the integers as a list of integers. You will need to call this function in your implementation of read_grid.\n", "- **test_get_pixel_at()** - Basic, brief sanity checks for get_pixel_at.\n", "- **test_average_of_surrounding()** - Basic, brief sanity checks for average_of_surrounding." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### The Data Structure\n", "
\n", "Note that several places above it refers to a \"grid of pixels\". This is the data structure we will use inside of our program to represent the image. As described in the Background section, the black and white images we are using can be thought of as a rectangular grid of pixels, where each pixel is represented by an integer between 0 and 255. Inside of our program we will represent this grid as a list of lists of integers. The first (outer) list will be a list of rows of the grid. The length of this list is the \"height\" of the grid. Each row of the grid will be represented as a list of integers. Each row will be the same length. So the length of any one of these rows is the \"width\" of the grid. For example, the first image described in this writeup would be represented by the following list:\n", "\n", "```\n", "[ [1, 5, 61], [4, 3, 2], [10, 11, 100] ]\n", "```\n", "\n", "Here is another example pixel grid and the list that would represent it:\n", "\n", "```\n", " 1 2 3 4 5 \n", " 6 7 8 9 10 \n", "11 12 13 14 15\n", "\n", "[ [ 1, 2, 3, 4, 5 ], [ 6, 7, 8, 9, 10], [ 11, 12, 13, 14, 15] ]\n", "```\n", "\n", "Assuming this list was assigned to the variable grid, to access the pixel in the top left corner (containing the value 1), you would say: \n", "\n", "```\n", "grid[0][0]\n", "```\n", "\n", "\n", "To access the value in the bottom right corner (containing the value 15) you would say:\n", "\n", "```\n", "grid[2][4]\n", "``` \n", "\n", " \n", "*NOTE:* To access the 15 you could also say: \n", "\n", "```\n", "grid[len(grid)-1][len(grid[0])-1]\n", "```\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# EXAMPLE: The following command should result in the output: 8\n", "grid = [ [ 1, 2, 3, 4, 5 ], [ 6, 7, 8, 9, 10], [ 11, 12, 13, 14, 15] ]\n", "grid[1][2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### The File Formats\n", "
\n", "The program you are given will read in black and white image files in .png format. It will apply the blur algorithm to the given image and write the blurred image to two output files. One output file will be in .png format that can be opened in any image viewer. The other output file will be a text file (with file extension .txt) in CSV (Comma-Separated value) format. After you implement the read_grid function, your program will also be able to read input files in the same CSV format that the program produces. We have provided a few small sample files in this format in the test_grids folder. The purpose of supporting the reading and writing of files in this CSV format is to facilitate testing your program. While it might be hard to look at a blurred image in an image viewer and tell if you have done your calculations exactly right, this should be easy to do on small files in the CSV format. For example, the contents of the CSV file small_grid.txt and the CSV file containing the result of applying the blur algorithm to that grid are shown below. Note the commas between elements.\n", "\n", "```\n", "small_grid.txt:\n", " 0, 0, 0\n", " 0, 9, 0\n", " 0, 0, 0\n", "```\n", "\n", "```\n", "small_grid_blur_grid.txt:\n", "1, 1, 1\n", "1, 1, 1\n", "1, 1, 1\n", "```\n", "\n", "\n", "Feel free to create your own text files in this same CSV format for testing purposes.
\n", "**There is no code to write for Problem 3.**\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem 4: Implement the Blurring Algorithm\n", "\n", "**For this problem, you need to implement the three function bodies related to blurring an image:**\n", "- *get_pixel_at(pixel_grid, i, j)*\n", "- *average_of_surrounding(pixel_grid, i, j)*\n", "- *blur(pixel_grid)*\n", "\n", "We suggest you implement them in that order. You might go back to the description of the functions to refresh your memory. Below are some specifics about each of these functions and testing them. We are having you implement these particular functions because they demonstrate a nice decomposition of the overall problem of blurring an image. If you find that you are not calling all of these functions somewhere in your code you should go back and look for opportunities to do so. In particular, blur should call average_of_surrounding, and average_of_surounding should call get_pixel_at. Note that the smallest possible pixel_grid is one that contains a single pixel (e.g. [ [1] ]). You can assume that you will never be given a pixel_grid smaller than this.\n", "\n", "1) **get_pixel_at(pixel_grid, i, j)** should return the pixel in pixel_grid at row i and column j (zero-indexed) or 0 if there is no row i or no column j. We will not allow negative numbers to be valid indexes into our grid. This function can be done in about 4-6 lines of code, although it is fine if yours is slightly longer.\n", "\n", "The function *test_get_pixel_at()* can be used to do some basic sanity checks to test that you have implemented get_pixel_at correctly. *test_get_pixel_at()* makes use of the assert statement (described on slide 32 in the lecture slides about functions).\n", "\n", "*test_get_pixel_at()* creates a test pixel grid, and then executes a sequence of assert statements. assert statements assert things that should be true at that point in the program, such as *get_pixel_at(test_grid, 0, 0)* == 1. If any of the assertions in *test_get_pixel_at()* fail then an error message will be printed. If you see such a message this means that you have not implemented get_pixel_at correctly. Look at the test_grid in test_get_pixel_at and the message printed and then see if you can figure out what is wrong with your implementation of get_pixel_at.\n", "\n", "Notice that there is already a commented out call to *test_get_pixel_at()* immediately after its definition. Un-comment the call to *test_get_pixel_at()* now. The way our program is wrtten, as soon as get_pixel_at and test_get_pixel_at have been defined, the function *test_get_pixel_at()* will be called.\n", "\n", "2) **average_of_surrounding(pixel_grid, i, j)** should return the average of the values of: the pixel at row i and column j and the eight pixels surrounding it in the given pixel_grid. This is the algorithm described in the Background section. *average_of_surrounding(pixel_grid, i, j)* will calculate this average for a single pixel. Notice how in the code we have given you we expect you to sum the nine pixels into the variable pixel_sum and then divide that sum by 9 using truncating integer division. You should not change this part of the code; this is how we intend for the average to be calculated. You will probably need to add anywhere from 6-10 lines of code to this function. It can be done using loops by adding in about four lines of code but that is not required. It will be fine if you solution adds closer to 10 lines of code.\n", "\n", "The function test_average_of_surrounding is similar to *test_get_pixel_at()*. It can be used to do some basic checks to test that you have implemented average_of_surrounding correctly. Un-comment the call to *test_average_of_surrounding()* now.\n", "\n", "3) **blur(pixel_grid)** - Given pixel_grid, a rectangular grid of pixels, blur should return a new grid of pixels of the same size and shape, that is the result of blurring pixel_grid. You will read the origial pixel_grid and write your results into a new grid. For each location in the given pixel_grid, compute its average based on values in pixel_grid and then store that average in the same location in the new grid you are creating. When you are done with this you should return the new grid. You will probably need to add anywhere from 8-15 lines of code to this function. We strongly recommend using the approach of starting with an empty list and appending things onto lists to create your new grid, as other approaches (e.g. copying grids) are likely to result in hard to track down bugs. See this [PythonTutor example](https://goo.gl/r4MABT) (also provided in more depth in the code cell following this description).\n", "\n", "Here is a sample pixel_grid and the new blurred grid that should be returned.\n", "pixel_grid:\n", "\n", "```\n", "[[1, 2, 3], [4, 5, 6], [7, 8, 9]]\n", "```\n", "\n", "new blurred grid:\n", "\n", "```\n", "[[1, 2, 1], [3, 5, 3], [2, 4, 3]]\n", "```\n", "\n", "There is no test function provided for blur. Although you may write one if you wish, using the two test functions above as models. Writing a test function for blur is not required but we encourage you to get in the practice of writing test functions. You can find other examples of blurred grids elsewhere in this writeup. You can examine the output of your blurring algorithm visually after you have finished Problem 5 below. You won't know whether you have blurred the image exactly according to the algorithm until after you have finished Problem 6 and can compare the output of small blurred test_grids.\n", "\n", "After you have implemented blur and are convinced that get_pixel_at and average_of_surrounding are working properly, move on to Problem 5.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pixel_grid = [[1, 2], [3, 4], [5, 6]]\n", "new_grid = pixel_grid # Does NOT make a copy of pixel_grid\n", "new_grid[2][1] = 99 # Modifies pixel_grid AND new_grid\n", "print(\"Modified new_grid.\")\n", "print(\"new_grid:\",new_grid)\n", "print(\"pixel_grid:\",pixel_grid)\n", "print()\n", "\n", "simple_list = [1, 2, 3]\n", "new_simple_list = simple_list[:] # Makes a copy of simple_list\n", "newer_simple_list = list(simple_list) # Also makes a \"copy of simple_list\n", "new_simple_list[1] = 5\n", "print(\"Modified simple_list.\")\n", "print(\"simple_list:\",simple_list)\n", "print(\"new_simple_list:\",new_simple_list)\n", "print()\n", "\n", "print(\"starting with:\")\n", "newer_grid = pixel_grid[:] # Makes a copy of the \"outer\" list but NOT \"inner\" lists\n", "print(\"pixel_grid\", pixel_grid)\n", "print(\"newer_grid\", newer_grid)\n", "newer_grid[0][1] = 88 # Modifies pixel_grid AND newer_grid\n", "print(\"Modified newer_grid.\")\n", "print(\"pixel_grid\", pixel_grid)\n", "print(\"newer_grid\", newer_grid)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Modify/test get_pixel_at(pixel_grid, i, j) code here:\n", "\n", "def get_pixel_at(pixel_grid, i, j):\n", " '''\n", " Returns the pixel in pixel_grid at row i and column j (zero-indexed).\n", " Returns 0 if i or j is out of bounds for the given pixel_grid.\n", " Returns 0 if i or j is a negative value.\n", " '''\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Modify/test average_of_surrounding(pixel_grid, i, j) coode here:\n", "\n", "def average_of_surrounding(pixel_grid, i, j):\n", " '''\n", " Returns the unweighted average of the values of the pixel at row i \n", " and column j and the eight pixels surrounding it.\n", " '''\n", "\n", " # pixel_sum should be an integer. We intend for this to be \n", " # truncating integer division. \n", " return int(pixel_sum / 9)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Modify/test blur(pixel_grid) code here:\n", "\n", "def blur(pixel_grid):\n", " '''\n", " Given pixel_grid (a grid of pixels), returns a new grid of pixels\n", " that is the result of blurring pixel_grid. In the output grid, \n", " each pixel is the average of that pixel and its eight neighbors in\n", " the input grid. \n", " '''\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem 5: Write the Main Program Code\n", "\n", "For this problem you must **replace the two comments** in the main program with code that will implement step D (apply the blur algorithm) and step E (write the results to two output files). You might go back to the discussion of the main program to take a closer look at that we are asking. You will want to call several of the functions described above. This should consist of a small number of lines of code (3-5 lines total)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_**Nice work, you have finished the main program!**_
\n", "You should be able to read in .png images, blur them, and write the result to a .png and a .txt file. Try it out!!!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Test your blur_image.py program here\n", "\n", "!python ./homework3/blur_image.py ./homework3/images/Husky.png" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem 6: Implement Reading CSV Grids\n", "\n", "Finally, for this problem you need to **implement read_grid(file_path)** to allow your program to read in files in our CSV format. Note that you should **NOT** use any methods from Python's CSV module to implement this function: everything you need to know can be found in the lecture slides on File I/O.\n", "
\n", "\n", "Your code for *read_grid* should first open the file, and then read the file one line at a time. You should use the provided function *csv_line_to_list(line)* to convert each line of input into a list of integers. You will want to create a list of these lists that matches the \"grid of pixels\" format. Finally, you should **close the file and return the grid of pixels**.\n", "
\n", "\n", "If you go back and look at the main program you will notice that we are already calling *read_grid* where we need to. So you do not need to add in any calls to *read_grid*. Now you should be able to call *blur_image.py* giving it *either* a .png image or a file with the .txt extension in our CSV format." ] }, { "cell_type": "code", "execution_count": 251, "metadata": {}, "outputs": [], "source": [ "# You might want to test your function here, before adding it to the blur_image.py file\n", "# The csv_line_to_list function is provided below for reference (and to run prior to testing)\n", "def read_grid(file_path):\n", " ''' \n", " Reads the CSV file at file_path into rectangular grid of pixels,\n", " represented as a list of lists of integers. This method should\n", " read any file written by the write_grid function. Returns the\n", " grid of pixels. \n", " '''\n", " \n" ] }, { "cell_type": "code", "execution_count": 243, "metadata": {}, "outputs": [], "source": [ "def csv_line_to_list(line):\n", " ''' \n", " Given a CSV-formatted row of integers, returns the integers\n", " as a list. The argument line must be a string such as\n", "\n", " \"255, 0, 27\"\n", "\n", " Note that\n", " * There are no extra spaces at the beginning or end of \n", " the string, and \n", " * Commas are only between elements (no comma before the \n", " first element, and no comma after the last one).\n", "\n", " This method will present an error if either of these \n", " constraints are violated.\n", " '''\n", " # YOU DO NOT NEED TO MODIFY THIS FUNCTION.\n", " # Do not worry about understanding \"How\" this function works.\n", " # Although it is not too tricky if you look up the string split\n", " # function.\n", " row = []\n", " for pixel in line.split(','):\n", " row.append(int(pixel))\n", " return row" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Test your blur_image.py program here using a .txt \n", "# Note: We are using the blurred .txt file created by running blur_image earlier.\n", "\n", "!python ./homework3/blur_image.py ./Husky_blur_grid.txt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submit your work\n", "\n", "**_You are almost done!_**\n", "\n", "At the bottom of your *blur_image.txt* file, in the **“Collaboration”** part, state which students or other people (besides the course staff) helped you with the assignment, or that no one did.\n", "\n", "Submit the following file via Canvas.\n", "\n", "- *blur_image.py*\n", "\n", "Answer a [survey](https://canvas.uw.edu/courses/1254701/quizzes/1104498?module_item_id=9128455) asking how much time you spent and other reflections on this assignment. You will find the survey in the Week 4 module close to the HW 3 due date.\n", "\n", "**_Now you are done!_**" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.1" } }, "nbformat": 4, "nbformat_minor": 2 }