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

Showing posts with label gems. Show all posts
Showing posts with label gems. Show all posts

Wednesday, 29 October 2014

Toxiclibs (or any other processing library) as a rubygem

As a proof of principle I have created gem that wraps some of Karsten Schmidts (aka toxi @toxi, aka postspectacular k@thi.ng) toxiclibs as a ruby-gem. This gem has been shown to usable with ruby-processing (or its development version JRubyArt) but if yokolet is right it can also be used with clojure, and other jvm languages (by using JRuby Embed, formerly RedBridge). Here is more on RedBridge and clojure.
require 'toxiclibs'

include Toxi

#
# <p>GrayScottToneMap shows how to use the ColorGradient & ToneMap classes of the
# colorutils package to create a tone map for rendering the results of
# the Gray-Scott reaction-diffusion.</p>
#
# <p><strong>Usage:</strong><ul>
# <li>click + drag mouse to draw dots used as simulation seed</li>
# <li>press any key to reset</li>
# </ul></p>
#

#
# Copyright (c) 2010 Karsten Schmidt
#
# This demo & library is free software you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation either
# version 2.1 of the License, or (at your option) any later version.
#
# http://creativecommons.org/licenses/LGPL/2.1/
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

NUM_ITERATIONS = 10

attr_reader :gs, :tone_map

def setup
  size(256,256)
  @gs= Toxi::GrayScott.new width,height, false
  @gs.set_coefficients 0.021, 0.076, 0.12, 0.06
  # create a color gradient for 256 values
  grad = Toxi::ColorGradient.new
  # NamedColors are preset colors, but any TColor can be added
  # see javadocs for list of names:
  # http://toxiclibs.org/docs/colorutils/toxi/color/NamedColor.html
  # NB: use '::' in place of '.' here for these java constants
  grad.add_color_at(0, Toxi::NamedColor::BLACK)
  grad.add_color_at(128, Toxi::NamedColor::RED)
  grad.add_color_at(192, Toxi::NamedColor::YELLOW)
  grad.add_color_at(255, Toxi::NamedColor::WHITE)
  # this gradient is used to map simulation values to colors
  # the first 2 parameters define the min/max values of the
  # input range (Gray-Scott produces values in the interval of 0.0 - 0.5)
  # setting the max = 0.33 increases the contrast
  @tone_map = Toxi::ToneMap.new 0, 0.33, grad
end

def draw
  @gs.set_rect(mouse_x, mouse_y, 20, 20) if mouse_pressed?
  load_pixels
  # update the simulation a few time steps
  NUM_ITERATIONS.times { @gs.update(1) }
  # read out the V result array
  # and use tone map to render colours
  gs.v.length.times do |i|
    pixels[i]=tone_map.getARGBToneFor(gs.v[i])  # NB: don't camel case convert here
  end
  update_pixels
end

def key_pressed
  @gs.reset
end



Here is another example running a JRubyArt sketch from netbeans.

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

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