Diving Into the Deluge of Data :: Lab 4 :: Visualizing Data Representations

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:

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

  • Please review the material from Lecture 8 on lists and from Lecture 10 on binary.
    • The code examples from class will be helpful

Step 1: Source Code

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:

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:

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.

Additionally, one function draw_code, is incomplete. The draw_code function should do the following:

    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.

Binary Encoding

Binary Sequence

Gray Code

Gray Code Sequence

Anti-Gray Code

Anti-Gray Code Sequence

Step 6: Submission