Project: send a picture

Capture a camera frame, make it 1-bit, compress it with RLE and send it by radio.

F8.10Data representationGCSE25 min

Do this lesson in the simulator

A team of robots is exploring. One robot sees something interesting and wants to show the others, but the radio carries at most 200 characters in a message, and a picture is hundreds of pixels. In this project BugBot captures what it sees, turns it into a black and white picture, compresses it with run length encoding until it fits, and sends it. Every idea in F8 is in here: pixels, colour depth, file size, and compression.

The brief

The robot captures a 32 × 24 camera image and converts it to 1-bit black and white: # for a pixel whose average of red, green and blue is below 128, . otherwise. It compresses each row with RLE, joins the rows with /, and checks the whole message fits in 200 characters. If it fits, it sends it with send(); if not, it prints too big and sends nothing. It reports the uncompressed and compressed sizes, and proves the compression is lossless by decoding the message back into the picture.

The program: capture the camera image, turn it into # and . , compress each row and join them with /, decode it to check nothing was lost, then send it if it fits in 200 characters or print too bigcamera_image(32, 24)32 × 24 pixels, 3 bytesstep 11-bit picture# if average < 128, else .step 1rle_encode each rowjoin the rows with /step 2rle_decode each partsame as the picture?step 3lossless: Truelen(message)≤ 200?step 4yessend(message)over the radionoprint("too big")and send nothing
Steps 1 to 4 of the project in order. The check in step 3 is the proof the compression is lossless, and the only way out to the radio is through the check on the length.

Step 1: capture and convert

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

img = camera_image(32, 24)
picture = []
for row in img:
    line = ""
    for r, g, b in row:
        line = line + ("#" if (r + g + b) / 3 < 128 else ".")
    picture.append(line)

for line in picture:
    print(line)
print("uncompressed:", 32 * 24, "bits")

Run this in the simulator

The picture is now a list of 24 strings, one per row: a 1-bit image, stored as characters.

Step 2: compress it

Use rle_encode from lesson F8.9 on every row, and join them with / so the receiver knows where each row ends. The / is the metadata the receiver needs to rebuild the rows.

message = "/".join(rle_encode(line) for line in picture)

"/".join(...) puts a / between the encoded rows. Print the message and its len. A plain view of the mat compresses very well, because most rows are one long run.

Step 3: decode and check

The receiver splits the message at / and decodes each part. If the decoded picture is exactly the original, the compression was lossless:

rows = [rle_decode(part) for part in message.split("/")]
print("lossless:", rows == picture)

Step 4: send it

if len(message) <= 200:
    send(message)
else:
    print("too big")

From where the robot starts, the message is 111 characters: 21 of the 24 rows are the single run 32.. Try moving before capturing: after forward(30, distance=15) the balls and the wall are bigger and more broken up, and the message grows to 121 characters. Every change between # and . in a row adds to the message, so a picture with enough detail would pass 200 characters and not fit: the more detail in a picture, the less RLE can squeeze it.

The 1-bit picture is 768 characters as it is. Compressed it is 111 characters from where the robot starts and 121 after it drives forward, both under the radio's 200the picture as it is768 characters: too bigcompressed at the start111 characters: fitscompressed after driving121 characters: fitsthe radio's limit: 2000200400600768characters
Anything left of the dashed line fits in one message. Most rows of the robot's view are one long run, so compression takes the picture from far too big to comfortably small. Every extra change from one character to the other in a row pushes the bar to the right.

Task: send a picture

Build the program from the brief. Print the 24 picture lines, then uncompressed: 768 bits (calculated), compressed: <n> characters, and lossless: True. Send the message if it has 200 characters or fewer.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

img = camera_image(32, 24)

Challenges

  1. Compress the whole picture as one string instead of row by row. Which is shorter, and why?
  2. Send a 64 × 48 picture by splitting its message into several messages of 200 characters each, numbered so they can be put back in order.
  3. Invent a way to make the message shorter still, for example by writing a row that repeats the one above it as =.