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 which I have recently updated to processing-2.1.2 and https://github.com/monkstone/cf3ruby for my version of the cfdg DSL (context-free-art)

Thursday, 24 April 2014

Netbeans-8 rubyplugin now available (run ruby files from the ide)

Well this will no doubt be useful for ruby-processing development:-

Saturday, 19 April 2014

Testing the water for more jruby integration

For some time now I've been thinking about implementing stuff in jruby java for ruby-processing. To that end I have forked yokolets Fraction class here. This could be a sort of half-way house where the guts could be implemented in pure java, but with a compiled jruby java interface, but I am sure I would do better with 100% jruby implementation of say my vecmath library (which currently is pure ruby). For performance, it is possible (but by no means a certainty to me) that SinCos lut tables would improve ruby-processing performance, but they will definetly need jruby java implementation to work. Charles nutter recently cited this as possible model for writing a jruby extension other people point to jrubies RubyEnumerable implementation.

Wednesday, 16 April 2014

Latest ruby-processing release 2.4.4

The latest release ruby-processing-2.4.4 has been updated to use the just released jruby-1.7.12 (which is mainly bug-fixes). Coincidentally a new version of vanilla processing (version 2.1.2) has also just been released. I have been testing this processing-2.1.2 during its development on my linux box, and the most significant change for ruby-processing users is that we are now able to use watch mode with P2D and P3D, but there have been other changes that might make this release work better generally on MacOSX. I am therefore recommending that all ruby-processing users update processing to the latest version. One caveat is that I expect application export to be broken on the Mac, it should not be too difficult to fix but I haven't got a Mac, so I'm hoping someone supplies a fix.

Tuesday, 8 April 2014

Ruby-Processing Architecture

Recently I watched a presentation by Hisaro Asari on JRuby which inspired me to produce this for ruby-processing:-

The interesting from this talk was the future direction of JRuby including replacing the current the Lexical Analysis parsing to an AST with a Semantic Analysis to an Intermediate Representation. This will allow for optimization, before passing to compiler (via Control Flow Graph, and Data Flow Graph analysis?). In the further future there may be foreign function interface support at the jvm level (Charlie has submitted an ffi proposal, 2016 at earliest), and there is the experimental truffle and graaal. What is clear is there will be no further support for ruby-2.0.0 (experimental in JRuby 1.7) with JRuby-9000 aiming at ruby-2.1.0 support.


Seems we may not need to wait too long to test the new Intermediate Representation runtime (and then we might also be able to start using ruby-2.1.0 syntax?).

Monday, 7 April 2014

Funked up drawolver

Now draw multiple shapes, use 'c' key to clear.
# Composite Drawolver: draw 2D & revolve 3D

# Example to show how to use the VecMath library.
# Also features the use each_cons, possibly a rare 
# use for this ruby Enumerable method?
# From an original by fjenett (funked up by monkstone)
# now uses 'zip' and 'each', in place of a custom Array object
# with a 'one_of_each' method

load_library :vecmath

attr_reader :drawing_mode, :points, :rot_x, :rot_y, :vertices

def setup
  size 1024, 768, P3D
  frame_rate 30
  reset_scene
end

def draw
  background 0
  if (!drawing_mode)
    translate(width/2, height/2)
    rotate_x rot_x
    rotate_y rot_y
    @rot_x += 0.01
    @rot_y += 0.02
    translate(-width/2, -height/2)
  end
  no_fill
  stroke 255
  points.each_cons(2) { |ps, pe| line ps.x, ps.y, pe.x, pe.y}

  if (!drawing_mode)
    stroke 125
    fill 120
    lights
    ambient_light 120, 120, 120
    vertices.each_cons(2) do |r1, r2|
      begin_shape(TRIANGLE_STRIP)
      r1.zip(r2).each do |v1, v2|
        vertex v1.x, v1.y, v1.z
        vertex v2.x, v2.y, v2.z
      end
      end_shape
    end
  end
end

def reset_scene
  @drawing_mode = true
  @points = []
  @rot_x = 0.0
  @rot_y = 0.0
end

def mouse_pressed
  reset_scene
  points << Vec3D.new(mouse_x, mouse_y)
end

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

def mouse_released
  points << Vec3D.new(mouse_x, mouse_y)
  recalculate_shape
end

def key_pressed
  case key
  when 'c'
    @vertices.clear
    reset_scene
  end
end

def recalculate_shape
  @vertices ||= []
  points.each_cons(2) do |ps, pe|
    b = points.last - points.first
    b.normalize!
    a = ps - points.first
    dot = a.dot b
    b = b * dot
    normal = points.first + b
    c = ps - normal
    vertices << []
    ecos = ->(n, c, a){n +c * cos(a)}
    esin = ->(c, a){c.mag * sin(a)}
    (0..TAU).step(PI/15) do |ang|
      e = ecos.call(normal, c, ang)
      e.z = esin.call(c, ang)
      vertices.last << e
    end
  end
  @drawing_mode = false
end

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

Wednesday, 19 March 2014

Time to embrace jdk1.8.0 for ruby-processing (don't try this with vanilla processing).

There are performance improvements to be had if you switch to using jdk8 with ruby-processing, if you are a jEdit user it is dead simple just modify the bsh macro (gist below) to suit your system. While you are at it you can experiment with ruby-2.0 syntax (current default is ruby-1.9.3 with jruby). Check it with esefera.rb (starts of at moderate speed, even slows down briefly but the picks up again as the optimizations kick in). See below or gist (embedded gist looked crap).
// rp5.bsh
//setenv("JAVA_HOME", "/opt/jdk1.7.0_51");
setenv("JAVA_HOME", "/opt/jdk1.8.0");
setenv("GEM_HOME", "/home/tux/.gem/ruby/2.1.0");
setenv("GEM_PATH", "/home/tux/.gem/ruby/2.1.0");
new console.commando.CommandoDialog(view,"commando.rp5");


<!-- rp5.xml monkstone, 2013-August-16 for ruby-processing > 2.0.12 -->
<COMMANDO>
<UI>
<CAPTION LABEL="Run">
<FILE_ENTRY LABEL="ruby file" VARNAME="file" EVAL="buffer.getName()"/>
</CAPTION>
<CAPTION LABEL="Path to rp5">
<ENTRY LABEL="path" VARNAME="rp5path" DEFAULT=""/>
</CAPTION>
<CAPTION LABEL="Choose Run/Watch/Create/App">
<CHOICE LABEL="Select" VARNAME="type" DEFAULT="run" >
<OPTION  LABEL="run" VALUE="run"/>
<OPTION LABEL="watch" VALUE="watch"/>
<OPTION LABEL="create" VALUE="create"/>
<OPTION LABEL="export app" VALUE="app"/>
<OPTION 
</CHOICE>
</CAPTION>
<CAPTION LABEL="JRuby Opt">
<TOGGLE LABEL="jruby-complete" VARNAME="jruby" DEFAULT="FALSE"/>
</CAPTION>
</UI>

<COMMANDS>

<COMMAND SHELL="System" CONFIRM="FALSE">
<!-- cd to working dir -->

   buf = new StringBuilder("cd ");
   buf.append(MiscUtilities.getParentOfPath(buffer.getPath()));
   buf.toString();

</COMMAND>



<COMMAND SHELL="System" CONFIRM="FALSE">

   buf = new StringBuilder(rp5path);
   buf.append("rp5 ");
   if (jruby){
   buf.append("--nojruby ");
   }
   buf.append(type);
   buf.append(" ");
   switch(type){
   case "run":
   case "watch":
       buf.append(file);
       break;
   }
   buf.toString();

</COMMAND>


</COMMANDS>
</COMMANDO>

F.P.S esfera.rb
framejdk7jdk8
303.282163.78027
603.749404.49563
904.433146.03558
1204.287476.36669
1504.323806.30815
1804.297456.16097
2104.322816.37150

The above table was generated by capturing fps at 30 frame intervals (so is not entirely fair), and what you don't see cf watching the sketch running live, is a slightly faster start, followed by a pronounced slowdown (presumably when the optimization kicks in). Following a bit of experimentation best results were obtained with -XX:+TieredCompilation option alone 7+ fps with jdk8.

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.