Python for Fractal Generation

This post will get straight to the point- Python code for generating a fractal image using IFS (Iterated Function System). Python is a simple language for scripting. I use PyCharm for coding, but you can do it other ways.

import numpy as np
from PIL import Image
import math

#color parameter space according to escape time

'''
Smaller images will generate really fast. You can change the dimensions to 
thousands of pixels, but it will slow down the process.
'''
# width of image
width = 400
# height if image
height = 400
#number of color shades used
iterations = 16

'''
a, b, c, d define a square viewing window. The bigger the numbers, the smaller
the image in the window. They control scale of the image and general positioning.
Changing them moves center of the image and can warp the image.
'''

a = -2
b = -2
# bigger c and d values move image up
c = 2
d = 2


origin = [0, 0]

'''
If this number is too low you get an image with smoother and less fractal like
edges. Higher numbers also produce wider color bands.
'''
radius = 30  # defines a circular region in R2

#canvas is a coordinate space in R2. Third parameter is for color.
canvas = np.zeros((width, height, 3), dtype=np.uint8)
#fill canvas with a background color
canvas[0:width, 0:height] = [200, 0, 80]

def plot_points():
    # point p,q is on my canvas and colored based on escape time if it diverges
    for p in range(0, height):
        for q in range(0, width):
            ''' lambda point k, l in P in R2
            k and l are range bound by values of a,b,c,d
            code below scales a viewing window on the 2d canvas to keep in bounds.
            They are the starting point for the formula and cover each point in
            the 2d plane of size height and width but not at high precision.
            '''
            #print("k and l")
            k =  (a + ((c - a) * p/height))
            #print(k)
            l = (b + ((d - b) * q/width))
            #print(l)

            x = origin[0]
            y = origin[1]
            # this controls the number of color shades but increases run time
            for n in range (0, iterations):
                
                #get next value of the IFS.
                newx = x*x - y - k #Real - k
                newy = 2*x*y - y*y - l  #Imaginary - l
                #store new value for use in next iteration
                x = newx
                y = newy

                ''' less then radius fills in mandelbrot.
                greater than radius outlines mandelbrot. Note that
                distance is being computed from the origin.
                '''
                if (distance([x,y], origin) > radius):
                    '''
                    plot in color- convert and scale n to some shade of a color.
                    You can use any color algorithm you can imagine. n is a 
                    measure of how many steps it takes until the orbit escapes
                    outside the radius of the circle.
                    
                    color inversion:
                    (1 - n*colorScale/256)*256
                    '''
                    colorScale = 256/iterations
                    canvas[p, q] = [n*colorScale, 0,
                                    n*colorScale]
                    #print(n)
                    break

    #turn the data in the canvas array into an image and display it
    img = Image.fromarray(canvas, 'RGB')
    img.show()

def distance(a, b):
    #this is just the 2d distance formula
    return math.sqrt(math.pow(a[0] - b[0], 2) + math.pow(a[1] - b[1], 2))

if __name__ == '__main__':
    plot_points()

The Fractal Formula

Why this formula? It is the mandelbrot equation:

Zn+1 = Zn^2 + C where C is a constant value

Z = x + iy and is a complex number.

Squaring Z gives x^2 + 2ixy – y^2. For IFS approach, the formula is broken into real and imaginary coefficients. Those are the values being tested for divergence and shown below as newx and newy. Changing the formula will produce very crazy shapes you cannot get with other forms of math.

Crazy example 1

  • newx = 2*x*x – y*y – k #Real – k
  • newy = 4*x*x + 2*x*y – y – l  #Imaginary – l

Crazy example 2

  • newx = x*x – y*y – k #Real – k
  • newy = 2*x*y – y – l  #Imaginary – l

Crazy example 3

  • newx = x*x – y*y – x – k #Real – k
  • newy = 2*x*y – l  #Imaginary – l

Crazy example 4 (image below)

  • newx = x*x – y – k #Real – k
  • newy = 2*x*y – y*y – l  #Imaginary – l

Not all formulas will create a fractal!

More Variations

Values other than 0, 0 cause crazy distortions of the mandelbrot image. Changing the origin has a dramatic affect. You can make images that look like the map of a coast line. Best results have origin values between -1 and 1. Experiment by changing these and see what happens!

  • origin = [0.37, -0.3]
  • origin = [0.7, -0.6] #little numbers make big islands
  • origin = [-.1, .3]
  • origin = [0.3, .2]
  • origin = [-.5, .3]  l#ots of little islands off the big island
  • origin = [-1.3, .4] #just little islands
  • origin = [-1, -.8]  #nice islands
  • origin = [1, -.8] #changing the sign creates a mirror image (though not perfect!)
  • origin = [.5, -.8] # big island with little islands
  • origin = [.7, -.8] # big island with little islands
  • origin = [-.7, -.8]
  • origin = [1.5, 1.9] #empty image

Using more advanced techniques can generate truly amazing images like the one below. This was made with the Java code I use for creating images for my clothing and other prouct at https://skyefractal.com

If you like these articles go get some swag from Skyefractal!

Arduino Fractal Music Synth Part 2

Welcome to Part Two. I am adding features quickly at this point since most are software, my specialty, so they go pretty quick. In this installment I present the latest code and the addition of a single toggle switch. Have a listen to the latest features and read about the details and the code below.

FYI, there will be many parts to these development effort. The current phase is hardware and software prototyping. I will be adding articles on the creation and testing of typical synth components- VCA, envelope generator, filter, mixer, power, and finally assembly into a case. The case will be special- I am using a nice desktop PC case with a clear side panel. I have gutted it for other projects but the rest is intact and perfect for this.

Extra great news– I may have decided to make my Github Repo public earlier than planned. This will show everything going on, warts and all, across not just this project but many other Arduino experiments, like my speak and spell work.

Here is the repo link: https://github.com/mogrifier/arduino

What’s In a Name?

I disagree with Shakespeare. Names matter and I have named this synth

Grimoire

8 DEC Feature Update

Change Overview

There is no such thing as coding, only refactoring.

Erich Izdepski

I did quite a bit of refactoring of my code during this iteration. There was unnecessary use of global variables, and I renamed some things and added more comments. I learned some more about coding for Arduino and found out how to encapsulate the variables properly, which protects them from accidental modification.

In Arduino, you use the static keyword in a variable declaration to identify a variable that will keep its value each time the function defining it is called. This is very nice and solves a multitude of problems when working with many changing values in a big loop. I also pass variables by reference using the & symbol with function parameters. You can go to the git repo and look at the changes.

I made some other changes meant to track the state of all the switches the synth will have. It is more than is needed and I may change in the future. The concept was to use flags and bitmasks to simply store status of each switch and being able to read quickly. The approach ends up using more code and variables than I think is needed and for little benefit. State (as in a state machine) is worth storing. The approach is important and will make the code more maintainable. I think it worth looking at Arduino Classes for this. I also added a #define DEBUG 0 statement typical for including or excluding debug code from the compiled form. Arduino lets you use “if defined(your variable)” and will only compile the code between the if and endif if the variable is defined at the top of the file in a #define declaration. [Had to look this up since did it wrong the first time.]

The feature change is the addition of square wave vibrato. The relevant code commit diff is here. I added a toggle switch that selects between sine and square wave. It is very simple to implement and uses the Arduino internal pullup resistor on the digital pin. This means you simply connect one side of the switch to the digital pin in use and the other to ground. The pin, through the internal pullup resistor (see INPUT_PULLUP in code below), ensures stable readings by keeping the pin at 5v when the switch is open, and 0v when closed.

See the toggle switch connected between ground and digital pin 2.

The code.



/*
play tones. Arduino MEGA 1280.

This is creating really nice and usable sync-like tones. All with with output voice. 
Very worthwhile to put separate analog controls on the variables for freq, modRange, maxMod
and maybe loop delay.
This is a continuous player- run through FX for sure! 
duration should be less than loopdelay

*/
#define DEBUG 0   //uncomment line to turn on some debug statements

//digital pins for sensing switch position (don't use 0 or 1 since for serial data)
const int SPEAKERPIN = 9;
const int VIBRATOPIN = 2;
//analog pins for continuous sensors, like potentiometers or other sources of 1-5v
const int MODPIN = 0;

const int FREQ = 500;
const int LOOPDELAY = 15;
const int DURATION = 12;  //you can hear down to 11ms or so. combined with LOOPDELAY this creates extra sync sound
const float TWELFTHROOT = 1.05946;
const float LOWA = 27.5;

//these are not constant since will likely be changeable with a sensor input
int INC_SENSOR = 3;
int MAXMOD_SENSOR = 10;

/*
flag variable and flag names for state of synthesizer switches. I am using toggle switches. The switch state is
 captured in the software. This means the software must track each button press and update the state.
*/
volatile int synthState = 0; //all 16 flags clear
const int VIBRATO_FLAG = 0; //1st bit in the synth_state flag is for type of vibrato; default is SINE_VIBRATO
const int SINE_VIBRATO = 0;
const int SQUARE_VIBRATO = 1;

//arrays for holding IFS matrix
float a[4];
float b[4];
float c[4];
float d[4];
float e[4];
float f[4];

void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);

  //vibrato pin; I am using the internal pullup resistor to simplify the circuit
  pinMode(VIBRATOPIN, INPUT_PULLUP);  

  //initial IFS matrix data for a fern
  a[0] = 0;
  a[1] = 0.85;
  a[2] = 0.2;
  a[3] = -0.15;

  b[0] = 0;
  b[1] = 0.04;
  b[2] = -0.26;
  b[3] = 0.28;

  c[0] = 0;
  c[1] = -0.04;
  c[2] = 0.23;
  c[3] = 0.26;

  d[0] = 0.16;
  d[1] = 0.85;
  d[2] = 0.22;
  d[3] = 0.44;

  e[0] = 0;
  e[1] = 0;
  e[2] = 0;
  e[3] = 0;

  f[0] = 0;
  f[1] = 1.6;
  f[2] = 1.6;
  f[3] = 0.44;
}

void loop() {

  //some variable values need to be saved between calls to loop so declare them as static
  static int cycles;     //for calculating how long a note has played
  static int count;      //
  static int sineFreq;  //sine vibrato
  static int deltaFreq; //total vibrato to apply
  static int duration;   //how long the note will play (minimum)
  static int freq;
  static int square_vibrato_sign = 1;
  int squareFreq; //square vibrato- calculated, not saved
  // put your main code here, to run repeatedly:
  int sensor = analogRead(MODPIN);

  /*update flags by looking for button presses. I am using toggle switches. Debounce not an issue.
  How you do this depends on the type of switches you use. Momentary means use interrupt, toggle means poll.
  */  
  updateSynthState();

  if (cycles * DURATION >= duration) {
    //note has played for at least the amount of milliseconds specified so get a new note
    //reset cycle counter
    cycles = 0;
    //compute new freq and duration values
    compute_music(freq, duration);

  #if defined(DEBUG)
      char buffer[40];
      sprintf(buffer, "Pitch %d and duration %d", freq, duration);
      Serial.println(buffer);
  #endif
  }
  else {
    //use previous freq and duration- values is stored since variables are static, but the duration has to "count down"
    cycles += 1;
  }

  int modRange = map(sensor, 0, 1023, 5, 30);  //effectively control range and speed of vibrato
  //what should max value be? Need to tie better to freq - make a tunable parameter
  //Serial.print("modrange= ");
  //Serial.println(modRange);

  //programmable vibrato-like control. 
  count++;  
  if (count % modRange == 0) {
    //reset count (if you don't it would overflow after many hours)
    count = 0;
    if (bitRead(synthState, VIBRATO_FLAG) == SINE_VIBRATO) {
      //This creates a sinusoidal vibrato.    
      sineFreq += INC_SENSOR;
      if (sineFreq >= MAXMOD_SENSOR) {
        INC_SENSOR = -INC_SENSOR;
      }
      if (sineFreq <= -MAXMOD_SENSOR) {
        INC_SENSOR = -INC_SENSOR;
      }
      deltaFreq = sineFreq;
    }
    else {
      //flip the square wave vibrato sign
      square_vibrato_sign = -square_vibrato_sign;
      //this creates a square vibrato. deltaFreq oscillates between a positive and a negative value
      deltaFreq = square_vibrato_sign * (modRange + INC_SENSOR);
    }    
  }

  tone(SPEAKERPIN, freq + deltaFreq, DURATION);
  // long timeDelay = map(sensor, 0, 1023, 10, 20);
  delay(LOOPDELAY);
}


/*
Get the next pitch and duration from the IFS code. Just compute all as needed.
*/
void compute_music(int &freq, int &duration) {
  static int totalIterations;
  //starting point for IFS code. Store past and present for iteration.
  static float x;
  static float y;
  static float next_x;
  static float next_y;

  totalIterations += 1;
  int k = get_chance();
  next_x = a[k] * x + b[k] * y + e[k];
  next_y = c[k] * x + d[k] * y + f[k];
  x = next_x;
  y = next_y;

  //the next note to play is next_x with a duration of next_y

  //scale values so in bounds and make sense for pitch frequency and duration in milliseconds
  int scale_x = int(abs(x) * 100);
  if (scale_x > 100) {
    scale_x = 100;
  }
  //constrain the piano key range to one the arduino can play and also not too high since unpleasant
  int piano_key = map(scale_x, 0, 100, 25, 74);
  //y has a range up to 3.5 or so
  int scale_y = int(abs(y) * 600 + 400);

  //assign values to the variable references so changes are seen in the loop function
  freq = get_freq(piano_key);
  duration = scale_y;

  if (totalIterations > 100) {
    //reset to new starting point for iteration
    init_xy(x, y);
    totalIterations = 0;
  }
}

/*
Choose array indices based on a hard-coded probability distribution.
*/
int get_chance() {
  float r = (float)random(1, 100) / 100;
  if (r <= 0.1)
    return 0;
  if (r <= 0.2)
    return 1;
  if (r <= 0.4)
    return 2;
  else
    return 3;
}

void init_xy(float &x, float &y) {
  x = (float)random(1, 100) / 100;
  y = (float)random(1, 100) / 100;
}

/*
Convert the piano key position (1 to 88) to the corresponding frequency
*/
int get_freq(int key) {
  int octave = (int)(key / 12);
  int note = (key % 12) - 1;
  float freq = LOWA * pow(2, octave) * pow(TWELFTHROOT, note);
  //round to nearest whole value
  return int(freq + 0.5);
}

/*
Get toggle button state and update the flag variable. This uses polling and is called in main loop.
*/
void updateSynthState() {
  //each flag is 1 or 0
  int val = digitalRead(VIBRATOPIN);

  #if defined(DEBUG)
    if (val != bitRead(synthState, VIBRATO_FLAG)) {
      //value changed
      Serial.println(synthState);    
    }
  #endif  
  
  bitSet(synthState, VIBRATO_FLAG) = val;
}


What are fractals?

A fractal is a type of image. They are known for intricate designs that contain nearly identical copies of the overall image that can be seen if you zoom in as with a microscope. This isn’t entirely accurate but helps explain the idea. What really happens is when you zoom in, you must redraw the image at the new scale using a computer to view the copies inside the fractal. A fractal is self-similar (more details). You can zoom in forever, in theory.

If you look closely at the image below, you will see the self-similar copies in the blue circles.

With the right computer software you can choose a region to explore, and redraw the fractal in the region but at a larger scale so you can see the detail revealed as you ‘zoom’ in.

How is this possible?

Time to blow your mind. Between any two numbers, 0 and 2 for example, there are an infinite amount of numbers. These numbers can be used by software to redraw a fractal (as you zoom in), but scale it up so visible on your computer screen. Scaling is needed since a pixel on your screen is just a pixel. There are no half pixels, so when you try to draw something that consists of points of data like [1.5, 0.7] you have to scale it to full pixels (whole numbers) to get an accurate drawing. There is some rounding and approximatioon that inevitably occurs but the images still come out nice.

What about those colors?

This gets tricky. The colors of a fractal image are chosen by the computer programmer. The fractal formula being graphed does not provide color, but does provide information that can be used to create colors that make sense and look beautiful. Here is the formula for the fractals at the top of this page:

Zn+1 = e^Zn * 0.38

Zn is the nth value of Z. The fractal calculations start with some Z value, Zn. The fomula calculates the next Z value, Zn+1. You feed that Z into the formula to get the next, Zn+2, and so on. This is call iteration. Z is special and very spooky. It is a complex number, meaning it has a compound form, x + iy, where i by definition is the square root of -1. The x and y values are simply the coordinates of a point in the 2d plane that you are using as the starting value for performing iteration on the fractal formula.

At this point you are probably thinking why don’t you just graph the value of the formula? What does iteration have to do with it? The formula defines a set of values that either diverge to infinity or not.

The image you see as a fractal is a colorization of whether a given starting point leads to divergence to infinity or not.

Colorization can take many forms- it can simply be black and white. It can choose the color based on how quickly infinity is approached. You can choose colors based on the coordinates of the starting point. You can choose them based on the time or anything you can imagine, as long as you make the decision based on the formula diverging or not diverging to infinity for a given starting point, otherwise you won’t be plotting a fractal.

Next

I don’t think there is a simple way to discuss fractals, but I have tried to keep the math to a minimum and talk more about the theory. The next post will go into techniques for evaluating and graphing the simplest fractal- the Mandelbrot Set. There are several options for generating fractals. You can use complex numbers or Iterated Functions Systems (see https://en.wikipedia.org/wiki/Iterated_function_system)

You can program them and graph the image in just about any language. I have used Python, Java, anbd Arduino C to generate fractal data. Using Java is especially powerful but it will require additional libraries from Apache to support arithmetic with Complex Numbers.

The image below was generated using Iterated Function Systems and Python. Buy the shirt at https://www.skyefractal.com/listing/new-fractal-love?product=11

Read ahead!

Fractals and LEGO bricks

Did you know that you can convert a picture into a LEGO Mosaic? You can do this easily with Bricklink Studio (also some Python software on github). It is free and approved by LEGO for making 3D designs, instruction booklets, parts lists, etc. It is available from https://www.bricklink.com/v3/studio/download.page

I created a model containing over 50,000 LEGO tiles (the smooth pieces) for my fractal art, “Sunrise over Ukraine,” using this technique.

The steps are really simple. Just choose Import Mosaic from the File menu. It is really a bad name, since it does NOT import a mosaic; instead, it CREATES a mosaic from an image you have.

You may run into problems if creating a mosaic with more than a few hundred bricks in each dimension (depends on how much memory your computer has). I am still testing because I plan to make them with millions of bricks so the fractals look really nice.

Online 3D LEGO Mosaic Model

You can explore my model using this link:

https://www.bricklink.com/v3/studio/design.page?idModel=360945

By the way, building this in the real world would be an expensive and time consuming operation, but a good team of people could do it.

Will You Help Ukraine?

I have made this art available on many products, and buying them supports refugees (75% of profits go to https://rescue.org). The products are available here: https://www.skyefractal.com/search?searchterm=ukraine

Sunrise Over Ukraine product line

I can put this on other products, too, so leave a comment if you want something special!