Ruby Processing

Here is my blog in which I will describe my experiments with ruby-processing, find out more about ruby-processing at:- https://github.com/jashkenas/ruby-processing compatible with processing-2.2.1 and https://github.com/monkstone/cf3ruby for my version of the cfdg DSL (context-free-art)

Monday, 1 September 2014

Instrumenting ruby-processing

Enhance your geek credibility by using JMXBeans to monitor your ruby-processing sketch. See jruby wiki for details. Above was done using a modified version of JRubyArt (the development version of ruby-processing) where I sent --manage flag to jruby when running the sketch.

Getting started with ruby-processing for ruby purists

Previously in my post getting started for wizards I showed how to create a sketch that would be more familiar to people coming from processing to ruby-processing. The sketch does not need to be explicitly wrapped as a class (that extends from Processing::App), since ruby-processing does that for you under the hood. This is the DSL approach to writing ruby-processing sketches which I favour, and makes it easier to read across from other processing modes to ruby-processing. However since ruby-processing-2.6.0 it is just as easy to create a classical sketch.
rp5 create classic_sketch 200 200 --wrap
rp5 watch classic_sketch.rb


Open a new console and 'vim classic_sketch.rb' or us another suitable editor

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw

  end
end

# ClassicSketch.new(x: 20, y: 20)

For instant gratification and to relocate the sketch on your display un-comment the last line of the sketch and save (also note this line was "not" required to run the sketch, rp5 run or rp5 watch takes care of creating a new instance of sketch for you). You only use this form to send parameters, such as 'x offset' to your sketch

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw

  end
end

ClassicSketch.new(x: 20, y: 20)
The sketch updates auto-magically...
Already in the works for the next release is the possibility of placing the sketch on your screen using the config file '~/.rp5rc' so this is kind of redundant, but a post initialization hook might get added so you will be able to send all sorts of other parameters. Which are then available by overriding the post_initialization hook method (See Sandi Metz POODR)

Next change the background of your sketch

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw
    background 0
  end
end

ClassicSketch.new(x: 20, y: 20)

Next create a blue box.

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw
    background 0
    fill 0, 0, 200
    rect 40, 50, 120, 100
  end
end

ClassicSketch.new(x: 20, y: 20)


To create class wrapped P3D sketch just "rp5 create classy_sketch 200 200 p3d --wrap" actually it probably doesn't matter where you put the --wrap option after create (but the order of the other variables is important)...

Sunday, 31 August 2014

Getting started with ruby-processing for wizards (best for vim users, though other editors can be used)

You have to start somewhere and for brevity I am assuming you've installed ruby-processing-2.6.1 here:-
rp5 create my_sketch 200 200
rp5 watch my_sketch.rb


In a new terminal start up vim
vim my_sketch.rb

def setup
  size 200, 200
end

def draw

end

Edit the sketch to change its background

def setup
  size 200, 200
end

def draw
  background 0
end

On saving the sketch refreshes for you

Now add a colored ellipse

def setup
  size 200, 200
end

def draw
  background 0
  fill 200, 0, 0
  ellipse 100, 100, 120, 100
end

Save the sketch again and is update once more, how cool is that!!!

Now this all easiest done using vim (I mean you only need two consoles and you are cooking, but you could do the same with emacs, jEdit, eric, textmate to name but a few decent editors). With processing-2.2.1 can also be use with PDF, P2D and P3D modes "rp5 create my_sketch 200 200 p3d" works to create P3D sketches. Now as far as I know there is no book on ruby processing but there is Dan Shiffmans nature of code, and the examples have been translated to ruby-processing here or for the beginner there is his Learning Processing examples translated here. Then there is the 300+ examples included with ruby-processing.........

Thursday, 21 August 2014

JRuby-9000 returns for testing


Well it seems that jruby-head is ready for testing again!!!
jruby --version
SNAPSHOT (2.1.2) 2014-08-20 8fb3b63 Java HotSpot(TM) 64-Bit Server VM 25.20-b15 on 1.8.0_20-ea-b15 [linux-amd64]

..... after a bit of short lived euphoria, it seems it is still broken after all (I was using jruby-complete-1.7.13) in my initial tests ('cos I'd configured it that way mea culpa) so jruby-head is failing to load jars, I'd seen that reported before, will have to look into it.....
However problem seems to be a failure to load yaml configuration from file, see irb (mri) and jirb (ruby-head) below:-
>> require 'psych'
=> true
>> RP_CONFIG = Psych.load_file("/home/tux/.rp5rc")
=> {"PROCESSING_ROOT"=>"/home/tux/processing-2.2.1", "JRUBY"=>"true"}
>>

tux@monkstone:~/Desktop$ jirb
Picked up _JAVA_OPTIONS: -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel
>> require 'psych'
=> true
>> RP_CONFIG = Psych.load_file("/home/tux/.rp5rc")
=> false
>>


Well I created a workaround (load yaml from File, and Psych.load(yaml)) which gets me around that issue. Problem is majority of sketches now crash with an uncaught exception from the processing "Animation" thread (I've filed a bug report, but I am not holding my breath for a response, given my past experience).

Another example of a sketch using map1d in JRubyArt (development version of ruby-processing)

With every sketch that I look at with fresh eyes, the more convinced I am that ruby-processing does not need to (probably should not) ape processing convenience functions, and should where possible use idiomatic ruby. In this sketch we also use ruby array max method (built into ruby) in place of the processing convenience method:-
# Histogram. 
# 
# Calculates and displays the distribution of brightness levels 
# across an image, as a histogram. In this case the x scale is
# used for the brighness levels 0 .. 255, and the y scale is the
# frequency distribution. Note use of map1d function to map the
# ranges. Also for greater efficiency we load the image pixels.

attr_reader :hist

def setup
  size(640, 360)

  # Load an image from the data directory
  # Load a different image by modifying the comments
  img = load_image('frontier.jpg')
  image(img, 0, 0)
  img.load_pixels
  @hist = Array.new(256, 0)

  # Calculate the histogram
  (0 ... img.width).each do |i|
    (0 ... img.height).each do |j|
      bright = (brightness(img.pixels[j * img.width + i]))
      hist[bright] += 1
    end
  end

  # Find the largest value in the histogram using 'ruby' array max function
  hist_max = hist.max

  stroke(255)
  # Draw half of the histogram (skip every second value)
  (0 ... img.width).step(2) do |i|
    # Map i (from 0..img.width) to a location in the histogram (0..255)
    which = map1d(i, (0 .. img.width), (0 .. 255))
    # Convert the histogram value to a location between 
    # the bottom and the top of the picture
    y = map1d(hist[which], (0 .. hist_max), (img.height .. 0))
    line(i, img.height, i, y)
  end
end

Wednesday, 20 August 2014

A more ruby like constrain (for JRubyArt , ruby-processing development)

Having written a more ruby like version of the processing map method, I realized it might be better to have constrain also use a range input:-
# explicitly provide a ruby-processing constrain instance method
# to return a float:- amt, where range = (low .. high)
def constrain(amt, range)
  return amt if range.include? amt
  return range.first if amt < range.first
  return range.end
end

Here is a use case example, with excluded top value version of range
#
# A Processing implementation of Game of Life
# By Joan Soler-Adillon
#
# Press SPACE BAR to pause and change the cell's values with the mouse
# On pause, click to activate/deactivate cells
# Press R to randomly reset the cells' grid
# Press C to clear the cells' grid
#
# The original Game of Life was created by John Conway in 1970.
#

CELL_SIZE = 5
ALIVE = true
DEAD = false
ALIVE_START = 150
INTERVAL = 100

attr_reader :pause, :cells, :row, :column, :last_time, :alive, :cells_buffer

def setup
  size(960, 640)
  @pause = false
  # Instantiate arrays 
  @row = width / CELL_SIZE
  @column = height / CELL_SIZE
  @cells = Array.new(row) {Array.new(column) {(rand(1000) > ALIVE_START)? DEAD : ALIVE}}
  @last_time = 0
  @alive = color(100, 255, 100)
  # This stroke will draw the background grid (live cells)
  stroke(48, 100)
  noSmooth
end

def draw
  background(0)
  #Draw live cells
  row.times do |x|
    column.times do |y|
      if (cells[x][y])
        fill(alive)
        rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
      end
    end
  end
  # Iterate if timer ticks
  if (millis - last_time > INTERVAL)
    if (!pause)
      tick!
      @last_time = millis
    end
  end

  # Create  new cells manually on pause
  if (pause && mouse_pressed?)
    # # Map and avoid out of bound errors
    over_x = (map(mouse_x, 0, width, 0, row)).to_i
    over_x = constrain(over_x, (0 ... row))
    over_y = (map(mouse_y, 0, height, 0, column)).to_i
    over_y = constrain(over_y, (0 ... column))

    # Check against cells in buffer
    if (cells_buffer[over_x][over_y])  # Cell is alive
      cells[over_x][over_y] = DEAD # Kill
      fill(0) #reflect changed status
    else  # Cell is dead
      cells[over_x][over_y] = ALIVE # Make alive
      fill(alive) # Fill alive color
    end

  elsif (pause && !mouse_pressed?)  # And then save to buffer once mouse goes up
    # Save cells to buffer (so we operate with one array keeping the other intact)
    @cells_buffer = clone2d(cells)
  end
end

def tick!  # When the clock ticks
  # Save cells to buffer (so we operate with one array keeping the other intact)
  @cells_buffer = clone2d(cells)
  # Visit each cell:
  row.times do |x|
    column.times do |y|
      # And visit all the neighbours of each cell
      neighbours = 0 # We'll count the neighbours
      (x - 1 .. x + 1).each do |xx|
        (y - 1 .. y + 1).each do |yy|
          # Make sure you are not out of bounds
          if [(xx>=0), (xx<row), (yy>=0), (yy<column)].all? {|in_bounds| in_bounds == true}
            # Make sure to check against self
            if ![(xx == x), (yy == y)].all? {|is_self| is_self == true}
              if (cells_buffer[xx][yy]) # true == ALIVE
                neighbours += 1 # Check alive neighbours and count them
              end # alive
            end # End of if self
          end # End of if grid bounds
        end # End of yy loop
      end #End of xx loop
      # We've checked the neighbours: apply rules in one line (only in ruby)!
      cells[x][y] = (cells_buffer[x][y])?  ((2 .. 3) === neighbours) : (neighbours == 3)
    end # End of y loop
  end # End of x loop
end # End of function

def key_pressed
  case key
  when 'r', 'R'
    # Restart: reinitialization of cells    
    @cells = Array.new(row) {Array.new(column) {(rand(1000) > ALIVE_START)? DEAD : ALIVE}}
  when ' ' # On/off of pause
    @pause = !pause
  when 'c', 'C' # Clear all
    @cells = Array.new(row) {Array.new(column) {DEAD}}
  end
end

def clone2d array
  result = []
  array.each do |val|
    result << val.clone
  end
  return result
end

Here using included top value version:-
# Based on SeekingNeural example by Daniel Shiffman
# The Nature of Code
# http://natureofcode.com

load_library :vecmath


module SeekingNeural
  class Perceptron
    # Perceptron is created with n weights and learning constant
    def initialize(n, c)
      @weights = Array.new(n) { rand(0 .. 1.0) }
      @c = c
    end

    # Function to train the Perceptron
    # Weights are adjusted based on vehicle's error
    def train(forces, error)
      trained = @weights.zip(forces.map { |f| f.to_a }
        .map { |a, b| (a * error.x + b * error.y) * @c })
      .map { |w, c| constrain(w + c, (0.0 .. 1.0)) }
      @weights = trained
    end

    # Give me a steering result
    def feedforward(forces)
      # Sum all values
      forces.zip(@weights).map { |a, b| a * b }.inject(Vec2D.new, :+)
    end
  end

  # Seek
  # Daniel Shiffman <http://www.shiffman.net>

  class Vehicle
    MAX_SPEED = 4
    MAX_FORCE = 0.1

    attr_reader :brain, :sz, :location, :targets, :desired

    def initialize(n, x, y)
      @brain = Perceptron.new(n, 0.001)
      @acceleration = Vec2D.new
      @velocity = Vec2D.new
      @location = Vec2D.new(x, y)
      @sz = 6.0
    end

    # Method to update location
    def update(width, height)
      # Update velocity
      @velocity += @acceleration
      # Limit speed
      @velocity.set_mag(MAX_SPEED) { @velocity.mag > MAX_SPEED }
      @location += @velocity
      # Reset acceleration to 0 each cycle
      @acceleration *= 0
      @location.x = constrain(location.x, (0 .. width))
      @location.y = constrain(location.y, (0 .. height))
    end

    def apply_force(force)
      # We could add mass here if we want A = F / M
      @acceleration += force
    end

    # Here is where the brain processes everything
    def steer(targets, desired)
      # Steer towards all targets
      forces = targets.map { |target| seek(target) }

      # That array of forces is the input to the brain
      result = brain.feedforward(forces)

      # Use the result to steer the vehicle
      apply_force(result)

      # Train the brain according to the error
      error = desired - location
      brain.train(forces, error)
    end

    # A method that calculates a steering force towards a target
    # STEER = DESIRED MINUS VELOCITY
    def seek(target)
      desired = target - location  # A vector pointing from the location to the target

      # Normalize desired and scale to the maximum speed
      desired.normalize!
      desired *= MAX_SPEED
      # Steering = Desired minus velocity
      steer = desired - @velocity
      steer.set_mag(MAX_FORCE) { steer.mag > MAX_FORCE } # Limit to a maximum steering force
      steer
    end

    def display

      # Draw a triangle rotated in the direction of velocity
      theta = @velocity.heading + Math::PI / 2
      fill(175)
      stroke(0)
      stroke_weight(1)
      push_matrix
      translate(location.x, location.y)
      rotate(theta)
      begin_shape
        vertex(0, -sz)
        vertex(-sz * 0.5, sz)
        vertex(sz * 0.5, sz)
      end_shape(CLOSE)
      pop_matrix
    end
  end
end

include SeekingNeural

# A Vehicle controlled by a Perceptron
attr_reader :targets, :desired, :v


def setup
  size(640, 360)
  # The Vehicle's desired location
  @desired = Vec2D.new(width / 2, height / 2)

  # Create a list of targets
  make_targets

  # Create the Vehicle (it has to know about the number of targets
  # in order to configure its brain)
  @v = Vehicle.new(targets.size, rand(width), rand(height))
end

# Make a random ArrayList of targets to steer towards
def make_targets
  @targets = Array.new(8) { Vec2D.new(rand(width), rand(height)) }
end

def draw
  background(255)

  # Draw a circle to show the Vehicle's goal
  stroke(0)
  stroke_weight(2)
  fill(0, 100)
  ellipse(desired.x, desired.y, 36, 36)

  # Draw the targets
  targets.each do |target|
    no_fill
    stroke(0)
    stroke_weight(2)
    ellipse(target.x, target.y, 16, 16)
    line(target.x, target.y - 16, target.x, target.y + 16)
    line(target.x - 16, target.y, target.x + 16, target.y)
  end

  # Update the Vehicle
  v.steer(targets, desired)
  v.update(width, height)
  v.display
end

def mouse_pressed
  make_targets
end

Thursday, 14 August 2014

Experimental map1d method JRubyArt (ruby-processing development)

The recently updated JRubyArt has now removed the processing convenience functions that were deprecated in ruby-processing-2.5.1. Other functions such as map are retained, but I wondered would a map1d range method be a more ruby like alternative:-
# @param [float] val
# @param [range(start1 .. end1)] r_in
# @param [range(start2 .. end2)] r_out
# @return [float] mapped value
#
def map1d(val, r_in, r_out)
  r_out.first + (r_out.last - r_out.first) * ((val - r_in.first).to_f / (r_in.last - r_in.first))
end

# usage map1d(5, (0 .. 10), (10 .. 110))           exclude end is false

puts map1d(5, (0 .. 10), (10 .. 110))

# output 60.0

Here is the method in action
# Mandelbrot Set example
# by Jordan Scales (http://jordanscales.com)
# 27 Dec 2012
# Modified to use map1d (instead of map), and somewhat 
# optimized (update_pixels instead of set, and hypot for abs)

# default size 900x600
# no need to loop
def setup
  size 900, 600
  load_pixels
  no_loop
end

# main drawing method
def draw
  (0 ... 900).each do |x|
    (0 ... 600).each do |y|
      c = Complex.new(map1d(x, (0 .. 900), (-3 .. 1.5)), map1d(y, (0 .. 600), (-1.5 .. 1.5)))
      # mandel will return 0 to 20 (20 is strong)
      #   map this to 0, 255 (and flip it)
      pixels[x + y * 900] = color(255 - map1d(mandel(c, 20), (0 .. 20), (0 .. 255)).to_i)
    end
  end
  update_pixels
end

# calculates the "accuracy" of a given point in the mandelbrot set
#    : how many iterations the number survives without becoming chaotic
def mandel(z, max = 10)
  score = 0
  c = z.clone
  while score < max 
    # z = z^2 + c
    z.square
    z.add c
    break if z.abs > 2

    score += 1
  end

  score
end


# rolled my own Complex class
#   stripped of all functionality, except for what I needed (abs, square, add, to_s)
#   
#   Using this class, runs in ~12.5s on my MacBook Air
#     compared to ~22s using ruby's Complex struct
class Complex

  attr_accessor :real, :imag

  def initialize(real, imag)
    @real = real
    @imag = imag
  end

  # square a complex number - overwriting it
  def square
    r = real * real - imag * imag
    i = 2 * real * imag

    @real = r
    @imag = i
  end

  # add a given complex number
  def add(c)
    @real += c.real
    @imag += c.imag
  end

  # compute the magnitude
  def abs
    hypot(real, imag)
  end

  def to_s
    "#{real} + #{imag}i"
  end

end

PS: I have not tested whether Jordans findings re: ruby complex implementation still hold true (jruby truffle claims wicked fast mandelbrot calculations). Benchmarking is tricky with jvm warmup up time, I have replaced sqrt(real * real + imag * imag) (abs method) with hypot(real, imag), which should be more efficient...

Followers

About Me

My Photo
Consolidating my online identity as monkstone. I am a 64 bit linux user and advocate of open source software, you can sometimes find me on the processing forum.