HW 3:
Animations

Submission Details

Due: 2/25

Submit via Gradescope:

2 files in total

  • hw3.zip

  • A PDF that contains your self-assessment & reflection

Instructions

  • Log in to Coding Rooms

  • Navigate to the Assignment of HW 3: Animations

  • Work through each part of the homework and use Coding Rooms to develop your code.

  • When you're ready to submit, download the entire project from Coding Rooms

    • Rename it to be hw3.zip

    • Submit it to Gradescope.

  • Review the solutions to perform a self-assessment and write your reflection.

    • You may write up your self-assessment and reflection in whatever format is most comfortable for you (written on paper, typed in a Word or Google Doc, etc.), but you must convert it to a PDF before submitting. Please ask if you need help with this!

    • Submit the PDF to Gradescope and name it SelfAssessmentReflection.pdf.

Self-assessment & Reflection (reproduced from the Syllabus for reference)

Homeworks are an opportunity to engage with the material and practice your programming skills. To support you, solutions will be available on moodle. It is your responsibility to engage with them in a way that strategically moves your learning forward. You should:

  • Attempt the homework without looking at the solutions.

  • Review the solutions; if you were stuck, you may revise your homework. You may find yourself iteratively reviewing and revising to best support your learning.

  • When you submit your homework, you will be required to complete a self-assessment and reflection:

  1. Give yourself an assessment mark on the initial attempt:

✘ if you did not complete it

✓- if you completed it, but were very far from the solution

✓ if you completed it and essentially had the solution

✓+ if you completed it correctly, including precise communication

? if you are unsure as to whether or not you completed it

  1. Provide a short reflection as to why you earned that mark, such as:

  • I did not understand how to get started, and it was too close to the deadline to visit support hours.

  • I got stumped by a bug where my program entered an infinite loop.

  1. If you used the solutions to revise your work, describe what you needed to change, as in:

    • I modified the style of my conditional when I saw a simpler approach in the solutions.

    • I was so stuck that the majority of my submission is based on the solutions.

  2. Ask specific questions on portions that are still confusing, such as:

    • Why does it cause an error when I returned "1" instead of 1?

[Optional] Provide one hint for the homework (to your past self or future student).

Starter code

Overview

This homework will give you a chance to practice for loops over and over and over... First, you'll work to debug some code to help clarify your understanding or identify questions. Then, you'll use image processing to create animated gifs with iteration, like the ones below:

Learning goals

This homework will emphasize reading and adapting sample code that relies on iteration with for loops. It's a bit of a shift from the previous homeworks and project, which emphasized a more comprehensive goal. Here, you'll have a set of smaller tasks to accomplish, helping you develop the skills of:

  • reading sample code with comments to guide you

  • writing function definitions from high-level descriptions

  • adapting sample code to achieve similar behavior

  • type-hinting and using mypy to make sure types are consistent

Be strategic!

  • Start early!

  • Ask questions!

  • Leave time for questions by... starting early :)

  • When you're stuck, pause and evaluate what could be going on. Remember the self-regulated learning cycle!

    • Do you have a plan?

    • Are you evaluating your plan? How will you test your code as you go?

    • How will you revise? What will you do when you're confused? Stuck?

Part 1: Debugging for loops

In Coding Rooms, open the file buggyLoops.py.

  • This program has some bugs in it!

  • Work to debug the program, using the comments to guide you.

  • For reference, the original buggy program is below.

buggyLoops.py

def buggy1( baseName:str, lo:int, hi:int ) -> list:

""" This function should create a list of strings with

the baseName followed by numbers between lo and hi.

Includes lo, but not hi.

For example, buggy1("dragon", 2, 5) should return

["dragon2", "dragon3", "dragon4"] """


# use the accumulator pattern to grow the list

generatedNames:list = [] # start with the empty list


# for every value between lo and hi

for i in range(hi):

# create the current name

currentName:str = baseName + i


# accumulate it into the list

generatedNames.append( currentName )

# return the list

return generatedNames


def buggy2( baseName:str, lo:int, hi:int ) -> list:

""" This function should create a list of strings with

the baseName followed by numbers between lo and hi.

Includes lo, but not hi.

For example, buggy1("dragon", 2, 5) should return

["dragon2", "dragon3", "dragon4"] """


# determine how big the list should be

listSize:int = hi - lo


# initialize a list with that many slots

generatedNames:list = [""]*listSize


# walk over the list using the indices

for i in range(len(generatedNames)):

# create the current name

currentName:str = baseName + str(i)


# accumulate it into the list

generatedNames[i] = currentName

# return the list

return generatedNames


def buggy3( baseNumber:int, numPlaces:int ) -> list:

""" Given a baseNumber and a number of places,

construct and return a list that has numPlaces power values,

where index i holds the value baseNumber**i.

For example, buggy3( 3, 4 ) should return

[1,3,9,27] """


# use the accumulator pattern to grow the list

powers:list = [] # start with the empty list


# for every value between lo and hi

for i in range(numPlaces):

# create the current name

currentPower:int = baseNumber**i


# accumulate it into the list

powers.append( currentPower )

# return the list

return powers


def buggy4( baseNumber:int, numPlaces:int ) -> list:

""" Given a baseNumber and a number of places,

construct and return a list that has numPlaces power values,

where index i holds the value baseNumber**i.

For example, buggy3( 3, 4 ) should return

[1,3,9,27] """


# initialize a list with slots holding placeholder -1 values

powers:list = [-1]*numPlaces


# walk over the list using the indices

for i in range(baseNumber):

# create the current name

currentPower:int = baseNumber**i


# accumulate it into the list

powers[i] += currentpower


# return the list

return powers


def test():

# test the first function

dragons:list = buggy1( "dragon", 2, 5 )

print( f'Expecting: \t["dragon2", "dragon3", "dragon4"] \nActual: \t{dragons}' )


# test the second function

moreDragons:list = buggy2( "dragon", 2, 5 )

print( f'Expecting: \t["dragon2", "dragon3", "dragon4"] \nActual: \t{moreDragons}' )


# test the third function

powers:list = buggy3( 3, 4 )

print( f'Expecting: \t[1, 3, 9, 27] \nActual: \t{powers}' )


# test the fourth function

morePowers:list = buggy4( 3, 4 )

print( f'Expecting: \t[1, 3, 9, 27] \nActual: \t{morePowers}' )



if __name__ == "__main__":

test()

Tips

  • Some bugs are "easy" to detect in that Python will actually print out an error.

    • Use the error message to identify where the bug is.

      • The line number is a great starting point!

  • Some bugs are "logical" bugs, which are a bit harder to identify and fix. For the buggy code given to you, identify them by looking at the

      • printed messages -- the testing code helps to show when the actual behavior of a function doesn't match its expected behavior

      • comments -- they should help you understand the high-level approach; there is a logical bug whenever the Python code does not implement the high-level approach

Part 2: animation

In this part, we'll use iteration to produce animations!

We'll be using the Pillow library, which provides functions and objects for processing images in Python. We will only be using some basic functionality, and there is so much more to image processing. If you haven't had much experience with digital images and are curious about the data representation using pixels and (often) RGB colors, check out this video.

What is an animation?

We'll be using animated GIF files to store a sequence of images. Each "frame" of the animation is a single image. The animated gif shows each frame for a specific duration before showing the next one. I like to think about flip books to understand what these frames are -- each frame is a page of a flip book. When an animated GIF is displayed (in a program that supports the animation, like a browser), it is like flipping through the flip book pages.

Part 0: Getting started

In Coding Rooms, open the file animator.py.

There is are 4 functions defined for you, which work with image files already in your Coding Rooms workspace:

  • flappingToaster creates an animated GIF of a flying toaster (from a set of Macintosh screen savers popular back in the day...)

  • slideTheGates takes in a single parameter specifying the number of frames ("flip book pages") to animate a sweep effect revealing an image (like the flower image on the left above)

  • openTheGates takes in a single parameter specifying the number of frames ("flip book pages") to animate a sweep effect revealing a colorized version of an image (like the flower image in the center above)

  • dragonOnGate takes in a single parameter specifying where to (horizontally) paste a dragon image onto another image

Each of these functions will generate an image file and save it in your workspace.

  • due to some technical difficulties with Coding Rooms, you'll see some "empty" gifs in the animations folder that only show a white background

  • these will be updated as you move through this part of the homework

Step 1: Creating a simple animated GIF

Let's see how the accumulator pattern is used to construct an animated gif by building up a list of Image objects. The Image class is defined by the Pillow library, and we can refer to it like any other type.

  1. Read through the definition of the flappingToaster function -- what do you think will happen when you invoke it?

  2. Edit the main function to invoke the flappingToaster function.

  3. Run the program -- did it do what you expected?

Not sure what you should expect? Expand this section!

We should expect that a file called flappingToaster.gif with an animation is created -- we can view it by looking inside the animations directory (see image below).

Why might we expect this?

Let's try to understand the code at a high level, even though some of it will not fully make sense.


The flappingToaster function (based on the comments) should:

Load 4 flying toaster images from file and create/save an animated gif with the images as frames.

It does this using an accumulator pattern, creating a list of Image objects, one for each frame. The list is then saved to a single animated gif file.

  1. toasterFrames is the list that will be accumulated into and is initialized to the empty list

  2. a for loop is used to iterate over the numbers 0,1,2,3 which accomplishes

    • 4 iterations of the loop body

    • an easy way to access the files in order, since they are named using the pattern toasterNUM.gif, as in:

toaster0.gif, toaster1.gif, toaster2.gif, toaster3.gif
(You can look inside the toasters directory and see the files listed there.)

  1. each iteration of the loop creates an Image object that is then added to the list of frames

    • first, the correct "path" for that iteration is created

      • we not only need the file name, like toaster0.gif

  • we also need to tell Python that it's stored within the toasters directory

  • the full "path" gives these directions, as in toasters/toaster0.gif

    • then we open the file and store the returned Image object in a variable toasterImage

  • finally, we add it to the toasterFrames list to perform the accumulation for that iteration

  1. once the loop completes, we have a list of Image objects, one for each of the 4 frames -- we use that list to create a new file for the animated gif.


It's not important that you understand the mechanics of interacting with the Image files and the Pillow library. Instead, remember that we are being immersed in a development environment. Can you figure out roughly which parts you would need to change? Here are some sample questions to help:

  • How would you change the code if the original files were not in the toasters directory, but in a frames directory instead?

  • How would you change the code if the original files were not named toaster0.gif, toaster1.gif, toaster2.gif, toaster3.gif, but instead toaster_0.gif, toaster_1.gif, toaster_2.gif, toaster_3.gif or toaster1.gif, toaster2.gif, toaster3.gif, toaster4.gif?

  • What do you think would be different if the last line of code were not 'animations/flappingToaster.gif' but 'flappingToaster.gif' instead? Not sure? Try it and see!

Output

You won't see anything particularly exciting print to your console. Instead, the output of your program is the creation of a new file!

Coding Rooms will display files, like animated GIF, for you. You can double-click and should see a flying toaster if all goes well!

Your turn!

  1. You'll notice there is a folder in your workspace called dragons. This contains 25 dragon images authored by Lydia Cheah '20.

  2. Create a new function called dragons that takes in no parameters and has a return type of None.

    • The function should create an animated gif named animatedDragon.gif inside the animations folder.
      As noted, there is a technical issue with Coding Rooms that is preventing reliable creation of new GIF files. Because of this, there is a placeholder "blank" file that you will overwrite.

  3. Run your code and debug if necessary!

Step 2: Cropping images to create a sweep effect

Now look at the slideTheGates to see how the accumulator pattern can be used (twice) to create a sweep effect. Each iteration of the loop, we:

  • create a blank background (this is like adding a blank page to the flip book)

  • crop the image and increase a variable holding the width to use in the next iteration

  • paste the cropped image to the blank background (like a sticker)

  • add that image/frame/page to the list/book for the final animated GIF


  1. Read through the definition of the slideTheGates function -- what do you think will happen when you invoke it?

  2. Edit the main function to invoke the slideTheGates function, passing the value 5.

  3. Run the program -- did it do what you expected?

  4. Modify your invocation to instead pass the value 10 -- what do you think will happen?

  5. Run the program -- did it do what you expected?

Next, look at openTheGates to see how to use a grayscale background instead of a blank page to give the effect of colorizing an image.


  1. Read through the definition of the openTheGates function.

    • What differences do you notice from the previous slideTheGates function?

    • What do you think will happen when you invoke it?

  2. Edit the main function to invoke the openTheGates function, passing the value 20.

  3. Run the program -- did it do what you expected?

Your turn!

  1. Both the functions produce a constant speed sweep. Can you adapt the openTheGates code to make the sweep change pace exponentially, as in the flower animation above on the right?

  2. Create a new function called openTheGatesExp that does not take in any parameters and has a return type of None.

    • The function should create an animated gif named openingGatesExp.gif inside the animations folder.
      As noted, there is a technical issue with Coding Rooms that is preventing reliable creation of new GIF files. Because of this, there is a placeholder "blank" file that you will overwrite.

    • The sweep should use powers of 2 to reveal the image:

      • Iteration 0 should reveal a 20 = 1 pixel strip of the image

      • Iteration 1 should reveal a 21 = 2 pixel strip of the image

      • Iteration 2 should reveal a 22 = 4 pixel strip of the image

      • ...

      • Iteration i should reveal a 2i pixel strip of the image

      • ...

    • First, you'll need to determine when to stop -- how many frames will you have?

      • You may have learned that the log function is the inverse of the exponentiation function. Since we want to stop when 2i = the image width, we can use log_2 (image_width) to help us. In Python, this will look like the following, assuming you keep the same gates variable name from openTheGates:
        numFrames:int = int(math.ceil(math.log(gates.width,2))) + 1

      • This code calculates the log with base 2 using the math function log: math.log(_____,2)

        • You'll need to import math to access the log function

        • The log with base 2 is the number of times we divide a value in half until we get down to 1.

      • Then the code takes the ceiling of the log value, as it may be a decimal and not an integer value. The ceiling pulls the value up to the next whole number (unless the value is already an integer value).

        • The ceiling of 3.0 is 3.

        • The ceiling of 3.1 is 4.

        • The ceiling of 3.999 is 4.

      • We then add 1 to this value, since we want to make it to 2i and we started with i at 0.

    • Now that you know the number of frames, you're ready to use the accumulator pattern to build up your list of frames (flip book pages). How will you assign the width to grow exponentially?

  3. Run your code and debug if necessary!

Step 3: fly, dragon, fly!

Finally, let's create an animated gif with a dragon flying across it!

Look at dragonOnGate to see how to load and paste a dragon image onto another image.


  1. Read through the definition of the dragonOnGate function.

    • What do you think will happen when you invoke it?

  2. Edit the main function to invoke the openTheGates function, passing the value 0.

  3. Run the program -- did it do what you expected?

  4. Modify your function invocation to pass the value 50 -- what do you think will happen?

  5. Run the program -- did it do what you expected?

Your turn!

  1. Your task is to make an animated gif, similar to the one to the right.

  2. Create a new function called dragonGates that takes in a single parameter and has a return type of None.

  3. You should adapt the code from openTheGates to generate an animation that has:

    • the uncommonWomenMHC image as the background

    • 20 frames, each showed for a duration of 160 milliseconds

    • moves the dragon by image.width//(numFrames-2) each iteration

      • the -2 will make it fly "off" the picture

    • saves the file to dragonGates.gif within the animations folder

Dragon images credited to Lydia Cheah '20 as part of a video development project.

Thank you, Lydia!!!

Solutions

As per the Syllabus: you should use these to support your learning. Do not look at solutions until you have attempted the work. It is up to you to best leverage this resource.