Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0

Showing posts with label MDArray. Show all posts
Showing posts with label MDArray. Show all posts

Saturday, 4 October 2014

Rubygems with latest JRubyArt

With regular ruby-processing, the only way to use gems was to use an installed version of jruby (well apart from using some complicated bundler tool). Interestingly currently you seem to be able to use jruby-complete to launch a sketch with JRubyArt (providing youve've installed the gem with your installed jruby). Now the interest in using jruby-complete is all about being able to run sketches that load_image, shaders etc. Many sketches run just fine with an installed jruby.
                                                   
java -jar jruby-complete game_of_life.rb                                    

Here is the sketch
# game_of_life.rb featuring MDArray in ruby-processing
# 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.
#
require 'jruby_art'
require 'mdarray'

class GameOfLife < Processing::App
  CELL_SIZE = 5
  ALIVE = true
  DEAD = false
  ALIVE_START = 150
  WIDTH = 960
  HEIGHT = 640
  SKIP = 10
  INTERVAL = 100

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

  def setup
    size WIDTH, HEIGHT
    @row = WIDTH / CELL_SIZE
    @column = HEIGHT / CELL_SIZE
    background 0
    stroke_weight 2
    @last_time = 0
    @pause = false
    @cells = MDArray.boolean([row, column], random_data)
    @alive = color(100, 255, 100)
    stroke(48, 100)
    no_smooth
  end

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

    # Create  new cells manually on pause
    if pause && mouse_pressed?
      # Map and avoid out of bound errors (use map1d range and range.clip)
      x_cell_over = (map1d(mouse_x, (0..width), (0..row))).to_i
      x_cell_over = (0..row - 1).clip(x_cell_over)
      y_cell_over = (map1d(mouse_y, (0..height), (0..column))).to_i
      y_cell_over = (0..column - 1).clip(y_cell_over)

      # Check against cells in buffer
      if cells_buffer.get([x_cell_over, y_cell_over])  # Cell is alive
        cells.set([x_cell_over, y_cell_over], DEAD) # Kill
        fill(0) # reflect changed status
      else  # Cell is dead
        cells.set([x_cell_over, y_cell_over], 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 = cells.copy
    end
  end

  def tick!  # When the clock ticks
    # Save cells to buffer (so we operate with one array keeping the other intact)
    @cells_buffer = cells.copy
    # Visit each cell:
    (0...row).each do |x|
      (0...column).each 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
            next unless [(xx >= 0), (xx < row), (yy >= 0), (yy < column)].all? { |in_bounds| in_bounds == true }
            # Make sure to check against self
            unless [(xx == x), (yy == y)].all? { |is_self| is_self == true }
              if cells_buffer.get([xx, yy]) # true == ALIVE
                neighbours += 1 # Check alive neighbours and count them
              end # alive
            end # End of if self
          end # End of yy loop
        end # End of xx loop
        # We've checked the neighbours: apply rules in one line (only in ruby)!
        cells.set([x, y], (cells_buffer.get([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 = MDArray.boolean([row, column], random_data)
    when ' ' # On/off of pause
      @pause = !pause
    when 'c', 'C' # Clear all
      @cells = MDArray.boolean([row, column], DEAD)
    end
  end

  def random_data
    (0...row * column).map { rand(1000) < ALIVE_START }
  end
end

GameOfLife.new(title: 'Game of Life mdarray version')

Thursday, 3 April 2014

Enhance ruby-processing functionality with the rpbundle gem

One of the annoyances of ruby-processing has been the need to use an installed jruby to use rubygems (well in itself it is not an annoyance), because this bars us from using rubygems with sketches that require jruby-complete to run (mainly load_image but also shader sketches, and this has been the annoyance). Thanks to the recently released rpbundle gem this is no longer a problem since Emil Soman has figured a way to use bundler with jruby-complete (and gems installed by rpbundle are accessible to ruby-processing using the vendored jruby-complete). Why is this exciting well for the first time I will be able to experiment with ai4r gem with loaded images, but also mdarray (similar to numpy) so scope for image transformations. Installing rpbundle is very straightforward if you already use bundler and/or rvm user, and not much harder if you eschew both of them, you need only add the .rpbundle/bin to your path.
You need to create a Gemfile in your working folder eg:-
source 'https://rubygems.org'
gem "ai4r", "~> 1.13"

# if using jruby it may pay to specify engine


Here is the working sketch which after rpbundle install to install the ai4r gem you run with rpbundle exec run/watch sketch.rb

#####################################################################
# Using the ai4r gem in ruby-processing.
# A simple example that demonstrates using
# a backpropagation neural network. Use the drop box menu to
# select a prebuilt shape. To draw a test shape tick drawing checkbox,
# release the mouse when drawing a discontinous shape eg cross.
# Clear the sketch with clear button. 
# Press evaluate and result is printed to the console....
####################################################################

require 'ai4r'
require 'json'

load_library :vecmath, :control_panel

attr_reader  :img, :img_pixels, :ci_input, :cr_input, :tr_input, :sq_input, :net, :points, :panel, :hide, :drawing, :source_string

def setup
  size(320, 320)
  control_panel do |c|
    c.title = "control"
    c.look_feel "Nimbus"
    c.checkbox :drawing
    c.button :clear
    c.button :evaluate
    c.menu :shape, ['CIRCLE', 'CROSS', 'CROSS_WITH_NOISE', 'SQUARE', 'SQUARE_WITH_NOISE', 'TRIANGLE', 'DEFAULT']
    @panel = c
  end
  @hide = false
  @source_string = open("data/data.json", "r"){ |file| file.read }
  triangle = JSON.parse(source_string)["TRIANGLE"]
  square = JSON.parse(source_string)["SQUARE"]
  cross = JSON.parse(source_string)["CROSS"]
  circle = JSON.parse(source_string)["CIRCLE"]
  @points = []
  srand 1
  @net = Ai4r::NeuralNetwork::Backpropagation.new([256, 3])
  @tr_input = triangle.flatten.collect { |input| input.to_f / 127.0}
  @sq_input = square.flatten.collect { |input| input.to_f / 127.0}
  @cr_input = cross.flatten.collect { |input| input.to_f / 127.0}
  @ci_input = circle.flatten.collect { |input| input.to_f / 127.0}
  train
  background 255
end


def draw
  # only make control_panel visible once, or again when hide is false
  unless hide
    @hide = true
    panel.set_visible(hide)
  end
  if drawing
    stroke_weight 32
    stroke 127
    points.each_cons(2) { |ps, pe| line ps.x, ps.y, pe.x, pe.y}
  else
    no_fill
    stroke_weight(32)
    stroke(127)
    case @shape
    when 'CIRCLE'
      background(255)
      img = load_image('circle.png')
      image(img, 0, 0)
      @shape = 'DEFAULT'
    when 'CROSS'
      img = load_image('cross.png')
      image(img, 0, 0)
      @shape = 'DEFAULT'
    when 'CROSS_WITH_NOISE','SQUARE_WITH_NOISE'
      background(255)
      draw_shape @shape
      @shape = 'DEFAULT'
    when 'SQUARE'
      img = load_image('square.png')
      image(img, 0, 0)
      background(255)
      @shape = 'DEFAULT'
    when 'TRIANGLE'
      img = load_image('triangle.png')
      image(img, 0, 0)
      @shape = 'DEFAULT'
    end
  end
end

def draw_shape shp
  shape = JSON.parse(source_string)[shp]
  background(255)
  no_stroke
  (0  ... width / 20).each do |i|
    (0  ... height / 20).each do |j|
      col = 255 - shape[i][j]
      fill(col)
      rect(i * 20, j * 20,  20,  20)
    end
  end
end

def train
  puts "Training Network Please Wait"
  101.times do |i|
    error = net.train(tr_input, [1.0, 0, 0])
    error = net.train(sq_input, [0, 1.0, 0])
    error = net.train(cr_input, [0, 0, 1.0])
    error = net.train(ci_input, [0, 1.0, 1.0])
    puts "Error after iteration #{i}:\t#{format("%.5f", error)}" if i%20 == 0
  end
end

def result_label(result)
  if result.inject(0, :+).between?(1.9, 2.1)
    if result[0] < 0.01 && result[1].between?(0.99, 1.0) && result[2].between?(0.99, 1.0)
      return "CIRCLE"
    else
      return "UNKNOWN"
    end
  elsif result.inject(0, :+).between?(0.95, 1.1)
    if result[0].between?(0.95, 1.0) && (result[1] + result[2]) < 0.01
      return "TRIANGLE"
    elsif result[1].between?(0.95, 1.0) && (result[0] + result[2]) < 0.01
      return "SQUARE"
    elsif result[2].between?(0.95, 1.0) && (result[1] + result[0]) < 0.01
      return "CROSS"
    else
      return "UNKNOWN"
    end
  end
  return "UNKNOWN"
end

def mouse_dragged
  points << Vec2D.new(mouse_x, mouse_y)
end

def mouse_released
  points.clear
end

def draw_circle
  ellipse(width / 2, height / 2, 320 - 32, 320 - 32)
end

def draw_square
  rect(16, 16, 320 - 32, 320 - 32)
end

def draw_cross
  line(width / 2, 0, width / 2, 320)
  line(0, height / 2,  320 , height / 2)
end

def draw_triangle
  triangle(width / 2, 32, 24, height - 16,  width - 24, height - 16)
end

def clear
  background 255
end

def evaluate
  load_pixels
  img_pixels = []
  (0...height).step(20) do |y|
    row = []
    (0...width).step(20) do |x|
      row << 255 - brightness(pixels[(y + 10) * width + x + 10])
    end
    img_pixels << row
  end
  puts "#{net.eval(img_pixels.flatten).inspect} => #{result_label(net.eval(img_pixels.flatten))}"
end

Tuesday, 6 August 2013

Conways Game of Life in ruby-processing (featuring MDArray)

# game_of_life.rb featuring MDArray in ruby-processing
# 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.
#

require 'mdarray'

CELL_SIZE = 5
ALIVE = true
DEAD = false
ALIVE_START = 150
WIDTH = 960
HEIGHT = 640
SKIP = 10
INTERVAL = 100

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

# signature-specific aliases for overloaded methods
java_alias :my_color, :color, [Java::float, Java::float, Java::float]
java_alias :my_fill, :fill, [Java::int]
java_alias :my_stroke, :stroke, [Java::float, Java::float]
java_alias :my_background, :background, [Java::int]

def setup
  size WIDTH, HEIGHT
  @row = WIDTH / CELL_SIZE
  @column = HEIGHT / CELL_SIZE
  stroke_weight 2
  @last_time = 0
  @pause = false
  @cells = MDArray.boolean([row, column], random_data)
  @alive = my_color(100, 255, 100)
  my_stroke(48, 100)
  no_smooth
end

def draw
  my_background(0)
  #Draw live cells
  (0 ... row).each do |x|
    (0 ... column).each do |y|
      if (cells.get([x, y]))
        my_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
    x_cell_over = (map(mouse_x, 0, width, 0, row)).to_i
    x_cell_over = constrain(x_cell_over, 0, row - 1)
    y_cell_over = (map(mouse_y, 0, height, 0, column)).to_i
    y_cell_over = constrain(y_cell_over, 0, column - 1)

    # Check against cells in buffer
    if (cells_buffer.get([x_cell_over, y_cell_over]))  # Cell is alive
      cells.set([x_cell_over, y_cell_over], DEAD) # Kill
      my_fill(0) #reflect changed status
    else  # Cell is dead
      cells.set([x_cell_over, y_cell_over], ALIVE) # Make alive
      my_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 = cells.copy
  end
end

def tick!  # When the clock ticks
  # Save cells to buffer (so we operate with one array keeping the other intact)
  @cells_buffer = cells.copy
  # Visit each cell:
  (0 ... row).each do |x|
    (0 ... column).each 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.get([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.set([x, y], (cells_buffer.get([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 = MDArray.boolean([row, column], random_data)
  when ' ' # On/off of pause
    @pause = !pause
  when 'c', 'C' # Clear all
    @cells = MDArray.boolean([row, column], DEAD)
  end
end

def random_data
  data = []
  (0 ... row * column).each do
    data << (rand(1000) < ALIVE_START)
  end
  return data
end

Saturday, 3 August 2013

Using MDArray in ruby-processing

Well quite excited with this jruby gets its own flavour of numpy, namely MDArray. I had to give it a go here is a simple example:-
#  Demonstrates the syntax for creating a two-dimensional (2D) array,
#  fromfunction (actually a block) using MDArray (for jruby).
#  Values in a 2D array are accessed through two index values.  
#  2D arrays are useful for storing images. In this example, each dot 
#  is colored in relation to its distance from the center of the image.

require 'mdarray'

WIDTH=640
HEIGHT=360
SKIP=10

def setup
  size WIDTH, HEIGHT
  background 0
  stroke_weight 2
  max_distance = ( (WIDTH / 2 - WIDTH)**2  + (HEIGHT / 2 - HEIGHT)**2 )**0.5
  distances = MDArray.fromfunction("float", [WIDTH, HEIGHT]) do |x, y|
    255 * ( (WIDTH / 2 - x)**2  + (HEIGHT / 2 - y)**2 )**0.5 / max_distance
  end
  (SKIP ... WIDTH).step(SKIP) do |x|
      (SKIP ... HEIGHT).step(SKIP) do |y|
        stroke distances[x, y]
        point x, y
      end
  end
end


According to the limited documentation it might be more efficient to use the accessor to "get" data rather than using bare array access see below. For actual image manipulation in ruby-processing we might be in a bind, we require external jruby to access the gem, but that appears not to work with PImage sketches?
distances.get([x, y])

Followers

About Me

My photo
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2