Lab 11: Image Processing

Purpose: to practice nested lists as a data structure and nested for loops as a way to process them.

Overview

In this lab, you will build a simple image processing app that applies various transformations to images.

Setup

This lab builds on what you learned last week about Brython. We’re adding a new tool called radiant (GitHub page) that makes it easier to write Brython apps. To install it, select Tools > Manage packages in Thonny, search for radiant-runtime-bridge, and click Install.

Download these files into a new lab11 folder and update the header documentation in image_processing.py as usual.

Run image_app.py in Thonny. A browser tab will open automatically.

Familiarize yourself with the interface. You can get a source image in three ways: Capture a photo from your webcam, click Use Sample Image to load the included photo, or use the file chooser to upload any image from your computer. Once you have a source image, click Process to apply an operation to it.

Read through image_processing.py and skim image_app.pyimage_processing.py contains the image processing logic (your main focus) and image_app.py contains the user interface. You don’t need to read image_helpers.py.

Look carefully at how brighten works in image_processing.py. It uses nested for loops to visit every pixel and build a new image. Each pixel is a list [red, green, blue] where each value is 0–255.

Task 1: Add a darken function

Darkened (4×) Original Brightened (4×)
darkened original brightened

Task 2: Fix flip_horizontal


For each new function you add below, you’ll also need to wire it up in image_app.py: add its name to the operations list in __init__ (# ── ①) and add a matching elif branch in on_process (# ── ②).

Task 3: Refactoring practice — volume control

Before touching the image code, let’s practice a useful technique on a simpler example.

Here are two functions that adjust volume on a 0–100 scale:

def louder(volume):
    return min(100, volume + 10)

def softer(volume):
    return max(0, volume - 10)

These two functions are almost identical — the only difference is + 10 vs. - 10, and min vs max. Duplicated code like this is a problem: if you ever need to change the logic (say, changing the range to 0–200), you’d have to remember to change it in both places. A better design factors out the difference as a parameter.

Task 4: Refactor brighten and darken

Look at your brighten and darken functions in image_processing.py. They have the same duplication problem as louder/softer — one adds 25, the other subtracts 25, but otherwise they’re identical.

Task 5: New image processing functions

Optional extensions

If you finish early, try one or more of these.

Snow: write a snow(image) function that replaces every pixel with a random [r, g, b] color. You’ll need from random import randint.

Sepia: sepia toning gives images a warm brownish antique look. Each output channel is a weighted sum of the input channels:

Output Formula
Red int(0.393*r + 0.769*g + 0.189*b)
Green int(0.349*r + 0.686*g + 0.168*b)
Blue int(0.272*r + 0.534*g + 0.131*b)

Use min(255, ...) to clamp values that exceed 255.

Submission

Submit image_processing.py and image_app.py on Moodle. (No need to submit image_helpers.py since you didn’t edit it.)