GenerateMe

tutorials, explanations, etc…

Grow your own iris

Iris

(To be honest I have no idea how to call this organic object).

In this article I describe how to achieve such effect. Generally speaking it’s really simple particle system with also simple attract/repel model based on love/hate attributes. This is step-by-step tutorial.

Particles

Let’s build first our basis – particles. What we need are:

  • current position
  • current velocity
  • age – we want them to die sometimes
  • color
  • id – to distinguish them

And of course something to init, update and draw. First we draw them on the distorted circle. Behaviour (interactions and movement) will be the next step and will be calculated externally.

// number of particles
final static int N = 500;

// colors (some of them taken from http://www.colourlovers.com/pattern/5526827/bleak
final static color[] cols = { #FF8A00, #FFD200, #749D9D, #FCF5B3, #B39500, #272429 };

// collection of all particles
ArrayList<Particle> particles = new ArrayList<Particle>(N);

void setup() {
  size(800,800);
  noFill();
  smooth(8);
  strokeWeight(2.0); // exaggerate a little bit for a while
  background(0,0,20);
 
  // initialize particles
  for(int i=0;i<N;i++) {
   particles.add( new Particle(i) );
  }
}

void draw() {
  for(Particle p : particles) {
    p.draw();
  }
}

class Particle {
  // position
  float x,y;
  // step
  float dx, dy;
  // id
  int id;
  // life length
  float age;
  // some random color
  color c = cols[(int)random(cols.length)];
 
  void reset() {
    // distribute initial point on the ring, more near the outer edge, distorted
    float angle = random(TWO_PI);
    float r = 5.0*randomGaussian() + (width/2-100)*(1.0-pow(random(1.0),7.0));
    x = cos(angle)*r;
    y = sin(angle)*r;
 
    // set random age
    age = (int)random(100,1000);
  }
 
  void draw() {
   stroke(c);
   point(x+width/2,y+height/2);
  }
 
  // update position with externally calculated speed
  // check also age
  void update() {
    x += dx;
    y += dy;
    if(--age < 0) reset();
  }
 
  Particle(int i) {
    id = i;
    reset();
  }
}

Here is the initial result

000032210713

Behaviour

The most important part, how we move them. Here is the only rule.

For every particle pair: if they love each other attract, reppel otherwise. That’s all.

Ok, detail is: what can be love/hate factor?

Basic love factor

The most obvious is distance. And this one is used here. Formula is love=\frac{1}{distance}

  1. If distance is larger than 2.0 attract with love amount
  2. Repel otherwise

Let’s see this.

// number of particles
final static int N = 500;

// colors (some of them taken from http://www.colourlovers.com/pattern/5526827/bleak
final static color[] cols = { 
  #FF8A00, #FFD200, #749D9D, #FCF5B3, #B39500, #272429
};

// collection of all particles
ArrayList<Particle> particles = new ArrayList<Particle>(N);

void setup() {
  size(800, 800);
  noFill();
  smooth(8);
  strokeWeight(0.7);
  background(0, 0, 20);

  // initialize particles
  for (int i=0; i<N; i++) {
    particles.add( new Particle(i) );
  }
}

void draw() {
  for (Particle p : particles) {

    // love/hate vector
    float lovex = 0.0;
    float lovey = 0.0;

    for (Particle o : particles) {
      // do not compare with yourself
      if (p.id != o.id) {
        // calculate vector to get distance and direction
        PVector v = new PVector(o.x-p.x, o.y-p.y);
        float distance = v.mag();
        float angle = v.heading();

        // love!
        float love = 1.0 / distance;
        // or hate...
        if (distance<2.0) love = -love;

        // not too fast!
        love *= 0.3;

        // update love vector
        lovex += love * cos(angle);
        lovey += love * sin(angle);
      } 

      // calculated love vector will be our speed in resultant direction
      p.dx = lovex;
      p.dy = lovey;
    }
  }

  // update and draw
  for (Particle p : particles) {
   p.update();
   p.draw();
  }
}

class Particle {
  // position
  float x, y;
  // velocity
  float dx, dy;
  // id
  int id;
  // life length
  float age;
  // some random color
  color c = cols[(int)random(cols.length)];

  void reset() {
    // distribute initial point on the ring, more near the outer edge, distorted
    float angle = random(TWO_PI);
    float r = 5.0*randomGaussian() + (width/2-100)*(1.0-pow(random(1.0), 7.0));
    x = cos(angle)*r;
    y = sin(angle)*r;

    // set random age
    age = (int)random(100, 1000);
  }

  void draw() {
    stroke(c,50);
    point(x+width/2, y+height/2);
  }

  // update position with externally calculated speed
  // check also age
  void update() {
    x += dx;
    y += dy;
    if (--age < 0) reset();
  }

  Particle(int i) {
    id = i;
    reset();
  }
}

00004fab0781

Nice, we have branching and organic look already. Our particles attract mostly, repel only when distance is low.

Attribute love factor

Now let’s introduce another attribute of our particle. It will be nonnegative floating point value . Let’s call it mood. Rule:

Our particles love each other when they have similar mood. Hate otherwise.

That means that we have to calculate similarity factor between moods to get love factor. The similarity should be between (-1, 1). Where 1 means 100% love and -1 means 100% hate. Our factor can be calculated with such formula (or something like that):

(abs(mood_1 - mood_2) - 0.5 * mood_{max})*\frac{2.0}{mood_{max}}

One note: don’t be strict with similarity factor, some unbalance between love and hate gives different results.

What can be mood?

  • inital angle
  • noise value from current position and time (mood changes while particle moves and time goes on)
  • some vector field
  • etc…

The code for second case as follows

// number of particles
final static int N = 500;

// colors (some of them taken from http://www.colourlovers.com/pattern/5526827/bleak
final static color[] cols = { 
  #FF8A00, #FFD200, #749D9D, #FCF5B3, #B39500, #272429
};

// collection of all particles
ArrayList<Particle> particles = new ArrayList<Particle>(N);

void setup() {
  size(800, 800);
  noFill();
  smooth(8);
  strokeWeight(0.7);
  background(0, 0, 20);

  // initialize particles
  for (int i=0; i<N; i++) {
    particles.add( new Particle(i) );
  }
}

float time = 0.0;
void draw() {
  for (Particle p : particles) {

    // love/hate vector
    float lovex = 0.0;
    float lovey = 0.0;

    for (Particle o : particles) {
      // do not compare with yourself
      if (p.id != o.id) {
        // calculate vector to get distance and direction
        PVector v = new PVector(o.x-p.x, o.y-p.y);
        float distance = v.mag();
        float angle = v.heading();

        // love!
        float love = 1.0 / distance;
        // or hate...
        if (distance<2.0) love = -love;

        // mood factor
        love *= p.moodSimilarity(o);
        // not too fast!
        love *= 0.5;

        // update love vector
        lovex += love * cos(angle);
        lovey += love * sin(angle);
      } 

      // calculated love vector will be our speed in resultant direction
      p.dx = lovex;
      p.dy = lovey;
    }
  }

  // update and draw
  for (Particle p : particles) {
    p.update();
    p.draw();
  }
 
  time += 0.001;
}

class Particle {
  // position
  float x, y;
  // velocity
  float dx, dy;
  // id
  int id;
  // life length
  float age;
  // some random color
  color c = cols[(int)random(cols.length)];
 
  // mood factor
  float mood;

  void reset() {
    // distribute initial point on the ring, more near the outer edge, distorted
    float angle = random(TWO_PI);
    float r = 5.0*randomGaussian() + (width/2-100)*(1.0-pow(random(1.0), 7.0));
    x = cos(angle)*r;
    y = sin(angle)*r;
    // set random age
    age = (int)random(100, 2000);
    calcMood();
  }

  void draw() {
    stroke(c,50);
    point(x+width/2, y+height/2);
  }

  // update position with externally calculated speed
  // check also age
  void update() {
    if(--age < 0) {
      reset();
    } else {
      x += dx;
      y += dy;
      calcMood();
    }
  }

  Particle(int i) {
    id = i;
    reset();
  }
 
  // compare moods
  float moodSimilarity(Particle p) {
    return 1.0-abs(p.mood-this.mood);
  }
 
  // calculate current mood
  private void calcMood() {
    mood = sin(noise(x/10.0,y/10.0,time)*TWO_PI); 
  }
 }

000054671590

What’s next?

You can mess with several factors in this small model, eg:

  • number of particles (beware, it’s O(n^2))
  • particle size, colors, alpha
  • dinstance love/hate formulas (eg. hate distance)
  • love amount (“not too fast!” comment)
  • time speed
  • initial position and age
  • moodSimilarity and calcMood functions

Here are some examples:

 

Influences

Here are some links which guided me to this concept:

Left to explore more deeply: random growth guided by vector field.

Enjoy!

00008c5c01298

Curvature from noise

spr00F52545

In last article I’ve made the research about vector fields and movement based on it. This time I’m going to treat values from noise (and other functions) as acceleration.

Instead of reading an angle from noise and apply step to my drawing agents I’ll be changing angle itself and then use it in drawing step.

The code

This time drawing code looks like this. Marked line is responsible for modification of the angle.

//drawing agent
class Agent {
  PVector pos; // position of the agent
  float angle; // current angle of the agent

  void update() {
    // modify position using current angle
    pos.x += cos(angle);
    pos.y += sin(angle);

    // get point coordinates
    float xx = map(pos.x, 0, width, -1, 1);
    float yy = map(pos.y, 0, height, -1, 1);

    PVector v = new PVector(xx, yy);

    // modify an angle using noise information
    angle += map( noise(v.x, v.y), 0, 1, -1, 1);
  }
}

// all agents in the list
ArrayList<Agent> agents = new ArrayList<Agent>();

void setup() {
  size(800, 800);
  background(240);
  stroke(20, 10);
  smooth(8);
  strokeWeight(0.7);

  // initialize in random positions
  for (int i=0; i<5000; i++) {
    Agent a = new Agent();
    float posx = random(200, 600);
    float posy = random(200, 600);
    a.pos = new PVector(posx, posy);
    a.angle = random(TWO_PI);
    agents.add(a);
  }
}

float time = 0;
void draw() {
  for (Agent a : agents) {
    pushMatrix();
    // position
    translate(a.pos.x, a.pos.y);
    // rotate
    rotate(a.angle);
    // paint
    point(0, 0);
    popMatrix();
    // update
    a.update();
  }
  time += 0.001;
}

spr002CFCD8

Scaling

After scaling and shifting noise I got more interesing patterns.

void update() {
  // modify position using current angle
  pos.x += cos(angle);
  pos.y += sin(angle);

  // get point coordinates
  float xx = 3 * map(pos.x, 0, width, -1, 1);
  float yy = 3 * map(pos.y, 0, height, -1, 1);

  PVector v = new PVector(xx, yy);
  v.add(new PVector(5,-3));

  // modify an angle using noise information
  angle += 3 * map( noise(v.x, v.y), 0, 1, -1, 1);
}

Applying variations

The other results can be achieved by using variations and curves described in previous articles: Folds  and Drawing vector field. Just copy them at the end of the file. Change marked line from the previous code for following code for example:

PVector v = swirl(new PVector(xx, yy),1);

spr00760B0A

Other variations produce different results and patterns.

Or something like this:

float n1 = noise(xx+5,yy-3)-0.55;

PVector vc = kampyle(n1);
PVector v = sinusoidal(vc,1);
v.mult(5);
v.add(new PVector(5,-3));

spr00363AC2

Time

As previously we can add time to get noise change during drawing. It gives nice, tissue like, objects.

angle += (3+time) * map( noise(v.x, v.y, time), 0, 1, -1, 1);

Forced movement

Another method is to add some constant value to x position in order to force movement of the points towards the right side.

Lets position all points on the left side during initialization and make number of point smaller.

// initialize in random positions
for (int i=0; i<2000; i++) {
  Agent a = new Agent();
  float posx = 100;
  float posy = random(200, 600);
  a.pos = new PVector(posx, posy);
  a.angle = random(TWO_PI);
  agents.add(a);
}

Then force movement

void update() {
  // modify position using current angle
  pos.x += cos(angle)+0.1;
  pos.y += sin(angle);

  // get point coordinates
  float xx = 3 * map(pos.x, 0, width, -1, 1);
  float yy = 3 * map(pos.y, 0, height, -1, 1);

  PVector v = new PVector(xx,yy);
  v.add(new PVector(5,-3));

  // modify an angle using noise information
  angle += (5+time) * map( noise(v.x, v.y), 0, 1, -1, 1);
}

spr004CD988

Using some previous techniques (variations and curves) I got also such results

Or something like this.

Play and enjoy 🙂

The end

Questions? generateme.blog@gmail.com or 

Blogs:

 

Drawing vector field

folds2d.tumblr.com

f03370000E4D3

The goal

During last few days I’ve made several attempts to create and visualize 2D vector fields. In the following article you can read about this concept with several examples and code in Processing.

There are many beautiful art/code based on vector field visualization around the net. Mostly based on noise function. Check them out before you start:

What is the vector field?

Vector field (in our case) is just a \mathbf{R}^{2} to \mathbf{R}^{2} function exatly the same as variation described in my previous post about Folds.

Why vector? Because you can treat a pair of (x',y') as vector \vec{v} = \begin{bmatrix}x'\\ y'\end{bmatrix} for each point from 2d plane. This way you get field of vectors.

Vector fields can be visualised in the following way

How to get the vector field

The main goal, in this article, is to research various ways to create vector fields. There two cases.

We can operate on functions which returns pair of values like variations. In this case such function defines vector field itself. We can use also some vector operations (like substraction, sum, etc…) to get new functions.

The second option is to get single float number n like from noise(), dist() between points or dot product of vectors, etc. And convert this number into vector.

Drawing method

The method is quite simple.

  1. Create points from [-3,3] floating point range
  2. Draw them
  3. For each of the point calculate relating vector from the vector field
  4. Scale it and add to the point coordinates
  5. Repeat 2-4

Step 4 is key here. We move our points along vectors. Trace lines build the resulting image.

The general flow for constructing vector field follows this scheme:

p=(x,y)\mapsto [multiple operations]\mapsto \vec{v}

Note: I assume that on every step I can multiply function variables or returned values by any number.

Here is code stub for my experiments

// dynamic list with our points, PVector holds position
ArrayList<PVector> points = new ArrayList<PVector>();

// colors used for points
color[] pal = {
  color(0, 91, 197),
  color(0, 180, 252),
  color(23, 249, 255),
  color(223, 147, 0),
  color(248, 190, 0)
};

// global configuration
float vector_scale = 0.01; // vector scaling factor, we want small steps
float time = 0; // time passes by

void setup() {
  size(800, 800);
  strokeWeight(0.66);
  background(0, 5, 25);
  noFill();
  smooth(8);

  // noiseSeed(1111); // sometimes we select one noise field

  // create points from [-3,3] range
  for (float x=-3; x<=3; x+=0.07) {
    for (float y=-3; y<=3; y+=0.07) {
      // create point slightly distorted
      PVector v = new PVector(x+randomGaussian()*0.003, y+randomGaussian()*0.003);
      points.add(v);
    }
  }
}

void draw() {
  int point_idx = 0; // point index
  for (PVector p : points) {
    // map floating point coordinates to screen coordinates
    float xx = map(p.x, -6.5, 6.5, 0, width);
    float yy = map(p.y, -6.5, 6.5, 0, height);

    // select color from palette (index based on noise)
    int cn = (int)(100*pal.length*noise(point_idx))%pal.length;
    stroke(pal[cn], 15);
    point(xx, yy); //draw

    // placeholder for vector field calculations

    // v is vector from the field
    PVector v = new PVector(0, 0);

    p.x += vector_scale * v.x;
    p.y += vector_scale * v.y;

    // go to the next point
    point_idx++;
  }
  time += 0.001;
}

f014700009714

When I change my field vector to be constant one (different than 0), my points start move.

PVector v = new PVector(0.1, 0.1);

f056900004085

Perlin noise

Let’s begin with perlin noise in few configurations. I’m going to change only marked lines from the code stub.

Variant 1

noise(x,y) function in processing returns value from 0 to 1 (actually implementation used in Processing has some problems and real value is often in range from 0.2 to 0.8). I will treat resulted value as an angle in polar coordinates and use sin()/cos() functions to convert it to cartesian ones. To properly do this I have to scale it up to range [0,2\pi ] or [-\pi, \pi ] or even more. I show few variants here.

// placeholder for vector field calculations
float n = TWO_PI * noise(p.x,p.y);
PVector v = new PVector(cos(n),sin(n));

f043800005B6A

Variant 2

Let’s check what happens when I multiply noise by 10, 100, 300 or even 1000. Noise is now scaled to range [-1.1] (it is more usable range in further examples).

float n = 10 * map(noise(p.x/5,p.y/5),0,1,-1,1); // 100, 300 or 1000
PVector v = new PVector(cos(n),sin(n));

Variant 3

Two of above variants were using classic method. Now I get rid of polar->cartesian conversion and I’m going to treat returned value from noise() as cartesian coordinates. Angle of the vector is the same for whole field and only length of vector is changed. This leads to some interesting patterns. Experiment with scaling input coordinates and n

 

float n = 5*map(noise(p.x,p.y),0,1,-1,1);
PVector v = new PVector(n,n);

Parametric curves

Let’s use parametric curves to convert noise() result into vector. The big list of them is defined in Wolfram Alpha or named here. Parametric curve (or plane curve) is a function \mathbf{R}\rightarrow\mathbf{R}^2. Click section name to see plot of the curve.

 

Astroid

// placeholder for vector field calculations
float n = 5*map(noise(p.x,p.y),0,1,-1,1); // and 2

float sinn = sin(n);
float cosn = cos(n);

float xt = sq(sinn)*sinn;
float yt = sq(cosn)*cosn;

PVector v = new PVector(xt,yt);

Results for noise scale 2 and 5

Cissoid of Diocles

// placeholder for vector field calculations
float n = 3*map(noise(p.x,p.y),0,1,-1,1);

float sinn2 = 2*sq(sin(n));

float xt = sinn2;
float yt = sinn2*tan(n);

PVector v = new PVector(xt,yt);

f146100000B07

Kampyle of Eudoxus

// placeholder for vector field calculations
float n = 6*map(noise(p.x,p.y),0,1,-1,1);

float sec = 1/sin(n);

float xt = sec;
float yt = tan(n)*sec; 

PVector v = new PVector(xt,yt);

f08490000B8A6

Rectangular Hyperbola

// placeholder for vector field calculations
float n = 10*map(noise(p.x*2,p.y*2),0,1,-1,1);

float xt = 1/sin(n);
float yt = tan(n); 

PVector v = new PVector(xt,yt);

f123200005CCF

Superformula

// placeholder for vector field calculations
float n = 5*map(noise(p.x,p.y),0,1,-1,1);

float a = 1;
float b = 1;
float m = 6;
float n1 = 1;
float n2 = 7;
float n3 = 8;

float f1 = pow(abs(cos(m*n/4)/a),n2);
float f2 = pow(abs(sin(m*n/4)/b),n3);
float fr = pow(f1+f2,-1/n1);

float xt = cos(n)*fr;
float yt = sin(n)*fr;

PVector v = new PVector(xt,yt);

f034900000FCF

Noise and curves combined

Now I’m going to play with some combinations of above curves and make multiple passes through noise().

Let’s put all curves into functions (paste following code to the end of the script):

PVector circle(float n) { // polar to cartesian coordinates
  return new PVector(cos(n), sin(n));
}

PVector astroid(float n) {
  float sinn = sin(n);
  float cosn = cos(n);

  float xt = sq(sinn)*sinn;
  float yt = sq(cosn)*cosn;

  return new PVector(xt, yt);
}

PVector cissoid(float n) {
  float sinn2 = 2*sq(sin(n));

  float xt = sinn2;
  float yt = sinn2*tan(n);

  return new PVector(xt, yt);
}

PVector kampyle(float n) {
  float sec = 1/sin(n);

  float xt = sec;
  float yt = tan(n)*sec;

  return new PVector(xt, yt);
}

PVector rect_hyperbola(float n) {
  float sec = 1/sin(n);

  float xt = 1/sin(n);
  float yt = tan(n);

  return new PVector(xt, yt);
}

final static float superformula_a = 1;
final static float superformula_b = 1;
final static float superformula_m = 6;
final static float superformula_n1 = 1;
final static float superformula_n2 = 7;
final static float superformula_n3 = 8;
PVector superformula(float n) {
  float f1 = pow(abs(cos(superformula_m*n/4)/superformula_a), superformula_n2);
  float f2 = pow(abs(sin(superformula_m*n/4)/superformula_b), superformula_n3);
  float fr = pow(f1+f2, -1/superformula_n1);

  float xt = cos(n)*fr;
  float yt = sin(n)*fr;

  return new PVector(xt, yt);
}

Variant 1 – p5art

Code based on p5art web app noisePainting

point\rightarrow noise\rightarrow noise\rightarrow noise\rightarrow vector

// placeholder for vector field calculations
float n1 = 10*noise(1+p.x/20, 1+p.y/20); // shift input to avoid symetry
float n2 = 5*noise(n1, n1);
float n3 = 325*map(noise(n2, n2), 0, 1, -1, 1); // 25,325

PVector v = circle(n3);

Variant 2

point\rightarrow noise\rightarrow curves\rightarrow noise\rightarrow noise\rightarrow circle\rightarrow vector

// placeholder for vector field calculations
float n1 = 15*map(noise(1+p.x/20, 1+p.y/20),0,1,-1,1);

PVector v1 = cissoid(n1);
PVector v2 = astroid(n1);

float n2a = 5*noise(v1.x,v2.y);
float n2b = 5*noise(v2.x,v1.y);

float n3 = 10*map(noise(n2a, n2b/3), 0, 1, -1, 1);

PVector v = circle(n3);

f071000004C6F

Variant 3

point\rightarrow noise\rightarrow curves\rightarrow noises\rightarrow vector

// placeholder for vector field calculations
float n1 = 15*map(noise(1+p.x/10, 1+p.y/10),0,1,-1,1);

PVector v1 = rect_hyperbola(n1);
PVector v2 = astroid(n1);

float n2a = 2*map(noise(v1.x,v1.y),0,1,-1,1);
float n2b = 2*map(noise(v2.x,v2.y),0,1,-1,1);

PVector v = new PVector(n2a,n2b);

f056400001094

Variant 4

point\rightarrow curves\rightarrow noises\rightarrow circle\rightarrow vector

// placeholder for vector field calculations
PVector v1 = kampyle(p.x);
PVector v2 = superformula(p.y);

float n2a = 3*map(noise(v1.x,v1.y),0,1,-1,1);
float n2b = 3*map(noise(v2.x,v2.y),0,1,-1,1);

PVector v = new PVector(cos(n2a),sin(n2b));

f04470000695F

Variant 5

point\rightarrow noises\rightarrow curves\rightarrow noises\rightarrow circle\rightarrow vector

// placeholder for vector field calculations
float n1a = 3*map(noise(p.x,p.y),0,1,-1,1);
float n1b = 3*map(noise(p.y,p.x),0,1,-1,1);

PVector v1 = rect_hyperbola(n1a);
PVector v2 = astroid(n1b);

float n2a = 3*map(noise(v1.x,v1.y),0,1,-1,1);
float n2b = 3*map(noise(v2.x,v2.y),0,1,-1,1);

PVector v = new PVector(cos(n2a),sin(n2b));

f04750000BD8E

Variant 6 – comparison

Here I want to compare different curves using the same method. I change marked lines into pair of curves defined above. I’ve put noiseSeed(1111); in setup() to have the same noise structure. Check image description to see what curves were used.

point\rightarrow curves\rightarrow subtract\rightarrow vector

// placeholder for vector field calculations
float n1a = 15*map(noise(p.x/10,p.y/10),0,1,-1,1);
float n1b = 15*map(noise(p.y/10,p.x/10),0,1,-1,1);

PVector v1 = superformula(n1a);
PVector v2 = circle(n1b);

PVector diff = PVector.sub(v2,v1);
diff.mult(0.3);

PVector v = new PVector(diff.x,diff.y);

Time

Now let’s introduce time. An external variable which is incremented every frame with some small value. It can be used to slightly change noise field during rendrering or can be used as a scale factor.

Let’s compare the same vector field without time and time used as third noise parameter.

 // placeholder for vector field calculations
 float n1a = 3*map(noise(p.x/2,p.y/2),0,1,-1,1);
 float n1b = 3*map(noise(p.y/2,p.x/2),0,1,-1,1);
 float nn = 6*map(noise(n1a,n1b),0,1,-1,1);

 PVector v = circle(nn);

f041800008831

And now I add time as a third parameter to noise function. This way our vector field changes when time passes.

 // placeholder for vector field calculations
float n1a = 3*map(noise(p.x/2,p.y/2,time),0,1,-1,1);
float n1b = 3*map(noise(p.y/2,p.x/2,time),0,1,-1,1);
float nn = 6*map(noise(n1a,n1b,time),0,1,-1,1);

PVector v = circle(nn);

f051900001798

Now time is used as a scaling factor.

// placeholder for vector field calculations
float n1a = 3*map(noise(p.x/2,p.y/2),0,1,-1,1);
float n1b = 3*map(noise(p.y/2,p.x/2),0,1,-1,1);

float nn = time*10*6*map(noise(n1a,n1b),0,1,-1,1);

PVector v = circle(nn);

f089600004548

Variations

Now let’s introduce variations (see previous post about Folds) as a vector field definition.

Copy below code to the end of your sketch. It includes function definiotions used in this and next chapters.

PVector sinusoidal(PVector v, float amount) {
  return new PVector(amount * sin(v.x), amount * sin(v.y));
}

PVector waves2(PVector p, float weight) {
  float x = weight * (p.x + 0.9 * sin(p.y * 4));
  float y = weight * (p.y + 0.5 * sin(p.x * 5.555));
  return new PVector(x, y);
}

PVector polar(PVector p, float weight) {
  float r = p.mag();
  float theta = atan2(p.x, p.y);
  float x = theta / PI;
  float y = r - 2.0;
  return new PVector(weight * x, weight * y);
}

PVector swirl(PVector p, float weight) {
  float r2 = sq(p.x)+sq(p.y);
  float sinr = sin(r2);
  float cosr = cos(r2);
  float newX = 0.8 * (sinr * p.x - cosr * p.y);
  float newY = 0.8 * (cosr * p.y + sinr * p.y);
  return new PVector(weight * newX, weight * newY);
}

PVector hyperbolic(PVector v, float amount) {
  float r = v.mag() + 1.0e-10;
  float theta = atan2(v.x, v.y);
  float x = amount * sin(theta) / r;
  float y = amount * cos(theta) * r;
  return new PVector(x, y);
}

PVector power(PVector p, float weight) {
  float theta = atan2(p.y, p.x);
  float sinr = sin(theta);
  float cosr = cos(theta);
  float pow = weight * pow(p.mag(), sinr);
  return new PVector(pow * cosr, pow * sinr);
}

PVector cosine(PVector p, float weight) {
  float pix = p.x * PI;
  float x = weight * 0.8 * cos(pix) * cosh(p.y);
  float y = -weight * 0.8 * sin(pix) * sinh(p.y);
  return new PVector(x, y);
}

PVector cross(PVector p, float weight) {
  float r = sqrt(1.0 / (sq(sq(p.x)-sq(p.y)))+1.0e-10);
  return new PVector(weight * 0.8 * p.x * r, weight * 0.8 * p.y * r);
}

PVector vexp(PVector p, float weight) {
  float r = weight * exp(p.x);
  return new PVector(r * cos(p.y), r * sin(p.y));
}

// parametrization P={pdj_a,pdj_b,pdj_c,pdj_d}
float pdj_a = 0.1;
float pdj_b = 1.9;
float pdj_c = -0.8;
float pdj_d = -1.2;
PVector pdj(PVector v, float amount) {
  return new PVector( amount * (sin(pdj_a * v.y) - cos(pdj_b * v.x)),
  amount * (sin(pdj_c * v.x) - cos(pdj_d * v.y)));
}

final float cosh(float x) { return 0.5 * (exp(x) + exp(-x));}
final float sinh(float x) { return 0.5 * (exp(x) - exp(-x));}

Let’s see how individual variations look as vector fields. Sometimes I had to adjust scaling factor to keep points on the screen.

// placeholder for vector field calculations
PVector v = sinusoidal(p,1);
v.mult(1); // different values for different functions

All together

Let’s sumarize what functions we have now defined.

  • \mathbf{R}\rightarrow\mathbf{R}
    • multiplying/dividing by number
    • single variable noise()
    • other single variable functions (sin/cos, exp, log, sq, sqrt, etc.)
  • \mathbf{R}\rightarrow\mathbf{R^2}
    • parametric curves
  • \mathbf{R^2}\rightarrow\mathbf{R}
    • noise() with two parameters
    • distance between vectors
    • dot product of vectors
    • length of the vector
    • angle between vectors
  • \mathbf{R^2}\rightarrow\mathbf{R^2}
    • variations and their combinations
    • sum or difference of vectors
    • atan2() of the vector

All above operations can be used to generate vector field (or simply transform 2d point to another 2d point).

I will play with above transformations in following examples. It’s a record of experiments.

Variant 1

point\rightarrow variations\rightarrow circle\rightarrow vector

// placeholder for vector field calculations
float n1 = 5*map(noise(p.x/5,p.y/5),0,1,-1,1);

PVector v1 = rect_hyperbola(n1);
PVector v2 = swirl(v1,1);

PVector v = new PVector(cos(v2.x),sin(v2.y));

f049300008710

Variant 2

point\rightarrow noises\rightarrow variations\rightarrow subtraction\rightarrow variation\rightarrow vector

// placeholder for vector field calculations
float n1 = 5*map(noise(p.x/5,p.y/5),0,1,-1,1);
float n2 = 5*map(noise(p.y/5,p.x/5),0,1,-1,1);

PVector v1 = vexp(new PVector(n1,n2),1);
PVector v2 = swirl(new PVector(n2,n1),1);

PVector v3 = PVector.sub(v2,v1);

PVector v4 = waves2(v1,1);
v4.mult(0.8);

PVector v = new PVector(v4.x,v4.y);

f04190000D54D

Variant 3

point\rightarrow variation\rightarrow noises\rightarrow variation\rightarrow vector

// placeholder for vector field calculations
PVector v1 = vexp(p,1);

float n1 = map(noise(v1.x,v1.y,time),0,1,-1,1);
float n2 = map(noise(v1.y,v1.x,-time),0,1,-1,1);

PVector v2 = vexp(new PVector(n1,n2),1);

PVector v = new PVector(v2.x,v2.y);

f028600002221

Variant 4

point\rightarrow variation\rightarrow noise\rightarrow curve\rightarrow normalization\rightarrow vector

// placeholder for vector field calculations
PVector v1 = polar(cross(p,1),1);

float n1 = 15*map(noise(v1.x,v1.y,time),0,1,-1,1);
PVector v2 = cissoid(n1);
v2.normalize();

PVector v = new PVector(v2.x,v2.y);

f08680000EA5D

Variant 5

point\rightarrow variation\rightarrow noises\rightarrow variation\rightarrow angle\rightarrow variation\rightarrow circle\rightarrow vector

// placeholder for vector field calculations
PVector v1 = power(p,1);

float n1 = 5*map(noise(v1.x,v1.y,time),0,1,-1,1);
float n2 = 5*map(noise(p.x/3,p.y/3,-time),0,1,-1,1);

PVector v2 = cosine(new PVector(n1,n2),1);

float a1 = PVector.angleBetween(v1,v2);

PVector v3 = superformula(a1);
v3.mult(3);

PVector v = new PVector(cos(v3.x),sin(v3.y));

f04770000F923

Variant 6

point\rightarrow variation\rightarrow subtraction\rightarrow angles\rightarrow combination \rightarrow circle\rightarrow vector

// placeholder for vector field calculations
PVector v1 = waves2(p,1);
PVector v2 = PVector.sub(v1,p);

float n1 = 8*noise(time)*atan2(v1.y,v1.x);
float n2 = 8*noise(time+0.5)*atan2(v2.y,v2.x);

PVector v = new PVector(cos(v2.x*n1),sin(v1.y+n2));

f035200007633

Variant 7

point\rightarrow variation\rightarrow subtraction\rightarrow noises\rightarrow angles\rightarrow curves\rightarrow variations\rightarrow subtraction\rightarrow variation\rightarrow angle\rightarrow combination\rightarrow circle\rightarrow vector

// placeholder for vector field calculations
PVector v1 = swirl(p,1);
v1.mult(0.5);
PVector v2 = PVector.sub(v1,p);

float nv1 = noise(v1.x,v1.y,time);
float nv2 = noise(v2.x,v2.y,-time);

float n1 = (atan2(v1.y,v1.x)+nv2);
float n2 = (atan2(v2.y,v2.x)+nv1);

PVector v3 = superformula(n1);
PVector v4 = rect_hyperbola(n2);

PVector tv3 = waves2(v3,1);
PVector tv4 = sinusoidal(v4,1);

PVector v5 = PVector.sub(tv4,tv3);

PVector v6 = pdj(v5,1);

float an = PVector.angleBetween(v6,new PVector(n1,n2));

PVector v = new PVector(cos(v2.x+sin(an)),sin(v6.y-cos(an)));
v.mult(1+time*40);

f040300002B89

Image

The last part is about using channel information from an image as a vector field. I wrote two sketches some time ago which are based on vector field concept.

Drawing generative

res_75948_natalie

Threadraw

res_4BC7E81C_natalie

How to use image data

First load your image at the beginning of the sketch

img = loadImage("natalie.jpg"); // PImage img; must be declared globally

Then use image data following way

// placeholder for vector field calculations
PVector v1 = sinusoidal(p,1);

int img_x = (int)map(p.x,-3,3,0,img.width);
int img_y = (int)map(p.y,-3,3,0,img.height);

float b = brightness( img.get(img_x,img_y) )/255.0;
PVector br = circle(b);

float a = 5*PVector.angleBetween(br,v1);

PVector v = astroid(a);

f015500000067

Appendix

This concept is taken from zach lieberman and Keith Peters who explored painting by non-intersecting segments. This idea can be used on vector fields too. Here are two examples.

See the code HERE.

The end

Questions? generateme.blog@gmail.com or 

Folds

folds2d.tumblr.com

fr00A8DEB0_small

A few years ago I started to explore fractal flames which resulted in Scratch implementation of flame explorer (https://scratch.mit.edu/projects/17114670/). Click HERE to read about what a fractal flame actually is.

Actually most of the ‘flavour’ in fractal flames is made of something authors call ‘variations’. Variations are nothing more than real number functions from \mathbf{R}^{2} to \mathbf{R}^{2} (it can be also a 3D function). Variations are often nonlinear, not continuous, not differentiable and sometimes semi-random.

I’ve started to draw them in a lot of different ways in order to explore their beauty. Here is how I do it:

Variations

A Variation is a multivariable real number function with domain and codomain R^n. I will focus on the 2D version. Sometimes it can have a name map or a morphism, but I call it just a function. So our function converts a point (x,y) into new point (x',y').

V:(x,y)\rightarrow(x',y')

Usually a Variation is designed as a whole class of functions, which means there are a few external parameters that you need to set to certain values in order to get a specific function. In such a case a different set of parameters will result in a different function.

The other parameter used is the ‘amount’ of the function. ‘amount’ is a special parameter which in almost all cases scales the resulting value.

So a more formal definition of our function is:

V_{\alpha,P}=\alpha V_{P}(x,y)=(\alpha x',\alpha y'), where \alpha \in [0,1]; which means:

Variation V is a function with some sequence of parameters Ρ which is scaled by a factor α. Sequence of parameters Ρ is just a sequence of values which are used in definition of the function.

Although the domain is the whole R² space I usually only operate on [-3,3]x[-3,3] range.

Examples of Variations

Here are some basic ones (without parameters and with α=1):

  • Identity – V(x,y)=(x,y)
    • V(0,0)=(0,0)
    • V(1,1)=(1,1)
  • Linear – V(x,y)=(\alpha x,\alpha y)
  • Sinusoidal – V(x,y)=(\alpha \sin{x},\alpha \sin{y})
    • V(0,0)=(0,0)
    • V(1,-1)=(0,84,-0,84)

Things get more complicated when we add sequence of parameters (and α=1). These definitions are function class definitions and by using specific parameters you will obtain unique functions.

  • PDJ – V_{P}(x,y)=(\alpha (\sin{ay}-\cos{bx}),\alpha (\sin{cx}-\cos{dy})), where P=\{a,b,c,d\} from \mathbf{R}.
    • V_{P}(1,-1) = (\sin{(-\pi)}-\cos{0}, \sin{2}-\cos{0}) = (-1,-0.09), where P = \{\pi,0,2,0\}
  • Rectangles – V_{P}(x,y) = (\alpha (a(2\lfloor \frac{x}{a}\rfloor +1)-x),\alpha (b(2\lfloor \frac{y}{b}\rfloor +1)-y)), where P=\{a,b\} from \mathbf{R}
    • V_{P}(1,-1) = (3\cdotp 1-1, -1\cdotp 1+1) = (2,0),  where P = \{1,1\}

How to draw it

Here is the template I use to draw. The general concept is to take every point from the [-3,3]x[-3,3] range, calculate the result of the function and draw it. This template draws a simple identity function:

void setup() {
  size(600, 600);
  background(250);
  smooth(8);
  noFill();
  stroke(20, 15);
  strokeWeight(0.9);

  x1=y1=-3;
  x2=y2=3;
  y=y1;
  step=(x2-x1)/(2.321*width);
}

float x1, y1, x2, y2; // function domain
float step; // step within domain
float y;

boolean go = true;
void draw() {
  if (go) {
    for (int i=0; (i<20)&go; i++) { // draw 20 lines at once
      for (float x=x1; x<=x2; x+=step) {
        drawVariation(x, y);
      }
      y+=step;
      if (y>y2) {
        go = false;
        println("done");
      }
    }
  }
}

void drawVariation(float x, float y) {
  float xx = map(x, x1, x2, 20, width-20);
  float yy = map(y, y1, y2, 20, height-20);
  point(xx, yy);
}

fold_00CDA757
The result, which you can see here, is a grid that’s the result of rounding floating point values. By inserting the randomGaussian() function into the calculation I can distort the points a little and thus get a less rigid and more uniform look.

void drawVariation(float x, float y) {
  float xx = map(x+0.003*randomGaussian(), x1, x2, 20, width-20);
  float yy = map(y+0.003*randomGaussian(), y1, y2, 20, height-20);
  point(xx, yy);
}

fold_00AD8D20

Ok, now we are ready to plot our first functions!

Sinusoidal

Let’s start with a sinusoidal function. 

V(x,y) = (αsin(x),αsin(y));

The code to draw this function looks like this:

void drawVariation(float x, float y) {
  PVector v = new PVector(x,y);
  float amount = 1.0;

  v = sinusoidal(v,amount);

  float xx = map(v.x+0.003*randomGaussian(), x1, x2, 20, width-20);
  float yy = map(v.y+0.003*randomGaussian(), y1, y2, 20, height-20);
  point(xx, yy);
}

PVector sinusoidal(PVector v, float amount) {
  return new PVector(amount * sin(v.x), amount * sin(v.y));
}

The top image is a plot with amount=1.0. The bottom image is an example where I used amount=3.0 to scale the resulting value up to the [-3,3]x[-3,3] range.

fold_00DC7BE0fold_00E7E153

Hyperbolic

Another example is the hyperbolic function, which uses polar coordinates to calculate x and y.

PVector hyperbolic(PVector v, float amount) {
  float r = v.mag() + 1.0e-10;
  float theta = atan2(v.x, v.y);
  float x = amount * sin(theta) / r;
  float y = amount * cos(theta) * r;
  return new PVector(x, y);
}

fold_0053A2AA

PDJ

Now let’s draw functions from the PDJ class. The sequence of parameters are provided as external variables and used in the function.

// parametrization P={pdj_a,pdj_b,pdj_c,pdj_d}
float pdj_a = 0.1;
float pdj_b = 1.9;
float pdj_c = -0.8;
float pdj_d = -1.2;
PVector pdj(PVector v, float amount) {
  return new PVector( amount * (sin(pdj_a * v.y) - cos(pdj_b * v.x)),
                      amount * (sin(pdj_c * v.x) - cos(pdj_d * v.y)));
}

So, for example, when I set Ρ = {0.1, 1.9, -0.8, -1.2} I get:

fold_00375F13

But with Ρ={1.0111, -1.011, 2.08, 10.2} I get something very different:

fold_00CAED19

See also Holger Lippmann’s works based on PDJ functions

Julia

The next function I want to show is Julia where random() function is used.

PVector julia(PVector v, float amount) {
  float r = amount * sqrt(v.mag());
  float theta = 0.5 * atan2(v.x, v.y) + (int)(2.0 * random(0, 1)) * PI;
  float x = r * cos(theta);
  float y = r * sin(theta);
  return new PVector(x, y);
}

fold_009E8BA6

Sech

And then there’s the last function, Sech, which is based on an hyperbolic functions:

float cosh(float x) { return 0.5 * (exp(x) + exp(-x));}
float sinh(float x) { return 0.5 * (exp(x) - exp(-x));}

PVector sech(PVector p, float weight) {
  float d = cos(2.0*p.y) + cosh(2.0*p.x);
  if (d != 0)
    d = weight * 2.0 / d;
  return new PVector(d * cos(p.y) * cosh(p.x), -d * sin(p.y) * sinh(p.x));
}

fold_00AC76EB

More to see

Click HERE to see the animations of 129 basic variations that I have created in Scratch

Now let’s complicate things!

Being aware now of the base functions (which are widely available: flame.pdf paper defines 48 of them, JWildfire code defines 200+ usable functions or classes of functions), we can then create new ones ourselves. Math theories (like group/ring theories, homomorphism, analysis, etc) with associativity and distributivity of operations give us recipes to build new functions from the others.

Let’s suppose we have variations V(x,y)=(x',y') and W(x,y)=(x'',y'') and we want to produce a new function Z. In order to do this we can then do any of the following:

  1. Sum of the function: (V+W)(x,y) = V(x,y)+W(x,y) = (x'+x'', y'+y'')
  2. Subtraction of the functions: (V-W)(x,y) = V(x,y)-W(x,y) = (x'-x'', y'-y'')
  3. Combination of the function: (V\circ W)(x,y) = V(W(x,y)) = V(x'',y'')
  4. Kind of differencial/derivative (it’s not strict differential): \partial(V(x,y)) = \frac{V(x+h,y+h)-V(x,y)}{\sqrt{h}} = (\frac{x^*-x'}{\sqrt{h}}, \frac{y^*-y'}{\sqrt{h}}), where V(x+h,y+h)=(x^*,y^*) and h is some real number and h\neq 0.0
  5. Multiplication: (V*W)(x,y)=V(x,y)*W(x,y) = (x'*x'', y'*y'')
  6. Division: (\frac{V}{W})(x,y)=\frac{V(x,y)}{W(x,y)} = (\frac{x'}{x''},\frac{y'}{y''}), where when denominator is 0, value is also 0 (just to simplify things)
  7. Power: V^{n}(x,y) = V^{n-1}(x',y') or as recursive combinations: V(V(V(V(\cdots V(x,y)\cdots ))))

You can of course mix all of the above to create a bunch of new and unique functions. For example, you can create function which is a sum of combination of power of division of few base functions. Or whatever else you want.

The sum, subtraction, multiplication and division of functions can be coded like this:

PVector addF(PVector v1, PVector v2) { return new PVector(v1.x+v2.x, v1.y+v2.y); }
PVector subF(PVector v1, PVector v2) { return new PVector(v1.x-v2.x, v1.y-v2.y); }
PVector mulF(PVector v1, PVector v2) { return new PVector(v1.x*v2.x, v1.y*v2.y); }
PVector divF(PVector v1, PVector v2) { return new PVector(v2.x==0?0:v1.x/v2.x, v2.y==0?0:v1.y/v2.y); }

Combination can be obtained like this (julia o sech)

  v = julia(sech(v,amount),amount);

Power, which is multiple combination of the same function, can be coded like this (hyperbolic^5 = hyperbolic(hyperbolic(hyperbolic(hyperbolic(hyperbolic)))) )

  for(int i=0;i<5;i++) v = hyperbolic(v,amount);

And an example of a differential of pdj function:

PVector d_pdj(PVector v, float amount) {
  float h = 0.1; // step
  float sqrth = sqrt(h);
  PVector v1 = pdj(v, amount);
  PVector v2 = pdj(new PVector(v.x+h, v.y+h), amount);
  return new PVector( (v2.x-v1.x)/sqrth, (v2.y-v1.y)/sqrth );
}

hyperbolic + pdj

  v = addF( hyperbolic(v,amount), pdj(v,amount) );

fold_009E9C49

julia – hyperbolic

  v = subF( julia(v,amount), hyperbolic(v,amount) );

fold_003258DF

sech * pdj

  v = mulF( sech(v,amount), pdj(v,amount) );

fold_00FC9772

hyperbolic / sech

  v = divF( hyperbolic(v,amount), sech(v,amount) );

fold_0072A5CD

d(pdj)

  float amount = 3.0; // scale up
  v = d_pdj(v,amount);

fold_00CD3BAD

julia o hyperbolic = julia(hyperbolic)

  v = julia(hyperbolic(v,amount),amount);

fold_0014E98C

hyperbolic ^ 10

  for(int i=0;i<10;i++) v = hyperbolic(v,amount);

fold_003783A8

[julia((hyperbolic + julia o sech) * pdj) – d(pdj)]^3

  for(int i=0;i<3;i++)
    v = subF(julia( mulF(addF(hyperbolic(v,0.5),julia(sech(v,1),0.5)),pdj(v,1)),2.5),d_pdj(v,1));

fold_0025FE0C

Folding

In the above examples you can see that some of the points have escaped outside of the screen. The main reason is that the values exceeded -3 or 3. To keep them inside our area we have to force them to be in the required range of values. There are a couple of strategies for achieving this. I will describe two of them: modulo and sinusoidal.

I will use pdj + hyperbolic * sech function with amount = 2

  float amount = 2.0;
  v = addF(pdj(v,amount),mulF(hyperbolic(v,amount),sech(v,amount)));

Modulo

The first strategy is to treat the screen as a torus (points that exceed the screen area will reappar on the opposite side: top <> down, left <> right). The formula for this wrapping is documented below. It is called after the function has been calculated.

  v.x = (v.x - x1) % (x2-x1);
  if(v.x<0) v.x += (x2-x1);
  v.y = (v.y - y1) % (y2-y1);
  if(v.y<0) v.y += (y2-y1);
  v.x += x1;
  v.y += y1;

fold_00136519

Sinusoidal

The second strategy is to wrap the points using a sinus function, which moves between -1 to 1. With amount=3 you get the required range.

  v = sinusoidal(v,(x2-x1)/2);

fold_004782A9

Towards fixed point

In maths a ‘fixed point’ is a special point a where f(a) = a. If the function has a special characteristic (is a contraction mapping) a fixed point can be calculated by using the power of the function as defined above. Just call f(f(f(f(f(f….))))). In our case we operate on a set of points (range [-3,3]x[-3,3]) and our “fixed point” can also be set (if exists). So let’s try to modify our code a little and draw each step for a certain power of our function.

Let’s create a function v = sinusoidal o julia o sech calculate v(v(v(v(…)))) and draw each step: first v, then v(v), v(v(v)), in order to finish at some chosen number.

Our algorithm will look like this:

  1. set n = 3
  2. choose a point p = (x,y)
  3. calculate new point p = sinusoidal(julia(sech(p)))
  4. draw point p
  5. repeat n (=3) times steps 3-4 (using newly calculated point p)
  6. go to 2

But first we need to adjust the step value and set the stroke color to be slightly lighter

  stroke(60, 15);
  //...
  // more points drawn in drawVariation, bigger step
  step=sqrt(n)*(x2-x1)/(2.321*width);

And adapt drawVariation()

int n=1;
void drawVariation(float x, float y) {
  PVector v = new PVector(x, y);

  for (int i=0; i<n; i++) {
    v = julia(sech(v, 1), 1);
    v = sinusoidal(v, (x2-x1)/2);
    float xx = map(v.x+0.003*randomGaussian(), x1, x2, 20, width-20);
    float yy = map(v.y+0.003*randomGaussian(), y1, y2, 20, height-20);
    point(xx, yy);
  }
}

The following images represent represent values of n = 1, 2, 4, 8, 16, 32, 64, 128.

fold_00611057 fold_0020836C fold_004B2394 fold_00B6AFAB fold_00B1F37E fold_0017414F fold_000BCF67 fold_0031CDC9

For high n the sequence will finally result in this (it’s a fixed point of our function)

fold_0042635C

Folds2d

To produce my works I prepared a few more things to have more variety of functions and semi-artistic look. These are:

  • JWildfire wrapper for Processing (to get all Variations included there)
  • Random formula generator with evaluation
  • Randomization of parameters
  • Noisy, gray canvas
  • High resolution rendering
  • Fixed point option
  • Scaling / translating range
  • Various folding options
  • and probably few more

Final notes

I would like to THANK Jerome Herr for review of this article. Thank you very much man!

Don’t forget to visit his page: http://p5art.tumblr.com/

Questions/Errors? generateme.blog@gmail.com

fr00953D5F

 

Training own DNN for Deep Dream

Originaly posted on FB Deep Dream // Tutorials group under this link: https://www.facebook.com/groups/733099406836193/permalink/748784328601034/ A little bit messy. Give me an info if something is unclear or bullshit.

Ok, so, you’re bored, have spare time, have working caffe on gpu and want to try train network to get rid of dogs in deep dream images… Here is tutorial for you. In points.

  1. Forget about training from the scratch, only fine tune on googlenet. Building net from the scratch requires time, a lot of time, hundreds of hours… Read it first: http://caffe.berkeleyvision.org/gathered/examples/finetune_flickr_style.html.
  2. The hardest part: download 200-1000 images you want to use for training. I found that one type of images work well. Faces, porn, letters, animals, guns, etc.
  3. Resize all images into dimension of 256×256. Save it as truecolor jpgs (not grayscale, even if they are grayscale)
  4. OPTION: Calculate average color values of all your images. You need to know what is average value of red, green and blue of your set. I use command line tools convert and identify from ImageMagick: convert *.jpg -average res.png; identify -verbose res.png to see ‘mean’ for every channel.
  5. Create folder <caffe_path>/models/MYNET <- this will be your working folder. All folders and files you’ll create will be placed in MYNET
  6. Create folder named ‘images’ (in your working folder, MYNET)
  7. For every image you have create separate folder in ‘images’. I use numbers starting from 0. For example ‘images/0/firstimage.jpg’, ‘images/1/secondimage.jpg’, etc… Every folder is a category. So you end up with several folders with single image inside.
  8. Create text file called train.txt (and put it to the working folder). Every line of this file should be relative path of the image with the number of the image category. It looks like this:
    images/0/firstimage.jpg 0
    images/1/secondimage.jpg 1
  9. Copy train.txt into val.txt file
  10. Copy deploy.prototxt, train_val.prototxt and solver.prototxt into working folder from this link: https://gist.github.com/tsulej/ff2b3e37aa76e8fbb244
  11. Next you need to edit all files, let’s start.
  12. train_val.prototxt:
    • lines 13-15 (and 34-36) define mean values for your image set. For blue, green and red channels respectively (mind the reverse order). If you don’t know them just set all to 129
    • line 19, define number of images processed at once. 40 works well on 4gb GPU. You need probably change it to 20 for 2gb gpu. You’ll get out of memory error if number is too high, then just lower it. You can set it to 1 as well.
    • lines 917, 1680 and 2393. num_output should be set to number of your categories (number of your folders in image folder)
  13. deploy.prototxt:
    • line 2141, num_output as above
  14. solver.prototxt, what is important below:
    • display: 20 – print statistics every 20 iterations
    • base_lr: 0.0005 – learning rate, it’s a subject to change. You’ll be observing loss value and adapt base_lr regarding results (see strategy below)
    • max_iter: 200000 – how many iterations for training, you can put here even million.
    • snapshot: 5000 – how often create snapshot and network file (here, every 5000 iterations). It’s important if you want to break training in the middle.
  15. Almost ready to train… but you need googlenet yet. Go to caffe/models/bvlc_googlenet and download this file (save it here) http://dl.caffe.berkeleyvision.org/bvlc_googlenet.caffemodel
  16. Really ready to train. Go to the working folder and run this command:
../../build/tools/caffe train -solver ./solver.prototxt -weights ../bvlc_googlenet/bvlc_googlenet.caffemodel

It should work. Every 5000 iterations you’ll get snapshot. You can break training then and run deepdream on your net. First results should be visible on inception_5b/output layer.
To restart training use a snapshot running this command:

../../build/tools/caffe train -solver ./solver.prototxt -snapshot ./MYNET_iter_5000.solverstate

Strategy for base_lr in solver for first 1000 iterations. Observe loss value. During the training it should be in average lower and lower and go towards 0.0. But:

  • if you see ‘loss’ value during training is higher and higher – break and set base_lr 5 times less than current
  • if you see ‘loss’ stuck at near some value and lowers but very slowly – break and set base_lr 5 times more than current
  • if none of above strategy works – probably you have troubles and train failed. Change image set and start over.

Some insights:

  1. My GPU is NVIDIA GTX960 with 4gb RAM. Caffe is compiled with CuDNN library. Every 5k iterations take about 1 hour. On CPU calculations were 40 times slower.
  2. I usually stop after 40k of iterations. But did also 90k. DeepDickDream guy made 750k iterations to have dicks on Hulk Hogan (http://deepdickdreams.tumblr.com/)
  3. You have little chance to get your image set visible in deepdreams images before 100k of iterations. But you have high probability to see something new and no dogs
  4. Fine tune method means that you copy almost all information into your net from existing net except the layers called “classification” (three of them). To achieve this just rename layer name. You may decide and try not to copy other layers (eg. all inception_5b) to clean up more information. I have good and bad results using this method. To do this just change name of such layer.
  5. You can put more images into one category. You can use 20 categories with 200 images in it. You can use random images, same images, similar images, whatever you want. I couldn’t find a rule to get best results. It depends mostly on content I suppose.

Examples:

Net trained on british library images (https://www.flickr.com/photos/britishlibrary/albums ). 11 categories. 100k images total (every image had 10 variants). inception layer 3, 4 and 5 were cleaned up. Result after 25k iterations. Faces from portrait album are clearly visible. This was my first and best try (among 20). Image from 5b/output layer.

deepdreem example 1

Only letters album from british library images. 750 categories with one image in category. 40k iteration. Only classification layers cleaned up. Butterflies visible, but why? Layer 5b.
deepdreem example 2

Same as above. 5a layer. Hourglasses. deepdreem example 3

94 categories, 100 images each, porn image set. 90k iterations. default net set (only classification). Layer 5b. deepdreem example 4

As above but differently prepared image set (flip/flop, rotations, normalization/equalization, blur/unsharp, etc.). 40k iterations. Layer 5b. deepdreem example 5

Caffenet, built from scratch on 4 categories, 1000 glitch images each. 65k iterations. Pool5 layer. deepdreem example 6

Same image set as above. 80k iterations. googlenet with cleaned up 3, 4 and 5 layers. 5b layer. deepdreem example 7

 

Flickr collection 1

Flickr collection 2

Any questions? Comment it or write generateme.blog@gmail.com

 

 

 

 

 

 

 

Story of one picture

I’ve got request to explain thought process behind following pictures (click to see two more):

Wool

Wool

I’ll try to recreate process from the scratch showing ready-to-use code progressively.

Inspiration

In addition to my own, a lot of ideas are taken from others’ works. It can be a specific palette, technique, composition, structure or texture. Then I recreate, rework, inspire myself by them to build my own dictionary and toolset. Still learning and searching.

This time it was picture by SuperColony:

217/365

217/365 by SuperColony

Fortunately guys published source code and it was beginning for me.

Analysis

Code is quite simple and easy to understand but the result isn’t obvious. Let’s simplify code a little and draw one step.

Step 1

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  stroke(233, 131, 45);
  makeme();
}

float yoff = 0.0;
void makeme() {
  background(23, 67, 88);
  strokeWeight(2*noise(20,200));
  beginShape();
  float xoff2 = 0;
  for (float x = 0; x <= width; x += 5) {
    float y = map(noise(xoff2, yoff*20), 0, 1, 20, 800);
    vertex(x, y);
    xoff2 += random(0.01,0.05);
  }
  yoff += 0.01;
  vertex(width, height);
  vertex(0, height);
  endShape(CLOSE);
}

void draw() {}
void keyPressed() { saveFrame("f####.png"); }
void mousePressed() { makeme(); }

So, this is simply plot of noise function (line 16) using vertex(). If we draw 2000 steps we’ll see expected result. Originally authors chose 2 different colors (with alpha ranging from 5 to 20) and 3 positions of plot sets.

Step 2

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

float yoff = 0.0;
void makeme() {
  background(23, 67, 88);
  for(int iter=0;iter<2000;iter++) {
    stroke(233, 131, 45, random(5,20));
    strokeWeight(2*noise(20,200));
    beginShape();
    float xoff2 = 0;
    for (float x = 0; x <= width; x += 5) {
      float y = map(noise(xoff2, yoff*20.0), 0, 1, 20, 800);
      vertex(x, y);
      xoff2 += random(0.01,0.05);
    }
    yoff += 0.01;
    vertex(width, height);
    vertex(0, height);
    endShape(CLOSE);
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.png"); }
void mousePressed() { makeme(); }

Our picture represents traverse of noise field with almost the same x range (lines 15 and 19) and different y each line. We can see that we have some kind of periodicity. It leads to the conclusion that noise function is periodic. And this characteristic makes the effect. If you change line 15th to have xoff2 initialized randomly you can bypass this issue.

float xoff2 = random(1.0);

Step 3

Ok. This is enough to start with my own version.

Step 1 – Circle

Let’s make a circle from above wool-a-like structure, reset colors (will be decided later). To make circle just switch to polar coordinates. Random some attributes (xoff, yoff, ang step) to avoid regularity. Unfortunately nasty thing happens at angle=0.0. It’s due to fact that lines doesn’t join at the same point, traversing noise field I don’t return to the starting position.

Step 4

 

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

float yoff = random(1.0);
void makeme() {
  background(20);
  translate(400,400);
  for(int iter=0;iter<3000;iter++) {
    stroke(240, random(5.0,20.0));
    strokeWeight(random(1.0));
    float xoff = random(1.0);
    beginShape();
    for (float ang=0.0; ang<TWO_PI; ang+=random(TWO_PI/200.0)) {
      float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
      float x = r * cos(ang);
      float y = r * sin(ang);
      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    endShape(CLOSE);
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Step 2 – Break the circle

To bypass this issue just make the gap wider. To make it change line 17. Better but sharp edges are not so pleasant.

Step 5

for (float ang=0.0; ang<0.8*TWO_PI; ang+=random(TWO_PI/200.0)) {

Step 3 – Adjustment

To fix it let’s random a little beginning and end of the arc. And provide variable shape_closed to make two options: one with closed circle and other with open.

Step 6b

Step 6a

 

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

void makeOptions() {
  shape_closed = random(1)<0.5 ? true : false;
}

boolean shape_closed = true;
float yoff = random(1.0);
void makeme() {
  background(20);
  translate(400,400);
  makeOptions(); // calculate some random parameters
  for(int iter=0;iter<3000;iter++) {
    stroke(240, random(5.0,20.0));
    strokeWeight(random(1.0));
    float xoff = random(1.0);
    beginShape();
    for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {
      float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
      float x = r * cos(ang);
      float y = r * sin(ang);
      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    if(shape_closed) endShape(CLOSE); else endShape();
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Step 4 – Shift

This time let’s shift and make some distance of two halves. Make it optional (choosen randomly).

Step 7a

Step 7b

 

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

void makeOptions() {
  shape_closed = random(1)<0.5 ? true : false;
  shiftxy = (int)random(3);
}

int shiftxy = 0;
boolean shape_closed = true;
float yoff = random(1.0);
void makeme() {
  background(20);
  translate(400,400);
  makeOptions(); // calculate some random parameters
  for(int iter=0;iter<3000;iter++) {
    stroke(240, random(5.0,20.0));
    strokeWeight(random(1.0));
    float xoff = random(1.0);
    beginShape();
    for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {
      float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
      float x = r * cos(ang);
      float y = r * sin(ang);

      if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
        x += 20.0;
        y += 20.0;
      } else if(shiftxy > 0.0) {
        x -= 20.0;
        y -= 20.0;
      }

      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    if(shape_closed) endShape(CLOSE); else endShape();
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Step 5 – Blocks

I like rectangular view or similar distortions so here I going to divide my picture to squares and inside each square exchange x and y axises. Of course let’s make it optional. Calculating abs() of x or y (line 46 and 47) stops exchanging x/y for negative values. One more issue: when doing blocks all points are moved by square size we need to adjust it (line 25)

Step 8a

Step 8b

 

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

void makeOptions() {
  shape_closed = random(1)<0.5 ? true : false;
  shiftxy = (int)random(3);
  do_absx = random(1)<0.5 ? true : false;
  do_absy = random(1)<0.5 ? true : false;
  do_blocky = random(1)<0.75 ? true : false;
}

boolean do_absx=false;
boolean do_absy=false;
boolean do_blocky=true;
int shiftxy = 0;
boolean shape_closed = true;

float yoff = random(1.0);
void makeme() {
  background(20);
  if(do_blocky) translate(360,360); else translate(400,400);
  makeOptions(); // calculate some random parameters
  for(int iter=0;iter<3000;iter++) {
    stroke(240, random(5.0,20.0));
    strokeWeight(random(1.0));
    float xoff = random(1.0);
    beginShape();
    for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {
      float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
      float x = r * cos(ang);
      float y = r * sin(ang);

      if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
        x += 20.0;
        y += 20.0;
      } else if(shiftxy > 0.0) {
        x -= 20.0;
        y -= 20.0;
      }

      if(do_blocky) {
        float sx = (do_absx?abs(x):x) % 40.0;
        float sy = (do_absy?abs(y):y) % 40.0;
        int xx = (int)(x/40.0);
        int yy = (int)(y/40.0);
        x = xx*40.0 + (40.0-sx);
        y = yy*40.0 + (40.0-sy);
      }

      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    if(shape_closed) endShape(CLOSE); else endShape();
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Step 6 – Colors

Usually I have a huge problem with color choice. So I use Colour Lovers or Paletton (or random generated) to create/choose palletes. Here Infinite Skies from Colour Lovers is used. Let’s add one more option with 1% probablity: make filament orange/gold and put it in the middle of the ring. Some variety of main color is added also.

Step 9

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

void makeOptions() {
  shape_closed = random(1)<0.5 ? true : false;
  shiftxy = (int)random(3);
  do_absx = random(1)<0.5 ? true : false;
  do_absy = random(1)<0.5 ? true : false;
  do_blocky = random(1)<0.75 ? true : false;
}

boolean do_absx=false;
boolean do_absy=false;
boolean do_blocky=true;
int shiftxy = 0;
boolean shape_closed = true;

float yoff = random(1.0);
void makeme() {
  background(37,49,63);
  if(do_blocky) translate(360,360); else translate(400,400);
  makeOptions(); // calculate some random parameters

  for(int iter=0;iter<3000;iter++) {

    boolean gold_line = false;
    if(random(1)>0.01) {
      stroke(lerpColor(color(255,241,170),color(255,255,250),random(1)), random(5.0,20.0));
      strokeWeight(random(1.0));
    } else {
      gold_line = true;
      stroke(253,155,87,random(20,60));
      strokeWeight(random(1.0,1.5));
    }

    float xoff = random(1.0);
    beginShape();
    for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {

      float ll,rr;
      if(gold_line) { ll=120; rr=320; } else { ll=20; rr=400; }

      float r = map(noise(xoff, yoff*5.0), 0, 1, ll, rr);
      float x = r * cos(ang);
      float y = r * sin(ang);

      if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
        x += 20.0;
        y += 20.0;
      } else if(shiftxy > 0.0) {
        x -= 20.0;
        y -= 20.0;
      }

      if(do_blocky) {
        float sx = (do_absx?abs(x):x) % 40.0;
        float sy = (do_absy?abs(y):y) % 40.0;
        int xx = (int)(x/40.0);
        int yy = (int)(y/40.0);
        x = xx*40.0 + (40.0-sx);
        y = yy*40.0 + (40.0-sy);
      }

      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    if(shape_closed) endShape(CLOSE); else endShape();
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Step 9 – Background

The last thing remains: background. It’s grid made of lines with variable intensity. Grid is rotated 45 degrees.

Step 10

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

void makeOptions() {
  shape_closed = random(1)<0.5 ? true : false;
  shiftxy = (int)random(3);
  do_absx = random(1)<0.5 ? true : false;
  do_absy = random(1)<0.5 ? true : false;
  do_blocky = random(1)<0.75 ? true : false;
}

// background grid
void makeBackground() {
  background(37,49,63);
  strokeWeight(1);
  pushMatrix();
  translate(400,400);
  rotate(HALF_PI/2.0);
  for(float x=-600;x<600;x+=random(5)) {
    stroke(90,104,115,random(5,15));
    line(-600,x,600,x);
    line(x,-600,x,600);
  }
  popMatrix();
}

boolean do_absx=false;
boolean do_absy=false;
boolean do_blocky=true;
int shiftxy = 0;
boolean shape_closed = true;

float yoff = random(1.0);
void makeme() {
  makeBackground();
  pushMatrix();
  if(do_blocky) translate(360,360); else translate(400,400);
  makeOptions(); // calculate some random parameters

  for(int iter=0;iter<3000;iter++) {

    // gold line
    boolean gold_line = false;
    if(random(1)>0.01) {
      stroke(lerpColor(color(255,241,170),color(255,255,250),random(1)), random(5.0,20.0));
      strokeWeight(random(1.0));
    } else {
      gold_line = true;
      stroke(253,155,87,random(20,60));
      strokeWeight(random(1.0,1.5));
    }

    float xoff = random(1.0);
    beginShape();
    for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {

      float ll,rr;
      if(gold_line) { ll=120; rr=320; } else { ll=20; rr=400; }

      float r = map(noise(xoff, yoff*5.0), 0, 1, ll, rr);
      float x = r * cos(ang);
      float y = r * sin(ang);

      // shift
      if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
        x += 20.0;
        y += 20.0;
      } else if(shiftxy > 0.0) {
        x -= 20.0;
        y -= 20.0;
      }

      // rectangles
      if(do_blocky) {
        float sx = (do_absx?abs(x):x) % 40.0;
        float sy = (do_absy?abs(y):y) % 40.0;
        int xx = (int)(x/40.0);
        int yy = (int)(y/40.0);
        x = xx*40.0 + (40.0-sx);
        y = yy*40.0 + (40.0-sy);
      }

      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    if(shape_closed) endShape(CLOSE); else endShape();
  }
  popMatrix();
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Finished

That’s all. There is difference in colors of picture on my Tumblr and here. Reason is different algorithm of choosing color used (line 49). For Tumblr it’s exactly this:

if(random(1)<0.2)
  stroke(255,241,170,random(5,20));
else
  stroke(255,255,250,random(5,20));

If you want more explanation or clarification or another tutorial, let me know writing an e-mail: generateme.blog@gmail.com