Basic Programming Using Python: Images (Instructor Version)

Objectives

FIXME

Lesson

We now have almost everything we need to create the altered images Valerie needs for her experiments. The only thing we're missing is some actual image processing. Luckily, that's easy to add given what we've already learned:


In [1]:
import image_novice as novice
flower = novice.open('flower.png')
flower


Out[1]:

The first statement asks Python to find a library called skimage (the SciKit image processing library) and load its novice module. The second statement asks novice to open an file called flower.png and assign the result to the variable flower. Finally, we ask that image to display itself. If all has gone well, this shows a picture of the world's cutest child.

Like block grids, images have properties:


In [2]:
print 'format:', flower.format
print 'size:', flower.size
print 'width:', flower.width
print 'path:', flower.path


format: png
size: (240, 180)
width: 240
path: /home/hansenm/Projects/novice/flower.png

Unlike blocks grids, though, we can change some of the properties related to sizing:


In [3]:
flower.size = (400, 100)
flower


Out[3]:

We can also modify the colors of particular pixels:


In [4]:
for pixel in flower:
    pixel.red = 0
flower


Out[4]:

Once we make changes, the picture object in memory knows that it has been modified, and its path is set to None to show that it doesn't correspond to a file on disk any longer:


In [5]:
print 'modified:', flower.modified
print 'path:', flower.path


modified: True
path: None

We can easily save it to disk:


In [6]:
flower.save('/tmp/blue-green-flower.png')
print 'modified:', flower.modified
print 'path:', flower.path


modified: False
path: /tmp/blue-green-flower.png

And yes, we can select individual pixels or entire slices, but the results may surprise you:


In [7]:
flower[0:100, 0:50] = (255, 0, 0)
flower


Out[7]:

Like ipythonblocks.ImageGrid, skimage.novice uses a standard Cartesian coordinate system.

Let's generate some images:


In [8]:
BASE    = 128
STEP    = 4
SPACING = 4
NUMBER  = 6
GRAY    = (BASE, BASE, BASE)
SIZE    = 200

for color in range(BASE + STEP, BASE + NUMBER * STEP, STEP):
    image = novice.Picture(size=(SIZE, SIZE), color=GRAY)
    image[::SPACING, ::SPACING] = (color, color, color)
    filename = 'step-' + str(color) + '.png'
    image.save(filename)

This program creates files with names like step-132.png, step-136.png, and so on. Each one has white pixels spaced evenly across a gray background, with the white slowly getting whiter. Images like these are sometimes used in vision tests, since different spacings and colorings are noticeable to different people.

Having told you that, we've spoiled the second part of our experiment, which is to see how much knowing what a program does helps you figure out how it works. As you might suspect, the answer is, "A lot." Let's go through this one.

The constants at the top don't tell us much, although GRAY is obviously an RGB color value and SIZE is (probably) an image size. Next is our loop: given the name of its index variable, color, we can guess that we're looping over colors, and sure enough, two lines down, we see (color, color, color), which is creating a shade of gray defined by the current value of color.

Backing up a line, we're creating a SIZE×SIZE image using novice.new and setting all its pixels to the fixed color GRAY. The loop is going from BASE + STEP up to BASE + NUMBER * STEP in increments of STEP. BASE is 128—our initial shade of gray. STEP is 4: that's the increment each time we go around the loop. And NUMBER is how many we're doing, so this loop is creating a bunch of images with the same gray background, but whiter and whiter points spaced at equal intervals across them.

Key Points

FIXME