Diving Into the Deluge of Data :: Lab 6 :: Iterators and Art

Lab 6: Iterators and Art

This lab explores iterators, generators and direction-based visualizations. Our goal will be to produce images similar to those generated by Nadieh Bremer for π but for arbitrary irrational numbers. Our focus will be on e so we will also write a python iterator to generate digits of e and a python iterator to translate these digits into coordinates. Our visualization code will reinforce concepts in object-oriented programming that we've seen previously this semester, including encapsulation and inheritance.

Here are some example images for e using Nadieh's techniques:

             

Step 0: Lab Preparation

Step 1: Source Code

Step 2: An Iterator for e

In the file called iterators.py, write a generator called e_iterator with a single parameter, which is the number of digits of e that it should produce. Notes from Lecture 19 should help. Test your function in the REPL:

  >>> from iterators import e_iterator
  >>> [x for x in e_iterator(10)]
  [2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
  >>>

Step 3: An Iterator for points

In the same file iterators.py, write a generator called to_points(iterator, distinct_values) that has two parameters:

This iterator should, starting at (0,0), produce a new point of length 1 away from the previous point in the direction corresponding to the digit. The directions should start at 0 (corresponding to an angle of π/2 radians) and continue clockwise by 2π/N radians. You should make generous use of math.pi, math.sin, and math.cos. Example directions are shown below for N=4 and N=10 respectively.

There is one subtle difference between how we and Bremer associate points with digits. Bremer associates her first point with her first digit (3 corresponds to (0.0) in her π example) while we associate our first point with the step relative to (0,0) (2 corresponds to (0.95,0.33)). I perfer this because the visualization does not lose any information whereas associating the first digit with (0,0) means you can never recover the first digit from the image. Test your code in the REPL:

  >>> from iterators import e_iterator, to_points
  >>> [x for x in to_points(e_iterator(3),10)]
  [(0.9510565162951535, 0.3090169943749474), (0.0, -1.1102230246251565e-16), (0.5877852522924732, 0.8090169943749472)]

Step 4: VizImage Base Class

In viz.py you will find code stubs for a VizImage class. This class takes care of extracting a bounding box around the points in points, interpolating a point in the bounding box to a point in an image of dimensions width × height, creating and saving an image of appropriate dimensions, and drawing the path corresponding to points onto the image.

  class VizImage:

      def __init__(self, points, width, height):

      def _interpolate(self, point):

      def _draw(self, im):

      def draw(self, filename):
          im = Image.new("RGB", (self._width, self._height), getrgb("WHITE"))
          self._draw(im)
          im.save(filename, "PNG")

Some implementation notes:

The following code
  >>> viz = VizImage([x for x in to_points(e_iterator(100),10)], 2048,2048)
  >>> viz.draw("e100.png")
should produce the following image

Step 5: DirectionVizImage Subclass

You should define a class called DirectionVizImage that is similar to VizImage except that it requires an additioonal list parameter directions, which is identical in length to points and corresponds to the compass direction of each point relative to the previous point.

The class also makes use of color ranges, provided for you in color.py. This package provides one function called color(n) that returns a list of n colors in the same dark rainbox range used by Nadieh Bremer.

  class DirectionVizImage(VizImage):

    def __init__(self, points, directions, n, width, height):
        super().__init__(points, width, height)
        self._directions = directions
        self._colors = colors(n)

    def _draw(self, im):

Creatiing an instance of DirectionVizImage in the REPL and saving the output to a file

  d = DirectionVizImage([x for x in to_points(e_iterator(1000),10)],
                        [digit for digit in e_iterator(1000)],
                        10, 2048, 2048)
  d.draw('e1000.png')

should produce the following image.

Step 6: LengthVizImage Subclass

You should implement a class called LengthVizImage, which inherits from VizImage. Creating an instance of this class requires three arguments: points, width, and height.

Your implementation should use 10 colors to draw the lines, changing to the next color after processing an additioanl 10% of the lines. Here is what your image should look like for the first 10000 digits of e

Step 7: Submission