Today: variables, digital images, RGB color, for loop
106A Debug Steps, 1 and 2
- 1. Error message
Read the error message, look at line number
- 2. Wrong Output
Look at the output
Look at the code that produced it
Make a drawing creeps in ..
"what did line 6 see to do this"
PSA - What is "done" for a homework?
- Tempting: well the output is right, so I'm done!
Turn it in quick!
- You should understand why every line is there
- e.g. suppose you deleted all your codes
How long would it take to re-do it?
Ideally, not long, since you've learned it
- Progression
1. Solve problem, glance at lecture example
2. Ultimately, can solve it just on your own
- The exam problems will look a lot like the HW problems
Variables - 2 Things To Know
More details about variables in the Python Variables section in the Python guide.
A Python variable has a name and stores a value. We'll start with two rules of variables.
1. Creating a Variable
A variable is created in the code by a single equal sign = like this which creates a variable named x:
...
x = 42
...
The variable is set at the moment the line runs.
As a drawing, think of the variable as a little box, labeled with the variable's name, containing a pointer to the value stored.
2. Reading A Variable
After a variable is created, its name can appear in the code, e.g. x, and that use follows the arrow, retrieving whatever value was stored earlier. The name appears in the code as a bare word, no quote marks or anything.
Here's a little example. Suppose we set a variable named color to hold the value 'red'. Then subsequent lines can use color, and that retrieves the stored color.
color = 'red'
# instead of bit.paint('red')...
bit.paint(color)
bit.move()
bit.paint(color)
This paints 2 squares 'red', or whatever value was assigned to color on the first line.
Optional - All Blue Variable
We're not doing this one in class, but you can try it on your own to see a variable in action.
>
all-blue
Go back to our all-blue Bit loop. Change the code to use a color variable as below. The variable color is set to hold the value 'blue', and the later lines just paint whatever color is in the color variable. This version paints the whole row blue.
def all_blue(filename):
bit = Bit(filename)
color = 'blue'
bit.paint(color)
while bit.front_clear():
bit.move()
bit.paint(color)
bit.right()
Look at the lines bit.paint(color) lines - they refer to the variable by its name, following the arrow to retrieve 'blue' or whatever was stored there.
Q: How would you change this code to paint the whole row red?
A: Change line 3 to color = 'red' - the later lines just use whatever is in the color variable, so now they will paint red with no other change.
Python Math Expression + Evaluation
Math expressions and evaluation
You would think that computer programs have a lot of math at their core. It's true! Python can evaluate mathematical expressions, like a calculator can. Python follows the order of operations, so multiplication and division (* /) are evaluated before addition and subtraction (+ -). Other than that, the math is done from left to right. The math here looks a lot like regular mathematics, so we're not going to spend a lot of time explaining it.
So what will Python make of this expression?
1 + 2 * 3
The answer is 7, since Python does the multiplication before the addition. The official terminology is that Python "evaluates" the expression, computing a value from the expression.
Interpreter / Hack Mode >>>
The Python "interpreter" is a program on your laptop which makes Python work on your laptop. More details later on that. However, there is a way you can type code right at the interpreter to see what it does.
Try the >>> Hack Interpreter - there's a button for this at the bottom of each problem page on the experimental server. You type a little expression at the ">>>" prompt and hit the enter key. Python evaluates it, prints the resulting value on the next line. We'll use this more as we get into more Python features.
So the ">>>" is the Python interpreter. You type Python code to it directly, see what it does. Not a good way to get work done, but an excellent way to try little phrases of code to see what they do.
Variable Expression Demo
We can use the interpreter to try out the claims about variables and math expressions.
1. Variable Set / Get
>>> x = 11
>>> x
11
2. Expression
>>> 4 + 2 * 5 + 1
15
3. Variable + Expression
>>> x = 6
>>> 1 + 2 * x # evaluates to what?
The answer is 13. The appearance of x in the expression is just an example of a variable - just retrieves whatever value was set to that variable, in this case 6.
Images - Numbers - Code
- Layers of understanding
- 1. See An image - anyone can do that!
- 2. Understand structure of numbers, red/green/blue etc. making an image
- 3. Write code to change the numbers, changing the image .. CS106A!
- We'll look at all of these today
Digital Images - Pixels
- Originally the internet was made of text
- But perhaps images is where it really shines
- Digital images are made of small square "pixels"
"picture element" - "pixel"
- Pixel = small, square, shows a single color
- The color of a pixel is very often encoded as RGB
- Demo: pebbles.jpg in Mac Preview
Preview feature: new-from-clipboard to open copied image
It can zoom in far to see the pixels
See pebbles-zoomed.png
RGB Color Scheme
- The red-green-blue scheme, RGB
- We have three lights: red, green, and blue
- Each color is controlled by a number in the range 0..255
- (Why 255? I'll explain later)
- Each number represents the brightness of the red/green/blue light
- 0 = light is off
- 255 = light at maximum
- Can mix these 3 lights to make any color!
- Define any color by 3 numbers, 0..255
- Live RGB explorer: rgb-explorer
- Note: the RGB light-mixing scheme
different from paint-mixing scheme
Live RGB LED Demo
- Three super-bright LEDs
- red/green/blue
- A "MaxM" unit https://thingm.com/products/blinkm-maxm
- When I type "255 255 0" to this hardware
Turn red/green to max, blue off
Can set any combination on these three lights
- We can demo red / green / blue light mixing .. IRL!
Aside: Color Perception - Yikes!
- Red + green makes yellow
- That's close enough for our purposes
- But here's the real story...
- In reality, yellow is a different color than red or green
- In reality, your brain sees red + green light, interprets it as yellow
- It looks to us as if the red + green made yellow in the air, but that's just perception
- It's actually a bunch of red photons + a bunch of green photons .. and your brain does the rest
Image Structure in the Computer
- Image is made of pixels
- Pixels are in a x,y coordinate scheme
- Origin (0, 0) at the upper left
Origin at the upper left feels a little weird at first
Super common system on computers
We'll use it all quarter
- x numbers - x=0 is left edge
x values grow going to the right
- y numbers - y=0 is the top row
y values grow going down
- Each pixel:
Small
Square
Shows one color
- Pixel's color is encoded as 3 RGB numbers
Image Made of Pixels
Image Loading Code
This line loads an image into Python memory, and sets a variable named image to point to it, ready for Python to work on it.
# Load an image from the filesystem
# into memory in variable named "image".
# Now the image can be manipulated by code.
image = SimpleImage('flowers.jpg')
Have an Image, How To Change it?
Say we have loaded an image variable as shown above. Now we want to write code to change the image in some way.
For example, let's say we want to set the blue and green values in each pixel of the image to 0. This will leave just the red values. This is called the "red channel" of the image - an image made of just its red lights.
Preamble: pixel.red = 0
Suppose we have a variable pixel that refers to one pixel inside an image. (We'll show how to obtain such a pixel variable in the next step.)
Then the syntax pixel.red or pixel.blue or pixel.green refers to the red or blue or green value 0..255 inside the pixel.
The code below sets the red value of the pixel to 0, using the = similarly to above.
pixel.red = 0
Change Whole Image - for loop
The "red channel" of an image is just the red lights, with blue and green all turned off. Here is the code to make the red channel of an iamge using a "for loop".
def red_channel(filename):
image = SimpleImage(filename)
for pixel in image:
pixel.green = 0
pixel.blue = 0
return image
Here is a link - you can try running it first, then we'll see how it works
> Image1 Red
How It Works - Big Picture
For loop syntax:
for variable in collection:
# use variable in here
- The for loop is probably the most useful loop
- The for loop runs over a collection of elements
- Runs the loop body once for each element
- For each iteration, variable is set to point to one element from the collection
- Called the "for each" loop - running once for each element
- In this case, the loop body runs once for each pixel
- The variable name, e.g. pixel, can be any name the programmer chooses
Image For Loop Operation
Image Foreach Observations
- Filename is like 'flowers.jpg'
image = SimpleImage(filename)
loads image data into memory
image is a variable, points to image data
for pixel in image:
Loop runs lines once for each pixel in image
Loop sets pixel to point to each pixel in turn
return image
return xxx returns a value back to our caller, more later
- Q: how many times does first line run? How many times do the lines in the loop?
- A: once, once for each pixel
- Demo red_channel()
- So if there are 50,000 pixels, the loop body is run 50,000 times
- Experiment: green channel, make every pixel black
- See how for loop runs over the image
- See how
pixel.red accesses red/green/blue numbers in the image
Side trip about math
Update Variable: x = x + 1
What does this do:
x = x + 1
- Update the value of a variable
- Variable is on both left and right of =
- This changes the variable in a relative way
- Code rule:
- 1. first "evaluate" right side expression, after the =
- 2. assign that value back into the variable
- So x is 7 at the end
- Terminology
RHS is right hand side x + 1
LHS is left hand side x =
Basically, evaluate the RHS first, then do the LHS assignment
x = 6
x = x + 1
Example - Make Image Darker
> Image1 Darker
- Try making values smaller, image gets darker
e.g. red 200, change to red 100 .. literally darker
pixel.red = pixel.red * 0.5
- Relative change of red/green/blue on each pixel
- See below about "shorthand", re-write with *=
- (equivalently
pixel = pixel.red / 2)
for pixel in image:
pixel.red = pixel.red * 0.5
pixel.green = pixel.green * 0.5
pixel.blue = pixel.blue * 0.5
# or shorthand form:
# pixel.red *= 0.5
Relative Variable Shorthand: += -= *=
Shorthand way to write x = x + 1
x += 1
Shorthand for x = x * 2
x *= 2 # double x
For these image problems, that looks like
pixel.red = pixel.red * 0.5 # long form
pixel.red *= 0.5 # shorthand for above
- Works for all operators, such as
+= -= *= /=
- Handy because relative math on a variable is very common
- This just make the code more compact, not changing the underlying math
>>> x = 10
>>> x += 3
>>> x
13
>>> x *= 2
>>> x
26
Image1 Puzzles
> Copper Puzzle
Loop over the image, write code to change pixels, recovering the hidden image. Nick solves part, then students try to type code for the rest.
> 5-10-20 Banana Puzzle
5-10-20 puzzle: The red, green, and blue values are too small by a factor of 5 10 20. But we do not know which factor goes with which color. Figure it out by experimenting with code to modify the image with various factors (i.e. guessing and running it).
Image Coordinate System
Previously loaded image into memory like this. Now look at the x/y coordinate scheme of the pixels.
image = SimpleImage(filename)
image.width, image.height - int number of pixels
e.g. image.width is 200, image.height is 100
(like pixel.red - these are Python "properties")
- Origin x=0 y=0 is at upper left
- x grows right
- y grows down
- This coordinate scheme is like typesetting lines of text
- Zero based indexing
First element is index 0
- Q: Say width is 100, what is the rightmost pixel's x value
- A: it's not 100! it's 99
Super common mistake in zero-based world
- width 100, height 50 drawing, (x, y) pixels:
(0, 0) pixel at upper left
(99,0) at upper right
(99, 49) at lower right
- These x,y values are all fundamentally int numbers
There's no pixel at x=2.5
Using a float value to address an x,y will fail with an error
Talk about float values later
image.get_pixel(x, y)
- An image function that accesses one pixel
image.get_pixel(x, y)
- Returns a reference to the pixel at x,y
- Store reference in a variable, use .red .green on it
- Typically we use "pixel" as the variable name for this
# For the pixel at x=4 y=2 in "image",
# set its red value to 0
pixel = image.get_pixel(4, 2)
pixel.red = 0
Goal: Loop Over All the Coordinates
Step 1: range() function
for x in range(10):
- Today: want to write a loop where x = 0, 1, 2, 3, ...99
- 1-parameter range(n) function
range(10) represents the series:
0, 1, 2, ... 8, 9
- Start at 0, go up to but not including the n parameter
range(10) = 0, 1, 2, .. 9
range(5) = 0, 1, 2, 3, 4
range(n) = 0, 1, ... n-1
range(n) works in a foreach loop
- Works well with zero-based indexing
Hack/Demo: Try In Interpreter
Demo (or you can try it). The print(xx) function in this context just prints out what is passed to it within the parenthesis.
>>> for x in range(10):
print(x)
0
1
2
3
4
5
6
7
8
9
>>>
So here, we can see that foreach works with range, running the body once for each element.
Generating all x,y numbers for an image
- Say image width is 100, height is 50
- Basic plan to use range() to generate all x,y numbers
for x in range(image.width):
x will range over 0, 1, 2, .. 99
for y in range(image.height):
y will range over 0, 1, 2, .. 49
Nested Loops
- Nested loops are a little advanced
- Each run the loop body is called an "iteration" of the loop
- What happens if we place a loop inside another?
- An "outer" loop - use y loop above
- An "inner" loop - use x loop above
- Traditional do the "y" before the "x" here
In this way, it goes top-to-bottom on the image
for y in range(image.height):
for x in range(image.width):
# access x,y in here
pixel = image.get_pixel(x, y)
pixel.red = 0
Nested in Interpreter
- See how this works, need to get a feel for outer/inner loops work
- Demo it in the interpreter .. output is show below, as it's a lot to type
- e.g. say image width is 5, height is 4
- Outer y loop runs through the y values: 0, 1, 2, 3
- Inner loop runs through the x values: 0, 1, 2, 3, 4
- **Key rule**: for 1 iteration of outer, get all the iterations of the inner
e.g. y = 0, all the x's
then y = 1, all the x's again
>>> for y in range(4):
for x in range(5):
print('x:', x, 'y:', y)
x: 0 y: 0
x: 1 y: 0
x: 2 y: 0
x: 3 y: 0
x: 4 y: 0
x: 0 y: 1
x: 1 y: 1
x: 2 y: 1
x: 3 y: 1
x: 4 y: 1
x: 0 y: 2
x: 1 y: 2
x: 2 y: 2
x: 3 y: 2
x: 4 y: 2
x: 0 y: 3
x: 1 y: 3
x: 2 y: 3
x: 3 y: 3
x: 4 y: 3
Example: Darker-Nested
>
Darker Nested
Here is a version of the previous darker algorithm, but written using nested range(). Nested loops generate all the x,y. In the loop, the image.gix_pixel() function is called to get a reference to the pixel for each x,y.
def darker(filename):
"""
Darker-image algorithm, modifying
and returning the original image.
This version uses nested range loops.
Demo - this code is complete.
"""
image = SimpleImage(filename)
for y in range(image.height):
for x in range(image.width):
pixel = image.get_pixel(x, y)
pixel.red *= 0.5
pixel.green *= 0.5
pixel.blue *= 0.5
return image
- Use nested y/x loops to hit every pixel in an image
- Nested loop sequence:
- 1. Outer loop does first iteration, e.g. y = 0
- 2. Inner loop does all its iterations, x = 0, 1, 2, 3 ...
This gives us all y=0 coords: (0, 0), (1, 0), (2, 0), .. (99, 0)
- 3. Outer loop does second iteration, y = 1
- 4. Inner loop does all iterations again: x = 0, 1, 2, 3 ...
This gives us all y=1 coords: (0, 1), (1, 1), (2, 1), .. (99, 1)
- The effect is going through the rows top to bottom. Each row going left to right - like reading English text
- Demo: add in inner loop:
print('x:', x, 'y:', y)
print() like this is a debugging trick we'll build on later
See a line printed for each pixel, showing the whole x,y sequence
Only do this with small images like we have here
Otherwise it's too much output
- Demo: try making x too limited,
range(image.width - 20)
- Demo: what if we swap the roles of the y/x loops?
It works fine, just a different order of the pixels
Goes through the columns left-right. For each column, top to bottom
Here is a picture, showing the order the nested y/x loops go through all the pixels - all of the top y=0 row, then the next y=1 row, and so on.
Good news: this is our first nested loop. We'll do more later. It happens that the y/x
nested loop for an image is idiomatic - it's the same every time, so you can just kind of use it while you get used to it.