Software

From FabLabGenovaWiki
Revision as of 17:54, 18 February 2013 by Bond (talk | contribs) (Processing code)

Jump to: navigation, search

Image pre-processing

assuming GIMP:

  • Push up the contrast
  • Image ==> Mode ==> Indexed
  • B/W (1 bit palette)
  • Dithering: Floyd-Steinberg

Processing code

for the conversion from the image file to the pen tip path (Last update: Thu 14 Feb 2013, 19.39)

Step by Step:

  1. Install Processing
  2. Download and Install toxiclib
  3. Create a sketch with the code below:

<source lang="javascript"> import toxi.geom.*;

// tsp variables int particleRouteLength; Vec2D[] particles; int[] particleRoute; int maxParticles;

// image variable PImage img;

float millisLastFrame = 0; float frameTime = 0; // scale of the drawing float s = 2.0;

void setup() {

 maxParticles = 15000;
 //img = loadImage("lenna-lg_BW_loRes.png");
 img = loadImage("test.png");
 //img = loadImage("test_masa.png");
 //img = loadImage("lenna_BW_loRes2.png");
 size(img.width*(int)s, img.height*(int)s);
 //size(400, 600);
 // count black pixels
 int i;
 maxParticles = 0;
 for ( int x = 0; x < img.width; x++ ) {
   for ( int y = 0; y < img.height; y++ ) { 
     i = ( ( y * img.width ) + x ); // getting pixel index
     if ( img.pixels[i] == color( 0, 0, 0 ) ) {
       maxParticles++;
     }
   }
 }
 println("black dots: " + maxParticles);
 // allocate and fill points vector
 particles = new Vec2D[maxParticles];
 i = 0;
 int j = 0;
 for ( int x = 0; x < img.width; x++ ) {
   for ( int y = 0; y < img.height; y++ ) { 
     i = ( ( y * img.width ) + x );
     if ( img.pixels[i] == color( 0, 0, 0 ) ) {
       Vec2D p1 = new Vec2D(x, y);
       particles[j] = p1;
       j++;
     }
   }
 }
 millisLastFrame = millis();
 initPath();    // initialize path (NN heuristic)
 for (int l = 0; l < 5; l++ ) {
   // optimize path with 2-opt heuristic
   for (int k = 0; k < 1000; k++ ) optimizePath();
   // profiling ...
   frameTime = (millis() - millisLastFrame)/1000;
   millisLastFrame = millis();
   println("Frame time: " + millisLastFrame);
 }
 noLoop();

}

void initPath() {

 int temp;
 println("initializing path (NN)");
 Vec2D p1, p2;
 particleRouteLength = maxParticles;
 // array of free ramaining particles to be queried
 boolean freeParticles[] = new boolean[maxParticles]; 
 particleRoute = new int[particleRouteLength]; 
 int closestParticle;
 float distMin;
 p1 = particles[0];
 freeParticles[0] = true;
 particleRoute[0] = 0;
 // Nearest neighbor ("Simple, Greedy") algorithm path optimization:
 int i = 0, j;
 float dx, dy, distance; 
 while (i < particleRouteLength) {
   distMin = Float.MAX_VALUE; // re-initialize mimimun distance value
   closestParticle = 0;      // re-initialize closest particle
   for (j = 0; j < particleRouteLength; j++) {
     if (freeParticles[j] == false) {
       p2 = particles[j];  // get next particle to calculate distance
       dx = p1.x - p2.x;
       dy = p1.y - p2.y;
       distance = (float) (dx*dx+dy*dy);  // Only looking for closest; do not need sqrt factor!
       if (distance < distMin) {
         closestParticle = j;  // update the closest particle index
         distMin = distance;  // update the minimum distance value
       }
     }
   }
   freeParticles[closestParticle] = true; // remove the particle from the ones to be queried
   particleRoute[i] = closestParticle; //set the next particle in the path
   i++; // increment while counter
 }
 // Initial routing is complete
 frameTime = (millis() - millisLastFrame)/1000;
 millisLastFrame = millis();
 println("Frame time: " + millisLastFrame);

}

void optimizePath() {

 // 2-opt heuristic optimization:
 // Identify a pair of edges that would become shorter by reversing part of the tour.
 int temp;
 //println("optimizing path (2-opt) " );
 for (int i = 0; i < 5000; ++i) {   // 1000 tests per frame; you can edit this number.
   int indexA = floor(random(particleRouteLength - 1));
   int indexB = floor(random(particleRouteLength - 1));
   if (Math.abs(indexA  - indexB) < 2)
     continue;
   if (indexB < indexA) {  // swap A, B.
     temp = indexB;
     indexB = indexA;
     indexA = temp;
   }
   Vec2D a0 = particles[particleRoute[indexA]];
   Vec2D a1 = particles[particleRoute[indexA + 1]];
   Vec2D b0 = particles[particleRoute[indexB]];
   Vec2D b1 = particles[particleRoute[indexB + 1]];
   // Original distance:
   float  dx = a0.x - a1.x;
   float  dy = a0.y - a1.y;
   float  distance = (float) (dx*dx+dy*dy);  // only a comparison; do not need sqrt factor! 
   dx = b0.x - b1.x;
   dy = b0.y - b1.y;
   distance += (float) (dx*dx+dy*dy);  //  only a comparison; do not need sqrt factor! 
   // Possible shorter distance?
   dx = a0.x - b0.x;
   dy = a0.y - b0.y;
   float distance2 = (float) (dx*dx+dy*dy);  //  only a comparison; do not need sqrt factor! 
   dx = a1.x - b1.x;
   dy = a1.y - b1.y;
   distance2 += (float) (dx*dx+dy*dy);  // only a comparison; do not need sqrt factor! 
   if (distance2 < distance) { // Reverse tour between a1 and b0.   
     int indexhigh = indexB;
     int indexlow = indexA + 1;
     while (indexhigh > indexlow) {
       temp = particleRoute[indexlow];
       particleRoute[indexlow] = particleRoute[indexhigh];
       particleRoute[indexhigh] = temp;
       indexhigh--;
       indexlow++;
     }
   }
 }

}

void draw() {

 //image(img, 0, 0);
 image(img, width*s, height*s);
 int i = 0;
 stroke(128, 128, 255);    // Stroke color (blue)
 strokeWeight (.5);        // stroke weight
 println("in draw, n.part : " + particleRouteLength);
 String[] lines = new String[particleRouteLength];
 for (i = 0; i < particleRouteLength; i++) {
   lines[i] = particles[particleRoute[i]].x + " " + particles[particleRoute[i]].y;
 }
 saveStrings("path.txt", lines);
 // loop the particles drawing a line between successive points
 for ( i = 0; i < (particleRouteLength - 1); ++i) {
   Vec2D p1 = particles[particleRoute[i]];
   Vec2D p2 = particles[particleRoute[i + 1]];
   line(p1.x*s, p1.y*s, p2.x*s, p2.y*s);
 }
 //Aggiungo due linee per visualizzare il punto da cui parte il disegno. Bond.
 stroke(255,0,0);//Impostazioni di colore per la prima linea
 strokeWeight (10);
 point(particles[particleRoute[0]].x*s, particles[particleRoute[0]].y*s);

} </source>


Python code

TO DO

  • controllare che probabilmente la variabile defaultStepInterval non viene usata da stepMotor()
  • rifattorizzare l'algoritmo che calcola la velocità dei motori. Dobbiamo poter steppare contemporaneamente ma a velocità diversa i motori e entrambi devono finre la corsa insieme

release note

questa è la versione usata la sera del minimo. Produce immagini schiacciate.

code

<source lang="python">

  1. system parameters. Tune them following your system

PulleyRadius = 10.0 #................. pulley radius [mm] pulleyDistance = 1200.0 #............. pulley distance [mm] stepAngle = 1.8 # .................... step angle [deg] drawingScale = 0.2 #.................. drawing scale initial_position = [636, 636] fileName = "polpo.txt" defaultStepInterval = 1 # _TODO_ find some way to get this value confy

  1. end of system parameters.
              1. DO NOT EDIT BELOW THIS LINE ##########
  1. packages import

from time import sleep, clock import RPi.GPIO as GPIO from numpy import loadtxt, pi, sign, sqrt, subtract, median


  1. initialize GPIO

GPIO.cleanup() GPIO.setmode(GPIO.BOARD) GPIO.setup(7, GPIO.OUT) GPIO.setup(11, GPIO.OUT) GPIO.setup(12, GPIO.OUT) GPIO.setup(13, GPIO.OUT) GPIO.setup(15, GPIO.OUT)

  1. function definitions #
  1. getStringLenght definition

def getStringsLen(pos_xy, halfPullDist, scale):

   x2 = (pos_xy[1] * scale)**2
   x2b2 = ((halfPullDist - pos_xy[1]) * scale)**2
   y2 = (pos_xy[0] * scale)**2
   return [sqrt(x2b2+y2) , sqrt(x2+y2)]
  1. enable motors

def enableMotors():

   print "enabling motors"
   # here put high pin !enable (15 on PI)
   GPIO.output(15, True)
  1. this really move the motor operating on GPIO (note: doing only once)

def stepMotor(motorId,direction):

   #print "stepping motor ", motorId
   if motorId == 1:
       GPIO.output(11, direction)
       GPIO.output(7, True)
       sleep(0.0025)
       GPIO.output(7, False)
   if motorId == 2:
       GPIO.output(13 , direction)
       GPIO.output(12, True)
       sleep(0.0025)
       GPIO.output(12, False)
  1. moveMotor definition

def moveMotor(numStepM1, numStepM2):

   # variables initialization
   M1Interval = defaultStepInterval # 0.5 #.............................. compute time interval for stepping motor 1
   M2Interval = defaultStepInterval # 0.25 #.............................. compute time interval for stepping motor 2
   # calculate requested step and direction
   M1reverse = sign(numStepM1) < 0
   M2reverse = sign(numStepM2) < 0
   M1requestedStep = abs(numStepM1)
   M2requestedStep = abs(numStepM2)
   # calculate max spent time stepping at default speed _TODO_ find a less crude way to do that
   M1TravelTime = M1requestedStep*defaultStepInterval
   M2TravelTime = M2requestedStep*defaultStepInterval
   RealTravelInterval = median([M1TravelTime,M2TravelTime])
   print "M1 should do "+str(M1requestedStep)+" steps, M2 should do "+str(M2requestedStep)+" steps"
   print numStepM1 , numStepM2, M1reverse , M2reverse
   #initialize then...
  1. i = 0 #........................................ init while loop
   M1steppedStep = 0 #.............................motor 1 step counter
   M2steppedStep = 0 #.............................motor 2 step counter
  1. run = True #................................... while loop run flag
   clockStart = clock()
   # ...run
  1. while run:
   while M1requestedStep>M1steppedStep or M2requestedStep>M2steppedStep:
  1. i = i +1
       if M1requestedStep>M1steppedStep:
  1. if clock() - clockStart > RealTravelInterval * M1steppedStep:
           stepMotor(1,M1reverse)
           M1steppedStep = M1steppedStep + 1
       if M2requestedStep>M2steppedStep:
  1. if clock() - clockStart > RealTravelInterval * M2steppedStep:
           stepMotor(2,M2reverse)
           M2steppedStep = M2steppedStep + 1
  1. if(i > 100000): run = False # this is a timeout????
  1. now the part that take txt files and drive motor

try:

   # load path file
   path = loadtxt(fileName)
   print "file loaded"
   # convert sys param 
   r_p = PulleyRadius
   d_p = pulleyDistance
   d_p05 = d_p #* 0.5 #........ half pulley distance [mm]
   s_a = stepAngle * (2*pi/360) #... step angle [rad]
   s = drawingScale
   # initialize drawing
   pos_init = initial_position   #........................... set initial position vector (x,y)
   len_curr = getStringsLen(pos_init, d_p05, s) # ..... set initial string length in mm
   print len_curr
   c=0
   # do the drawing
   for i in path:

c=c+1

       pos_next = i #................................. allocate next position vector

pos_next = pos_next + initial_position

  1. pos_next[1] = pos_next[1]*1.51

len_next = getStringsLen(pos_next, d_p05, s) #. get next strings lengths

       print pos_next, len_next, len_curr
       dl = subtract(len_next, len_curr) #............ get string lengths difference 
       ds = dl/s_a #.................................. compute motor step (same step angles for both motors)
       RequestedStepMotor1 = round(ds[0]) # str(round(ds[0])).rstrip('0').rstrip('.')
       RequestedStepMotor2 = round(ds[1]) # str(round(ds[1])).rstrip('0').rstrip('.')
       print "Drawing to coordinate "+str(i)
       print "step M1:"+str(RequestedStepMotor1)+" M2:"+str(RequestedStepMotor2)
       if c>1:  # this is to get rid of initial unwanted segment

moveMotor(RequestedStepMotor1,RequestedStepMotor2) len_curr = len_next

   # print has finished! let's disable motors and free the GPIO channel
   GPIO.output(15, False)
   GPIO.cleanup()

except (KeyboardInterrupt, SystemExit):

       print " exiting..."
       GPIO.cleanup()
       print "\ncleaning GPIO..."
       raise

except:

       print "\n errori nel codice!!!"

</source>