Basic Programming Using Python: Resizing an Image

Objectives

  • Be able to import libraries using three forms of import and refer to their components using dotted notation.
  • Call simple functions and methods with numeric and string arguments.
  • Assign values to variables.
  • Add comments to programs.
  • Identify integer and floating-point numbers, and explain the differences between them.

Lesson

The best way to learn how to program is to start doing something useful, so let's create a thumbnail for an image. We'll start by loading and displaying the original:


In [29]:
from skimage import novice
picture = novice.open('flower.png')
picture.show()


Out[29]:

print its original size:


In [30]:
print 'original size:', picture.size


original size: (240, 180)

and then change its size and redisplay it:


In [31]:
picture.size = (100, 100)
picture.show()


skimage.dtype_converter: WARNING: Possible precision loss when converting from float64 to uint8
Out[31]:

If we just want to show the resized picture, we only need four of the six lines of code we just ran:

from skimage import novice picture = novice.open('flower.png') picture.size = (100, 100) picture.show()

There's a lot going on in those four lines, but once we understand them, and figure out how to avoid making the resized picture look squished, we'll understand about a quarter of what we need to accomplish most routine programming tasks.

Importing Libraries

The first line of our little program is called an import statement. It tells Python to find a library called skimage, look in it for a sub-library called novice, and load that into memory so that we can use the things it contains. Most programs start this way, because most of the power of a programming language isn't actually in the language, but in the libraries written for that language. (By analogy, most of the power of a language like English is not in the individual words, but in the books written in it.)

Here's a simpler example of importing something:


In [32]:
import math
print 'pi is:', math.pi


pi is: 3.14159265359

The first line imports the standard math library, which contains things like the value of π. The second line is a print statement: it tells Python to print the character string (or "string" for short) 'pi is:' and the value of π.


Printing, Spaces, and Newlines

`print` automatically puts a single space between the things we ask it to print, and then starts a new line. If we just want to print a blank line, we use `print` on its own. if we don't want to start a new line, we put a comma at the end of the list of things we've asked it to print:


In [33]:
print 'first', # print the word 'first', but don't start a new line
print 'second', # print the word 'second', but don't start a new line
print # just start a new line


first second

As the example above shows, we can put [comments](glossary.html#comment) in our programs to explain what they're doing. A comment starts with a '#' character and runs to the end of the line; Python ignores everything in comments when it's executing code.


Along with useful constants (like π), libraries can contain functions. A function is a piece of code that has been given a name so that we can use it in other programs. The math library contains many useful functions, two of which are shown below:


In [34]:
print 'square root of 5:', math.sqrt(5)


square root of 5: 2.2360679775

In [35]:
print 'cosine of pi/2:', math.cos(math.pi / 2)


cosine of pi/2: 6.12323399574e-17

Just as we we referred to π as math.pi, we refer to the functions in the math library using dotted notation as math.sqrt (for square root), math.cos (for cosine), and so on. This helps us keep track of things, just like calling something "Darwin's notebook" or "Gould's wallet". If we want to do a little less typing, we can give the library a nickname (or alias) as we import it:


In [36]:
import math as m
print 'square root of 7:', m.sqrt(7) # instead of math.sqrt


square root of 7: 2.64575131106

We can even import the things we want for direct use:


In [37]:
from math import sqrt
print 'square root of 9:', sqrt(9) # doesn't use the dot


square root of 9: 3.0


Why Isn't the Cosine of π/2 Zero?

When we printed the cosine of π/2, we got 6.12323399574e-17 (i.e., roughly 6×10-17) rather than 0. The reason is that the computer can't represent the value of π exactly in a finite number of digits, just as 0.333333333333… doest't capture 1/3 precisely. When we take the cosine of this approximation, we get something that is almost zero, but not quite. Small errors like this crop up in most scientific calculations; we'll look later at ways to handle them.


Values and Variables

The second line of our four-line thumbnail program is:

picture = novice.open('flower.png')

At this point, novice.open should make sense: we have loaded the novice sub-library from skimage (which, by the way, stands for "SciKit Imaging Library"), and we're using its open function. Instead of giving that function a number, as we did with math.sqrt and math.cos, we're giving it the name of the image file that we want to open as a character string. novice.open finds this file, copies its contents from disk into memory, and gives us a reference to that chunk of memory. We then store that reference in a variable called picture so that we can do things to it later.

FIXME: diagram

A variable is just a name for a value—something we can use to refer to that value later in the program. Python creates a variable whenever we assign a value to a name using =, and we can use the variable's value after that just by using the variable name:


In [38]:
mass = 90
age = 50
name = 'Subject #1'
print name, ':', age, '/', mass


Subject #1 : 50 / 90

To change a variable's value, we just assign it a new one:


In [39]:
age = 45 # correcting subject age
print name, ':', age, '/', mass


Subject #1 : 45 / 90

New values can be calculated from old:


In [40]:
mass_pounds = mass * 2.2
print 'mass in pounds:', mass_pounds


mass in pounds: 198.0

We can even calculate a new value based on an old one. In this case, Python reads the values of everything on the right side of the assignment before changing the variable on the left:


In [41]:
mass = mass * 2.2 # converting mass to pounds in place
print name, ':', age, '/', mass


Subject #1 : 45 / 198.0

And of course, we can use one variable to calculate a value for another:


In [42]:
ratio = mass / age
print 'pounds per year:', ratio


pounds per year: 4.4

What we can't do is use a variable that we haven't assigned a value. For example, suppose we try to print mas with one 's' instead of mass:


In [43]:
print 'Subject mass:', mas # only one 's'


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-43-0da7ce60de93> in <module>()
----> 1 print 'Subject mass:', mas # only one 's'

NameError: name 'mas' is not defined
Subject mass:

Other languages, such as Perl, do let programmers use variables they haven't defined, and automatically give those variables default values like zero. This occasionally saves a bit of typing, but it can also cause hard-to-find errors, since human eyes can all-too-easily mis-read mas as mass or temperatrue as temperature.


Arithmetic

The table below shows some commonly-used arithmetic operators in Python.

OperationSymbolExampleResult

Addition+2+35

Subtraction-2-3-1

Multiplication*2*36

Division/2/30

2.0/30.66666666666

Remainder%15%63

Exponentiation**2**38 </table> </em>


Resizing and Displaying

Now that we know what variables and assignment are, we can also understand the rest of our program:

picture.size = (100, 100)
picture.show()

The first of these lines (line 3 of the original program) assigns (100, 100) to picture.size. The pair of values in parentheses is called a tuple; like a coordinate pair in mathematics, it's a single thing with two parts. And as picture.size's name suggests, it is the size of our picture. When we assign it a new value, the skimage.novice library changes the size of the picture as requested.

Finally, the statement picture.show() called a method of the picture which, as its name suggests, displays the picture. A method is just a function that's part of a particular value, just as math.sqrt is a function that's part of the math library. If we had several pictures in memory, each one would have a show method that would do the same thing, but display that particular picture.

Unsquishing the Picture

Right now, our little program always creates a 100×100 thumbnail, even when the original image wasn't square. Suppose we want to create thumbnails that are always 100 pixels wide, but keep the aspect ratio of the original. If the original was 200×300, for example, our thumbnail would be 100×150, while a 300×200 image would be turned into a thumbnail that was 100 pixels wide and 66&frac23 pixels high.

That &frac23 is our first problem. There's no such things as a fraction of a pixel, so we have to decide whether we want to round off to the nearest whole number, or throw away the fractional part (which in this case means rounding down). The former seems more sensible, and Python has a built-in function called round to round things off, so we'll do that. Here's a quick test:


In [44]:
print 'rounding off 2/3 should give 1:', round(2/3)


 rounding off 2/3 should give 1: 0.0

Whoops: why is the result 0? And why is the result printed as 0.0 rather than 0?

The answer to both questions is that computers can store numbers as integers or floating point numbers (usually just called "floats"). Integers are the familiar values 1, 2, 3, 97, -6, and so on; floats have fractional parts, like 2.5 or -7.172483921748. In many cases, the difference isn't important—2×3 and 2.0×3.0 give the same answer:


In [45]:
print 'using integers:', 2*3
print 'using floats:', 2.0*3.0


using integers: 6
using floats: 6.0

However, if we divide one integer by another, Python only keeps the integer portion of the result, so 5/2 is 2 rather than 2.5, and 2/5 is 0 rather than 0.4:


In [46]:
print '5/2:', 5/2
print '2/5:', 2/5


5/2: 2
2/5: 0

If either argument of the division is a float, on the other hand, Python keeps the fraction when doing the calculation:


In [47]:
print '5.0/2:', 5.0/2
print '2/5.0:', 2/5.0


5.0/2: 2.5
2/5.0: 0.4

This is why round(2/3) produced 0.0: 2/3 is the integer 0, which round turned into the float 0.0. To get the answer we expected, we can either use constant values:


In [48]:
print 'nearest whole number to 2/3:', round(2.0/3.0)


nearest whole number to 2/3: 1.0

or use another built-in function called float to turn one or the other argument of the division into a float:


In [49]:
print 'nearest whole number to 2/3:', round(float(2)/3)


nearest whole number to 2/3: 1.0

There is another built-in function, int, which turns floats into integers, but unlike round, it truncates (just like division):


In [50]:
print 'int(0.66666666):', int(0.66666666)


int(0.66666666): 0


Why Two Kinds of Numbers?

There are two reasons why computers have two ways to represent numbers. The first is historical: for several decades, operations on integers were faster than operations on floats, so it made sense to let programmers use whichever they wanted. The other reason is that they really are different: integers are for counting things, while floats are for measuring quantitites.


We now have the tools we need to create proportional thumbnails. Once again, we'll start by loading and displaying:


In [65]:
flower = novice.open('flower.png')
flower.show()


Out[65]:

Notice that we didn't import the novice library again. Once it has been loaded into memory, it is there until we end our session (e.g., close our IPython Notebook or exit from the command-line interpreter). Notice also that we called our variable flower instead of picture. We could call the variable anything from a to zzzzzzzzzzzzzzzzzzzz—the computer doesn't care, and doesn't infer what kind of data a variable refers to from its name—but using names like picture and flower makes it easier for people to understand what we're doing.

Next, let's find out how wide and high our image is:


In [66]:
print 'flower.width:', flower.width
print 'flower.height:', flower.height


flower.width: 240
flower.height: 180

If the thumbnail is going to be 100 pixels wide, we need to shrink the picture by a factor of 100/240. We need to use floats for that calculation, though, so we'll write it as 100.0/240. To keep the picture's height proportional, we need to shrink it by the same ratio, which is 180×100.0/240. Here is the calculation and the resulting image:


In [67]:
new_height = 180*100.0/240
flower.size = (100, new_height)
flower.show()


skimage.dtype_converter: WARNING: Possible precision loss when converting from float64 to uint8
Out[67]:

And to check:


In [68]:
print 'flower.size after thumbnailing:', flower.size


flower.size after thumbnailing: (100, 75)

That's great, but we can do better. Our next picture might not be exactly 240 pixels wide and 180 high, so instead of using those values in the calculation, we should use the picture's actual width and height:


In [69]:
flower = novice.open('flower.png') # reload because we changed the size of the previous copy in memory
new_height = flower.height * (100.0 / flower.width)
flower.size = (100, new_height)
flower.show()


skimage.dtype_converter: WARNING: Possible precision loss when converting from float64 to uint8
Out[69]:

We have now accomplished our original task for one picture, but what if we had to create thumbnails for a dozen? Or for several thousand? Typing in same four lines a thousand times would be very tedious, and we would almost certainly make a mistake. In fact, we did make a mistake several times while writing this lesson: we called the variable referring to the second copy of the image flower, then used picture.width in the calculation that resized it. To solve these problems, we'll need to take a look at how functions are created, and at how to tell a computer to do something many times over.

Key Points

  • Use import library, import library as nickname, and from library import thing to load code from libraries.
  • Use variable = value to assign a value to a variable.
    • This creates variable if it does not already exist.
    • And assigns it a new value if it does.
  • Use meaningful names for variables to make programs easier for people to understand.
  • Use integers to count, and floats to measure.