Pixels and Colors

Most people want to do a lot more to pictures than just display them and crop them. If you do a lot of digital photography, you may want to remove the "red-eye" caused by your camera flash. You might also want to convert pictures to black and white for printing, highlight certain objects, and so on.

To do these things, you must work with the individual pixels that make up the image. The media module represents pixels using the RGB color model discussed in the sidebar on page 72. Module media provides a Color type and more than 100 predefined Color values. Several of them are listed in Figure 4.3, on page 62; black is represented as "no blue, no green, no red," white is the maximum possible amount of all three, and other colors lie somewhere in between.

The media module provides functions for getting and changing the colors in pixels (see Figure 4.9, on the next page) and for manipulating colors themselves (see Figure 4.10, on page 70).

To see how these functions are used, let's go through all the pixels in Madeleine's cropped and named picture and make it look like it was taken at sunset. To do this, we're going to remove some of the blue and some of the green from each pixel, making the picture darker and redder.2

2. We're not actually adding any red, but reducing the amount of blue and green will fool the eye into thinking we have.

Function get_red(pixel) set_red(pixel, value) get_blue(pixel) set_blue(pixel, value) get_green(pixel) set_green(pixel, value) get_color(pixel) set_color(pixel/ color)

Description

Gets the red component of pixel

Sets the red component of pixel to value

Gets the red component of pixel

Sets the blue component of pixel to value

Gets the red component of pixel

Sets the green component of pixel to value

Gets the color of pixel

Sets the color of pixel to color

Figure 4.9: Pixel-manipulation functions

Download modules/sunset.py

import media pic = media.load_picture( 'pic207.jpg') media.show(pic)

for p in media.get_pixels(pic):

new_blue = int(0.7 * media.get_blue(p)) new_green = int(0.7 * media.get_green(p)) media.set_blue(p, new_blue) media.set_green(p, new_green)

media.show(pic) Some things to note:

• Color values are integers, so we need to convert the result of multiplying the blue and green by 0.7 using the function int.

• The for loop does something to each pixel in the picture. We will talk about for loops in detail in Section 5.4, Processing List Items, on page 89, but just reading the code aloud will give you the idea that it associates each pixel in turn with the variable p, extracts the blue and green components, calculates new values for them, and then resets the values in the pixel.

Try this code on a picture of your own, and see how convincing the result is.

Description

Function darken(color) lighten(color)

create_color(red, green, blue) distance(c1, c2)

Description

Returns a color slightly darker than color Returns a color slightly darker than color Returns color (red, green, blue)

Returns how far apart colors c1 and c2 are

Figure 4.10: Color functions

4.5 Testing

Another use for modules in real-world Python programming is to make sure that programs don't just run but also produce the right answers. In science, for example, the programs you use to analyze experimental data must be at least as reliable as the lab equipment you used to collect that data, or there's no point running the experiment. The programs that run CAT scanners and other medical equipment must be even more reliable, since lives depend on them. As it happens, the tools used to make sure that these programs are behaving correctly can also be used by instructors to grade students' assignments and by students to check their programs before submitting them.

Checking that software is doing the right thing is called quality assurance, or QA. Over the last fifty years, programmers have learned that quality isn't some kind of magic pixie dust that you can sprinkle on a program after it has been written. Quality has to be designed in, and software must be tested and retested to check that it meets standards.

The good news is that putting effort into QA actually makes you more productive overall. The reason can be seen in Boehm's curve in Figure 4.11, on the following page. The later you find a bug, the more expensive it is to fix, so catching bugs early reduces overall effort.

Most good programmers today don't just test their software while writing it; they build their tests so that other people can rerun them months later and a dozen time zones away. This takes a little more time up front but makes programmers more productive overall, since every hour invested in preventing bugs saves two, three, or ten frustrating hours tracking bugs down.

Cost

/

Requirements

Design

Coding

Testing

Deployment

Figure 4.11: Boehm's curve

One popular testing library for Python is called Nose, which can be downloaded for free at http://code.google.com/p/python-nose/. To show how it works, we will use it to test our temperature module. To start, create a new Python file called test_temperature.py. The name is important: when Nose runs, it automatically looks for files whose names start with the letters test_. The second part of the name is up to us—we could call it test_hagrid.py if we wanted to—but a sensible name will make it easier for other people to find things in our code.

Every Nose test module should contain the following:

• Statements to import Nose and the module to be tested

• Functions that actually test our module

• A function call to trigger execution of those test functions

Like the name of the test module, the names of the test functions must start with test_. Using the structure outlined earlier, our first sketch of a testing module looks like this:

Download modules/structure.py

import nose import temperature def test_to_celsiusO:

'''Test function for to_celsius''' pass # we'll fill this in later def test_above_freezingO:

'''Test function for above_freezing.''' pass # we'll fill this in too if __name__ == '__main__': nose.runmoduleO

In the red-green-blue (or RGB) color system, each pixel in a picture has a certain amount of the three primary colors in it, and each color component is specified by a number in the range 0-255 (which is the range of numbers that can be represented in a single 8-bit byte).

By tradition, RGB values are represented in hexadecimal, or base-16, rather than in the usual base-10 decimal system. The "digits" in hexadecimal are the usual 0-9, plus the letters A-F (or a-f). This means that the number after 916 is not 10i6, but A16; the number after A16 is B16, and so on, up to F16, which is followed by 1016. Counting continues to 1F16, which is followed by 2016, and so on, up to FF16 (which is 15i0x16i0 + 15i0, or 255i0).

An RGB color is therefore six hexadecimal digits: two for red, two for green, and two for blue. Black is therefore #000000 (no color of any kind), while white is #FFFFFF (all colors saturated), and #008080 is a bluish-green (no red, half-strength green, half-strength blue).

For now, each test function contains nothing except a docstring and a pass statement. As the name suggests, this does nothing—it's just a placeholder to remind ourselves that we need to come back and write some more code.

If you run the test module, the output starts with two dots to say that two tests have run successfully. (If a test fails, Nose prints an "F" instead to attract attention to the problem.) The summary after the dashed line tells us that Nose found and ran two tests, that it took less than a millisecond to do so, and that everything was OK:

Download modules/structure.out

Ran 2 tests in 0.000s OK

Two successful tests isn't surprising, since our functions don't actually test anything yet. The next step is to fill them in so that they actually do something useful. The goal of testing is to confirm that our code works properly; for to_celsius, this means that given a value in Fahrenheit, the function produces the corresponding value in Celsius.

It's clearly not practical to try every possible value—after all, there are a lot of real numbers. Instead, we select a few representative values and make sure the function does the right thing for them.

For example, let's make sure that the round-off version of to_celsius from Section 4.2, Providing Help, on page 59 returns the right result for two reference values: 32 Fahrenheit (0 Celsius) and 212 Fahrenheit (100 Celsius). Just to be on the safe side, we should also check a value that doesn't translate so neatly. For example, 100 Fahrenheit is 37.777... Celsius, so our function should return 38 (since it's rounding off).

We can execute each test by comparing the actual value returned by the function with the expected value that it's supposed to return. In this case, we use an assert statement to let Nose know that to_celsius(100) should be 38:

Download modules/assert.py

import nose from temp_with_doc import to_celsius def test_freezing():

'''Test freezing point.''' assert to_celsius(32) == 0

def test_boiling():

'''Test boiling point.''' assert to_celsius(212) == 100

def test_roundoff():

nose.runmodule()

When the code is executed, each test will have one of three outcomes:

• Pass. The actual value matches the expected value.

• Fail. The actual value is different from the expected value.

• Error. Something went wrong inside the test itself; in other words, the test code contains a bug. In this case, the test doesn't tell us anything about the system being tested.

Run the test module; the output should be as follows:

Download modules/outcome.out

Ran 3 tests in 0.002s OK

As before, the dots tell us that the tests are passing.

Just to prove that Nose is doing the right thing, let's compare to_celsius's result with 37.8 instead:

Download modules/assert2.py

import nose from temp_with_doc import to_celsius def test_to_celsius():

'''Test function for to_celsius''' assert to_celsius(100) == 37.8

nose.runmoduleO

This causes the test case to fail, so the dot corresponding to it is replaced by an "F," an error message is printed, and the number of failures is listed in place of OK:

Download modules/fail.out

FAIL: Test function for to_celsius

Traceback (most recent call last):

File "/python25/lib/site-packages/nose/case.py", line 202, in runTest self.test(*self.arg) File "assert2.py", line 6, in test_to_celsius assert to_celsius(100) == 37.8 AssertionError

Ran 1 test in 0.000s FAILED (fai1ures=1)

The error message tells us that the failure happened in test_to_celsius on line 6. That is helpful, but the reason for failure can be made even clearer by adding a description of what is being tested to each assert statement.

Download modules/assert3.py

import nose from temp_with_doc import to_celsius def test_to_celsius():

'''Test function for to_celsius'''

assert to_celsius(100) == 37.8, 'Returning an unrounded result'

nose.runmodule()

That message is then included in the output:

Download modules/fail_comment.out

FAIL: Test function for to_celsius

Traceback (most recent call last):

File "c:\Python25\Lib\site-packages\nose\case.py", line 202, in runTest self.test(*self.arg) File "assert3.py", line 6, in test_to_celsius assert to_celsius(100) == 37.8, 'Returning an unrounded result' AssertionError: Returning an unrounded result

Ran 1 test in 0.000s FAILED (failures=1)

Having tested test_to_celsius with one value, we need to decide whether any other test cases are needed. The description of that test case states that it is a positive value, which implies that we may also want to test our code with a value of 0 or a negative value. The real question is whether our code will behave differently for those values. Since all we're doing is some simple arithmetic, we probably don't need to bother; in future chapters, though, we will see functions that are complicated enough to need several tests each.

Let's move on to test_above_freezing. The function it is supposed to test, above_freezing, is supposed to return True for any temperature above freezing, so let's make sure it does the right thing for 89.4. We should also check that it does the right thing for a temperature below freezing, so we'll add a check for -42.

Finally, we should also test that the function does the right thing for the dividing case, when the temperature is exactly freezing. Values like this are often called boundary cases, since they lie on the boundary between

Style Notes M 76

two different possible behaviors of the function. Experience shows that boundary cases are much more likely to contain bugs than other cases, so it's always worth figuring out what they are and testing them.

The test module, including comments, is now complete:

Download modules/test_freezing.py

import nose from temp_with_doc import above_freezing def test_above_freezing():

'''Test function for above_freezing.'''

assert above_freezing(89.4), 'A temperature above freezing.' assert not above_freezing(-42), 'A temperature below freezing.' assert not above_freezing(0), 'A temperature at freezing.'

When we run it, its output is as follows:

Download modules/test_freezing.out

Ran 1 test in 0.000s OK

Whoops—Nose believes that only one test was run, even though there are three assert statements in the file. The reason is that as far as Nose is concerned, each function is one test. If some of those functions want to check several things, that's their business. The problem with this is that as soon as one assertion fails, Python stops executing the function it's in. As a result, if the first check in test_above_freezing failed, we wouldn't get any information from the ones after it. It is therefore generally a good idea to write lots of small test functions, each of which only checks a small number of things, rather than putting dozens of assertions in each function.

Was this article helpful?

0 0

Responses

  • COLOMBANO
    How to change the color of an individual pixel in a picture in python?
    4 years ago

Post a comment