Intro to Programming in Python

Final

3 hours


A. Grading [Show]

A. Grading [Hide]

Template

I have provided a sample template that can be run without producing any error. This is worth 0 points. Additional milestones yield additional points.

Any code that (1) produces an error, or (2) fails to produces a graphic, is a worse solution than this template and is worth less than zero points

Do no submit code that does not run.

There are no tasks in this assignment for which working code is not present both within course materials (slides and the textbook) and within one of the ImageShop, Enigma, or Adventure projects.

Difficulty

This exam has been made of appropriate difficulty for students to achieve an honest assessment of their progress this term with access to all available resources. It is hard for me to forecast the appropriate difficulty so you should focus on doing your best and I will ensure you receive a fair grade.

Here is an example of a grading scale used for a similarly designed course at Cornell that I also used on an exam in my networks class last year. As you see, I may use a curve:

Grading Scale
Grade Percentile Percentage
A 4.0 70.00-100.00%
B+ 3.5 60.00-69.99%
B 3.0 45.00-59.99%
C+ 2.5 38.00-44.99%
C 2.0 30.00-37.99%
F N/A 0-29.99%
At the discretion of the instructors, the grading may be done on a curve.

B. Intro [Show]

B. Intro [Hide]

Intro

While you likely already followed a GitHub classroom link to see these instructions, The assignment will be released via a GitHub Classroom Link which will be written on the board. It will be of the form: https://classroom.github.com/a/ + ???

In the event of a Git Classroom error, you may fork the template repository, currently based at https://github.com/cd-public/ccscrm

This will be a project-style final exam, restricted to be completable in 3 hours.

There are 5 milestones corresponding to exam problems:

  • Prob0 is "Strings and Files"
  • Prob1 is "Interactive Graphics"
  • Prob2 is "Reading Python"
  • Prob3 is "Defining Classes"
  • Prob4 is "Fundamental Python"

After focusing this term on many of the famous historical computer scientists who programmed computers, this assignment focuses on the people and the land that make physical computing devices. It is centered on the plurinational state of Bolivia whose lithium mines and miners form a critical part of the global technology landscape. Specifically, on a flag used by the peoples that live within those mountains and work within thoe mines.

The people of Andes have been fighting to maintain self-determination (what people in the US often call "independence") in the face of large US-based tech companies for years, and we should be cognizant of their struggle and their story.

You can read more in imgs/README.md at your leisure.


C. Files [Show]

C. Files [Hide]

Files:

  • Update these files:
    • flag.py
  • You will also need data files:
    • rgbs.csv
    • imgs/
      • rojo.png
      • naranja.png
      • amarillo.png
      • blanco.png
      • verde.png
      • azul.png
      • violeta.png
  • Provided for ethical reasons:
    • imgs/README.md
  • Required libraries:
    • pillow

Resources on PGL (Portable Graphics Library):


0. get_colors (10 pts) [Show]

0. get_colors (10 pts) [Hide]

Implement a get_colors function

When you first download the template code and run "flag.py" it should create a file named "flag.png" which is a 700 pixel by 700 pixel white image. Each time you run "flag.py" this image will overwritten, with increasingly complex images generated throughout the final.

These are names of the colors included in "rgbs.csv":

  • rojo
  • naranja
  • amarillo
  • blanco
  • verde
  • azul
  • violeta

To determine the red-green-blue color value of a color, you will need to read it from the file rgbs.csv.

0a. read_rgbs

You are not required to write this optional helper function, but it is recommended.

Two important pieces of information are stored in rgbs.csv:

  1. The order of colors, and
  2. The red-green-blue (rgb) color values of colors.

Write a function read_rgbs that reads the file rgbs.csv for this information. My read_rgbs returned a list of color names and two dimensional array of colors as NumPy arrays of length NUM_COLORS that stored the rgb values of a color.


>>> colors, rgbs = read_rgbs()
>>> colors                         
['rojo', 'naranja', 'amarillo', 'blanco', 'verde', 'azul', 'violeta']
>>> rgbs                           
array([[219,  10,  19],
       [236, 120,   8],
       [252, 222,   2],
       [255, 255, 255],
       [  1, 138,  44],
       [  6,  69, 177],
       [219,  10,  19]], dtype=uint8)

0b. get_colors

Write a function get_colors which accept a three dimensional image array (height by width by colors) and return an array of the same shape. Expect this function to also accept a second argument, region, latter on, and the example code accepts but does not use this argument at this stage. You may either update the existing array or make a new one!

An example function is provided that returns a blank, all-white array. You may use this to get started, or write your own.

For this milestone, you do not need to do anything more than change the color of the image array in its entirey. Modify get_colors to produce a 700 by 700 "flag.png" in the "roja" color. It should look like this:



1. flag (20 pts) [Show]

1. flag (20 pts) [Hide]

Implement flag in flag.py

This is what the flag we hope to make looks like:



Specifically, this flag has its longest diagonal in white, which corresponds to the "Qullasuyu" flag. We will enable other flags in Prob2.

Loop over the pixels in the pixel array used within the flag function, and for each row-by-col location determine from which color to which each pixel should be set. This will be a simple arithmetic operation. For example, along the long diagonal, the "blanco" would be appropriate, so if the row index and column index are equal, you should set that location to the "blanco" array of length 3.

There are both technically and algorithimically correct ways to do this. Look at the patterns in rows, columns, and diagonals, and remember colors from a file came in a certain order. Before implementing a brute force solution, you may wish to read Prob2.

You are ready to move onto the next problem when you have made the flag.


2. Region (10 pts) [Show]

2. Region (10 pts) [Hide]

Implement the region argument to flag in flag.py

This flag represents Indigenous people across what is today termed the "Andes", and the flag with long "blanco" diagonal often specifically represents the "Qullasuyu" (Qulla region). There are other regions that have other colors on the long diagonal, but are otherwise identical. Here are the examples you must be able to make:

Region Color
Qullasuyu blanco
Antisuyu verde
Kuntisuyu amarillo
Chinchaysuyu rojo

Update the flag function to specify a region to the get_colors function and the get_colors function to get colors for the image array corresponding to any of these regions.

Your get_colors function should now accept two arguments: an image array and a string holding a region value.


def get_colors(img_arr, region):

You are ready to move on when your code may produce each of the following by calling flag() with different arguments:

flag('Qullasuyu') flag('Antisuyu') flag('Kuntisuyu') flag('Chinchaysuyu')

3. Pixel Array (30 pts) [Show]

3. Pixel Array (30 pts) [Hide]

Use pixel values from provided images rather than from rgbs.csv

In brief, the flag colors represent aspects of Plurinational Andean life and I have chosen public domain images represent these aspects as best as I can. I included these in the imgs folder with the starter code, named for the color to which the image corresponds.

For this milestone, instead of returning a literal color value from the, return a metaphorical color value from an image at the same row-by-col location.

  • Read these images from their files into arrays using np.array(im.open("imgs/rojo.png"))
    • For example, the "rojo" image is located at "imgs/rojo.png"
  • 3a. Crop the images to be squares, like the flag
  • 3b. Resize the images to exactly fill the IMAGE_SIZE.
  • Update get_colors to get a pixel from the image pixel array named for the color, rather than returning the same numerical color value, based on the row-by-col location in the image.

You are not required to do 3a and 3b separately, and will receive the full 30 pts if your Prob3 is correct. However, if you struggle on Prob3, you may receive partial credit for these methods.

3a. square (10 pts)

My square function accepted a single argument, an image array, and returned a new image array.

Each image in the imgs folder is not a square, and the flag is. For each pixel array, remove pixels from the left/right or top/bottom to make it square and centered.

Example verde.png shows a Bolivia lithium mine, "el símbolo de las riquezas naturales" (the symbol of the natural resources), that is 800 pixels wide and 448 pixels tall.

By changing the corresponding pixel array, it is possible to create a pixel array that corresponds to the following, square image which is centered within the existing image:

3b. resize (10 pts)

My resize function accepted a single argument, an image array, and returned a new image array. I assumed I was working with a square array after 3a.

Not all of the images will be the exact size as our IMAGE_SIZE, but it would be nice if they were. There are many ways to solve this problem, here I will describe one.

One way to resize images is via creating a pixel array of the appropriate size and looping over the entire array, selecting pixels from the original sized image that are in the same relative (e.g. percentage, proportional, fractional) distance across the image in height and width, but may have a different numerical row-by-col location as the numbers of rows/cols differs.

For example, the above image is 448 by 448 pixels, and there is a patch of teal in the center of the image, at around 200-250 by 200-250 pixels. In a 700 by 700 image, that teal patch should be around 300-350 pixels, or so.

3. Finishing Touches

Of note, you can test squaring and resizing separately prior to this step by simply saving images under various names at any point in your program and viewing them in the image viewer of your choice.

Once you have pixel arrays that correspond to images, update you get_colors function. Rather than set a row-by-col location to a fixed pixel value, reference an image.

We can think of this in three steps:

  1. Find a color in the colors list (of the same index as the "rgb" value)
  2. Find the image array that corresponds to that color.
  3. Find a pixel in that image.
Running flag('Qullasuyu') should produce this image:

Note the diagonal showing people in labcoats - these are engineers of UMSA, the leading public university in Bolivia, and represent "blanco" which stands for "el desarrollo de la ciencia y la tecnología" (scientific and technologically progress). This is because "blanco" occurs along the the longest diagonal, so along this diagonal we see the "blanco.png" image. You can probably also recognize the lithium mine where you would see the "verde" color, immediately below the lab coats.


4. ColorScale (30 pts) [Show]

4. ColorScale (30 pts) [Hide]

Implement a colorscale function

Our new image does not look so much like the flag we started with, so we will use the same idea as making grayscale images to take images of many colors make them look like one color - but in this case, flag colors rather than simply gray.

  • Implement colorscale (10pts)
    • For every pixel in a pixel array, calculate the luminence.
    • Given the luminence, calculate a red, green, and blue value.
      • If you don't have your luminence code, here's an approximation:
        r, g, b = color
        (4*g + 3*r+b)//8
      • The brightest value (255) should be the named color:
        • E.g. "rojo", is roughly [220,10,20] (~red).
        • So 255 corresponds to 210 for red, 10 for green, and 20 for blue.
      • The darkest value (0) should be [0, 0, 0] (~black) in all cases.
  • Apply colorscale to images wherever you square and resize them.
Of note, the colorscale will be equivalent to "grayscale" for the color "blanco" (white).

Once you have read in rgb values and colorscaled the pixel arrays within each ColorObj, your code should produce an image like the following:

Such an image is indicative of a perfect score on the final exam. Well done! Turn in your code and have a wonderful break.