Difference between revisions of "Software"
(→nightly Python code) |
(→Python code) |
||
| Line 200: | Line 200: | ||
== Python code == | == 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"> | <source lang="python"> | ||
| + | # 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 | ||
| − | + | # end of system parameters. | |
| − | + | ####### DO NOT EDIT BELOW THIS LINE ########## | |
| − | |||
| − | |||
| − | |||
| + | #packages import | ||
from time import sleep, clock | from time import sleep, clock | ||
| − | + | import RPi.GPIO as GPIO | |
| − | from numpy import loadtxt, pi, sqrt, subtract | + | from numpy import loadtxt, pi, sign, sqrt, subtract, median |
| − | + | ||
| − | # | + | |
| − | + | ||
| − | + | # 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) | |
| − | # | + | |
| − | + | # function definitions # | |
| − | # | + | #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)] | ||
| + | # enable motors | ||
| + | def enableMotors(): | ||
| + | print "enabling motors" | ||
| + | # here put high pin !enable (15 on PI) | ||
| + | GPIO.output(15, True) | ||
| − | + | # 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) | ||
| − | # | + | #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... | ||
# i = 0 #........................................ init while loop | # i = 0 #........................................ init while loop | ||
| − | # | + | M1steppedStep = 0 #.............................motor 1 step counter |
| − | # | + | M2steppedStep = 0 #.............................motor 2 step counter |
| − | # | + | # run = True #................................... while loop run flag |
| − | # | + | clockStart = clock() |
| − | # | + | # ...run |
| − | # | + | # while run: |
| − | # | + | while M1requestedStep>M1steppedStep or M2requestedStep>M2steppedStep: |
| − | # | + | # i = i +1 |
| − | + | if M1requestedStep>M1steppedStep: | |
| − | + | # if clock() - clockStart > RealTravelInterval * M1steppedStep: | |
| − | + | stepMotor(1,M1reverse) | |
| + | M1steppedStep = M1steppedStep + 1 | ||
| + | if M2requestedStep>M2steppedStep: | ||
| + | # if clock() - clockStart > RealTravelInterval * M2steppedStep: | ||
| + | stepMotor(2,M2reverse) | ||
| + | M2steppedStep = M2steppedStep + 1 | ||
| + | # if(i > 100000): run = False # this is a timeout???? | ||
| + | |||
| + | # 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 | ||
| + | # 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> | </source> | ||
Revision as of 02:31, 17 February 2013
Contents
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:
- Install Processing
- Download and Install toxiclib
- 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);
}
} </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">
- 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
- end of system parameters.
- DO NOT EDIT BELOW THIS LINE ##########
- packages import
from time import sleep, clock import RPi.GPIO as GPIO from numpy import loadtxt, pi, sign, sqrt, subtract, median
- 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)
- function definitions #
- 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)]
- enable motors
def enableMotors():
print "enabling motors" # here put high pin !enable (15 on PI) GPIO.output(15, True)
- 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)
- 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...
- i = 0 #........................................ init while loop
M1steppedStep = 0 #.............................motor 1 step counter M2steppedStep = 0 #.............................motor 2 step counter
- run = True #................................... while loop run flag
clockStart = clock() # ...run
- while run:
while M1requestedStep>M1steppedStep or M2requestedStep>M2steppedStep:
- i = i +1
if M1requestedStep>M1steppedStep:
- if clock() - clockStart > RealTravelInterval * M1steppedStep:
stepMotor(1,M1reverse)
M1steppedStep = M1steppedStep + 1
if M2requestedStep>M2steppedStep:
- if clock() - clockStart > RealTravelInterval * M2steppedStep:
stepMotor(2,M2reverse)
M2steppedStep = M2steppedStep + 1
- if(i > 100000): run = False # this is a timeout????
- 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
- 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>
nightly Python code
DO NOT RUN if you care your cpu -> is an infinite loop <source lang="python">
- system parameters. Tune them following your system
PulleyRadius = 18.0 #................. pulley radius [mm] pulleyDistance = 1500.0 #............. pulley distance [mm] stepAngle = 3.5 # .................... step angle [rad] drawingScale = 2.0 #.................. drawing scale fileName = "path.txt" defaultStepInterval = 0.5 # _TODO_ find some way to get this value confy
- end of system parameters.
- DO NOT EDIT BELOW THIS LINE ##########
- packages import
from time import sleep, clock
- import RPi.GPIO as GPIO
from numpy import loadtxt, pi, sqrt, subtract, median
- function definitions #
- getStringLenght definition
def getStringsLen(pos_xy, halfPullDist, scale):
x2 = (pos_xy[0] * scale)**2 x2b2 = (halfPullDist - pos_xy[0] * scale)**2 y2 = (pos_xy[1] * scale)**2 return [sqrt(x2+y2) , sqrt(x2b2+y2)]
- enable motors
def enableMotors(): print "enabling motors" # here put high pin !enable (15 on PI)
- this really move the motor operating on GPIO (note: doing only once)
def stepMotor(motorId,direction): print "stepping motor"+str(motorId) # here put high pin 3 and pulse once pin 4
- 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 = False M2reverse = False
M1requestedStep = abs(numStepM1)
M2requestedStep = abs(numStepM2)
if M1requestedStep==numStepM1: if numStepM1<0: M1reverse = True if M2requestedStep==numStepM2: if numStepM2<0: M2reverse = True
# 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])
#initialize then...
- i = 0 #........................................ init while loop
M1steppedStep = 0 #.............................motor 1 step counter
M2steppedStep = 0 #.............................motor 2 step counter
run = True #................................... while loop run flag
clockStart = clock() # ...run while run:
- i = i +1
if M1requestedStep<M1steppedStep: if clock() - clockStart > RealTravelInterval * M1steppedStep: stepMotor(1,M1reverse) M1steppedStep = M1steppedStep + 1 if M2requestedStep<M2steppedStep: if clock() - clockStart > RealTravelInterval * M2steppedStep: stepMotor(2,M2reverse) M2steppedStep = M2steppedStep + 1
- if(i > 100000): run = False # this is a timeout????
- now the part that take txt files and drive motor
try: while 1==1: # load path file path = loadtxt(fileName)
# 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 = [240, 240] #........................... set initial position vector (x,y) len_curr = getStringsLen(pos_init, d_p05, s) # ..... get initial string
# do the drawing for i in path: pos_next = i #................................. allocate next position vector len_next = getStringsLen(pos_next, d_p05, s) #. get next strings lengths 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 str(i)+" give: "+ RequestedStepMotor1+" "+RequestedStepMotor2
moveMotor(RequestedStepMotor1,RequestedStepMotor2) # _TODO_ double check that this for cicle wait moveMotor()
except (KeyboardInterrupt, SystemExit):
print " exiting..."
- GPIO.cleanup()
- print "\ncleaning GPIO..."
raise
except:
print "\n errori nel codice!!!"
</source>