Lab 4: Visualizing Data Representations
In this lab we will explore three ways to represent a sequence of integers. We will also create data visualizations for each of these integer codes:
- binary
- Gray codes
- anti-Gray codes
Though the details of these encodings are interesting topics in themselves, the true goals of this lab are to build our expertise with Python lists and with the pillow image library. So please consult your notes and lecture handouts for the details of the algorithms, and carefully review the Python list documentation.
Step 0: Lab Preparation
Step 1: Source Code
-
Clone your private repo to an appropriate directory
in your home folder (
~/labs
is a good choice):$ git clone https://github.com/williams-cs/<git-username>-lab4.git
Remember, you can always get the repo address by using the https copy-to-clipboard link on github. -
Change into your <git-username>-lab4 directory, with
$ cd <git-username>-lab3
and create a virtual environment using$ virtualenv --system-site-packages -p python3 venv
-
Activate your environment by typing:
$ . venv/bin/activate
-
Use pip to install the pillow imaging library:
$ pip install pillow
- Remember that you must always activate your virtual environment when opening a new terminal
- Type
$ git branch
and notice that you are currently editing the master branch. - Create a new branch with
$ git branch codes
This branch is represents a split from the master branch. - Checkout this branch by typing
$ git checkout codes
- Any changes you make to the repository are now isolated on this branch.
Step 2: Representing Numbers
The file convert.py contains a function header
for decimal_to_binary_digits
.
Implement this functions. Each list element should either be a
int(0)
or int(1)
, and
the list elements should be ordered from the
most significant to the least significant digit.
def decimal_to_binary_digits(n, d): """ Given a number of digits 'n' and a decimal number 'd' in the range [0, 2^n), outputs a list of digits in the binary representation of 'd' """
You can always test your functions out by
running python
in interpreter mode and typing
>>> import convert >>> convert.decimal_to_binary_digits(3, 1) [0, 0, 1] >>> convert.decimal_to_binary_digits(3, 4) [1, 0, 0] >>> convert.decimal_to_binary_digits(3, 7) [1, 1, 1]
Step 3: Representing Sequences of Numbers
Please implement the binary_code
function, which
returns the first 2^n non-negative integers in binary.
Here binary means a list of 0s and 1s.
def binary_code(n): """ returns a list of the first 2^n numbers in binary. In other words, this returns a list of lists of 0s and 1s. """
>>> import convert >>> convert.binary_code(4) [[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 0, 1, 1], [0, 1, 0, 0], [0, 1, 0, 1], [0, 1, 1, 0], [0, 1, 1, 1], [1, 0, 0, 0], [1, 0, 0, 1], [1, 0, 1, 0], [1, 0, 1, 1], [1, 1, 0, 0], [1, 1, 0, 1], [1, 1, 1, 0], [1, 1, 1, 1]]
Recall from class that we can also represent an integer sequence
using a Gray (or anti-Gray) code. Here we will implement the
functions: gray_code
and anti_gray_code
.
A Gray code is any code where the representation of consecutive integers differs in exactly one bit. In class, we explored an algorithm for binary-reflected Gray codes. There are more details in the lecture slides, but we have repeated the algorithm here for convenience.
We can build an (n+1)-bit Gray code from an n-bit Gray code using the following steps:
- Copy the sequence (creating an original and a copy)
- Reverse the order of the elements in the copy sequence (hence the name binary-reflected Gray code)
- Prefix each element in the original sequence with a 0
- Prefix each element in the reflected copy with a 1
- Combine the original sequence and the copy sequence
If we start with the Gray code for 1-bit, we can iteratively build a Gray code of any size by repeating these steps. The base case is: [[0], [1]].
You may find the copy.deepcopy
,
function useful. When you copy a Python list that contains other list objects,
the nested list objects do not get copied—the nested lists inside the copied
list are aliases to the nested lists in the original list.
copy.deepcopy
copies a list and all of its elements
(and any of those elements' elements and so on). Please sketch
out your solution on paper first!
def gray_code(n): """ returns a list of the first 2^n elements in a binary-reflected Gray code. In other words, this returns a list of lists of 0s and 1s. """
>>> import convert >>> convert.gray_code(4) [[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 1], [0, 0, 1, 0], [0, 1, 1, 0], [0, 1, 1, 1], [0, 1, 0, 1], [0, 1, 0, 0], [1, 1, 0, 0], [1, 1, 0, 1], [1, 1, 1, 1], [1, 1, 1, 0], [1, 0, 1, 0], [1, 0, 1, 1], [1, 0, 0, 1], [1, 0, 0, 0]]
We can think of a Gray code as a sequence with minimal changes between consecutive elements. The opposite of a Gray code, then, is a sequence where consecutive elements differ in the maximal number of digits. We call this an anti-Gray code.
To construct an anti-Gray code of n bits, we can:
- Construct a Gray code of n-1 bits. Call this original
- Append 0 to each element in original
- Then, for each element in original, we would:
- copy the original element to create a copy element
- flip every bit in copy element (make each 0 a 1, make each 1 a 0)
- append the original element and the copy element to our result list
Why does this work? We are taking a sequence where each element differs in one place (minimal difference), and interleaving it with elements that differ in every place (maximal difference). As a result, every element is maximally different with its two neighbors (which are minimally different from each other).
The next task is to write a function that creates an anti-Gray code. (Since we generate an anti-Gray code of n bits from a Gray code of n-1 bits, it might be useful to call your Gray code function from within your anti-Gray code function.) Please sketch out your solution on paper first!
def anti_gray_code(n): """ returns a list of the first 2^n elements in a binary-reflected anti-Gray code. In other words, this returns a list of lists of 0s and 1s. """
>>> import convert >>> convert.anti_gray_code(4) [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 1, 0], [1, 1, 0, 1], [0, 1, 1, 0], [1, 0, 0, 1], [0, 1, 0, 0], [1, 0, 1, 1], [1, 1, 0, 0], [0, 0, 1, 1], [1, 1, 1, 0], [0, 0, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 0, 0], [0, 1, 1, 1]]
Step 4: Making Images
Included in this week's source code is a module called image.py that provides a simple interface for creating images, drawing points and squares, and saving images.
from PIL import Image, ImageDraw def create_image(width, height): return Image.new("RGB", (width, height), (255, 255, 255)) def draw_point(image, x, y, color): ImageDraw.Draw(image).point((x,y), color) def draw_square(image, x, y, w, color): ImageDraw.Draw(image).rectangle([x,y,x+w,y+w], color) def save_image(image, filename): image.save(filename, "PNG")
We can use the above interface to create images. For example, here's a 100x100 pixel image checkered by 20-pixel wide squares. Note that the coordinate system for images runs from top-left (0,0) to bottom-right (100,100). Colors are just 3-tuples of RGB (RED, GREEN, and BLUE) values that each range from 0 to 255.
BLACK = (0, 0, 0) WHITE = (255, 255, 255) cols = [BLACK, WHITE] w = 20 im = create_image(100,100) for x in range(0, 100, w): for y in range(0, 100, w): ## alternate between black and white colors cols.reverse() draw_square(im, x, y, w, cols[0]) save_image(im, "checkered.png")

We can also change the dimensions of our images, as well as the size and color of the boxes that we draw. For example, instead of a 100x100 pixel image, the code below generates a 200x150 pixel image, and it draws 25-pixel wide squares.
You can think of this process in the same way as our patterns in Lab 2. We know the size of our image, and as a result we know how many squares can fit in any row or column. So we walk through all of the points that we want to draw a square, and at each point we decide what color we want to make that square.
GRAY = (100, 100, 100) ORANGE = (255, 87, 51) colors = [GRAY, ORANGE] img_height = 200 img_width = 125 sq_width = 25 rows = img_height // sq_width cols = img_width // sq_width im = create_image(img_width, img_height) for y in range(rows): for x in range(cols): draw_square(im, x*sq_width, y*sq_width, sq_width, colors[(y+x) % 2]) save_image(im, "checkered2.png")

Step 5: Visualizing Integer Sequences
Examine the source code in draw.py. There are three constants declared at the top of the file.
- SWATCH_WIDTH determines the dimensions of each swatch (i.e., square) of the visualization;
- ZERO_COLOR determines the color for the 0 bits; and
- ONE_COLOR determines the color for the 1 bits.
- Create an image with dimensions WIDTH x HEIGHT using the image module we provided you. Remember that WIDTH and HEIGHT should be a function of SWATCH_WIDTH, n, and 2^n.
-
For each binary list in the 2**n-element list
code_sequence, draw its representation. The
variable code_sequence is either a binary, Gray, or
anti-Gray code generated by the functions you developed earlier.
Specifically:
- Draw a row of SWATCH_WIDTH x SWATCH_WIDTH squares to visualize the values of the bits for each integer representation in your code sequence.
-
If the value of the bit is a
0
, fill the square with ZERO_COLOR. -
If the value of the bit is a
1
, fill the square with ONE_COLOR. - In the images below, you'll notice that our squares have an outline. This outline is actually just the background color, so when you draw the square it will actually have dimensions SWATCH_WIDTH-2 x SWATCH_WIDTH-2 and it will be drawn offset by 1 pixel.
- Save the resuling image as filename
def draw_code(filename, code_sequence, n): """ Generate an image that represents a sequence of encoded integers. 'code_sequence' is a list containing 2**n elements. Each element of 'code_sequence' is a list of 'n' digits This image represents 'code' by coloring square swatches to represent the digits of each element in the sequence. """
You should be able to run
$ python draw.py 4
and produce the images below.
Step 6: Submission
- Make sure to commit your changes using
$ git commit -a -m " some comment "
- Push your changes back to github repo:
$ git push
- When you're ready to submit this code to us, issue a pull request by going to githb and clicking on the green Compare & pull request button.