I am guessing that there may be an answer about this on the internet somewhere but I can not find it. I am making a graphing calculator and I tried to make "plots" follow a certain function y = 2x. Though i can not seem to find out how to make the plots have their own x and y (their unique x and y).
function CreateDot() {
this.ix = 1; //this is the x value of the function and is also used in y = 2x
this.fy = this.ix * 2; //this is the y value of the funciton y = 2x
this.newDot = function() {
//this is defining the x value of the plot and scaling the value to have it follow the grid
this.x1 = spacing * this.ix;
// this is defining the y value of the plot and scaling the value to have it follow the grid
this.y1 = 500 - spacing * this.fy; //
//this is the code for creating the "plot" which is a dot on the screen and red in colour
stroke(255,0,0);
strokeWeight(25);
//this is defining the position of the point with the values above x1 and y1
point(this.x1,this.y1);
//this is supposed to allow the next value of x=2 in the function y = 2x and so allowing the next coordinates of the plot to be ( 2, 4 ) and the next dot (3, 6) and so on.
this.ix = this.ix + 1;
}
}
I what I noticed is that after I made this a constructor function and put new dots into an array and limited it to 5, I ran it and one dot flew all the way to the right. I printed each of the objects x and y and they had the same values..
So my question is how do I make sure Each object has their own unique x and y values?
Thanks in advance!
You have to put the function either on the prototype and call it that way, or you have to pass the dots as parameters to the update function:
// Prototype
function CreateDot( spacing ) {
this.ix = 1;
this.fy = this.ix * 2;
this.x1 = spacing * this.ix;
this.y1 = 500 - spacing * this.fy;
}
CreateDot.prototype.addOne = function() {
// this refers to: 'the dot you call addOne upon'.
this.ix = this.ix + 1;
};
var myDot = new CreateDot( 250 );
console.log( 'before myDot.addOne() : ' + myDot.ix );
myDot.addOne();
console.log( 'after myDot.addOne() : ' + myDot.ix );
// Seperate function
var addOne_f = function( dot ) {
// this refers to 'the window instance', so you need to use the 'dot' parameter to refer to the dot.
dot.ix = dot.ix + 1;
};
console.log( 'before addOne_f( myDot ) : ' + myDot.ix );
addOne_f( myDot );
console.log( 'after addOne_f( myDot ) : ' + myDot.ix );
// Inside an array:
var dots = [
new CreateDot( 200 ),
new CreateDot( 225 ),
new CreateDot( 250 )
];
// Update all dots
dots.forEach( dot => dot.addOne());
// Update the second dot again to show they're not linked
dots[ 1 ].addOne();
console.log( dots.map( dot => dot.ix ));
Related
So, i'm trying to implement hough transform, this version is 1-dimensional (its for all dims reduced to 1 dim optimization) version based on the minor properties.
Enclosed is my code, with a sample image... input and output.
Obvious question is what am i doing wrong. I've tripled check my logic and code and it looks good also my parameters. But obviously i'm missing on something.
Notice that the red pixels are supposed to be ellipses centers , while the blue pixels are edges to be removed (belong to the ellipse that conform to the mathematical equations).
also, i'm not interested in openCV / matlab / ocatve / etc.. usage (nothing against them).
Thank you very much!
var fs = require("fs"),
Canvas = require("canvas"),
Image = Canvas.Image;
var LEAST_REQUIRED_DISTANCE = 40, // LEAST required distance between 2 points , lets say smallest ellipse minor
LEAST_REQUIRED_ELLIPSES = 6, // number of found ellipse
arr_accum = [],
arr_edges = [],
edges_canvas,
xy,
x1y1,
x2y2,
x0,
y0,
a,
alpha,
d,
b,
max_votes,
cos_tau,
sin_tau_sqr,
f,
new_x0,
new_y0,
any_minor_dist,
max_minor,
i,
found_minor_in_accum,
arr_edges_len,
hough_file = 'sample_me2.jpg',
edges_canvas = drawImgToCanvasSync(hough_file); // make sure everything is black and white!
arr_edges = getEdgesArr(edges_canvas);
arr_edges_len = arr_edges.length;
var hough_canvas_img_data = edges_canvas.getContext('2d').getImageData(0, 0, edges_canvas.width,edges_canvas.height);
for(x1y1 = 0; x1y1 < arr_edges_len ; x1y1++){
if (arr_edges[x1y1].x === -1) { continue; }
for(x2y2 = 0 ; x2y2 < arr_edges_len; x2y2++){
if ((arr_edges[x2y2].x === -1) ||
(arr_edges[x2y2].x === arr_edges[x1y1].x && arr_edges[x2y2].y === arr_edges[x1y1].y)) { continue; }
if (distance(arr_edges[x1y1],arr_edges[x2y2]) > LEAST_REQUIRED_DISTANCE){
x0 = (arr_edges[x1y1].x + arr_edges[x2y2].x) / 2;
y0 = (arr_edges[x1y1].y + arr_edges[x2y2].y) / 2;
a = Math.sqrt((arr_edges[x1y1].x - arr_edges[x2y2].x) * (arr_edges[x1y1].x - arr_edges[x2y2].x) + (arr_edges[x1y1].y - arr_edges[x2y2].y) * (arr_edges[x1y1].y - arr_edges[x2y2].y)) / 2;
alpha = Math.atan((arr_edges[x2y2].y - arr_edges[x1y1].y) / (arr_edges[x2y2].x - arr_edges[x1y1].x));
for(xy = 0 ; xy < arr_edges_len; xy++){
if ((arr_edges[xy].x === -1) ||
(arr_edges[xy].x === arr_edges[x2y2].x && arr_edges[xy].y === arr_edges[x2y2].y) ||
(arr_edges[xy].x === arr_edges[x1y1].x && arr_edges[xy].y === arr_edges[x1y1].y)) { continue; }
d = distance({x: x0, y: y0},arr_edges[xy]);
if (d > LEAST_REQUIRED_DISTANCE){
f = distance(arr_edges[xy],arr_edges[x2y2]); // focus
cos_tau = (a * a + d * d - f * f) / (2 * a * d);
sin_tau_sqr = (1 - cos_tau * cos_tau);//Math.sqrt(1 - cos_tau * cos_tau); // getting sin out of cos
b = (a * a * d * d * sin_tau_sqr ) / (a * a - d * d * cos_tau * cos_tau);
b = Math.sqrt(b);
b = parseInt(b.toFixed(0));
d = parseInt(d.toFixed(0));
if (b > 0){
found_minor_in_accum = arr_accum.hasOwnProperty(b);
if (!found_minor_in_accum){
arr_accum[b] = {f: f, cos_tau: cos_tau, sin_tau_sqr: sin_tau_sqr, b: b, d: d, xy: xy, xy_point: JSON.stringify(arr_edges[xy]), x0: x0, y0: y0, accum: 0};
}
else{
arr_accum[b].accum++;
}
}// b
}// if2 - LEAST_REQUIRED_DISTANCE
}// for xy
max_votes = getMaxMinor(arr_accum);
// ONE ellipse has been detected
if (max_votes != null &&
(max_votes.max_votes > LEAST_REQUIRED_ELLIPSES)){
// output ellipse details
new_x0 = parseInt(arr_accum[max_votes.index].x0.toFixed(0)),
new_y0 = parseInt(arr_accum[max_votes.index].y0.toFixed(0));
setPixel(hough_canvas_img_data,new_x0,new_y0,255,0,0,255); // Red centers
// remove the pixels on the detected ellipse from edge pixel array
for (i=0; i < arr_edges.length; i++){
any_minor_dist = distance({x:new_x0, y: new_y0}, arr_edges[i]);
any_minor_dist = parseInt(any_minor_dist.toFixed(0));
max_minor = b;//Math.max(b,arr_accum[max_votes.index].d); // between the max and the min
// coloring in blue the edges we don't need
if (any_minor_dist <= max_minor){
setPixel(hough_canvas_img_data,arr_edges[i].x,arr_edges[i].y,0,0,255,255);
arr_edges[i] = {x: -1, y: -1};
}// if
}// for
}// if - LEAST_REQUIRED_ELLIPSES
// clear accumulated array
arr_accum = [];
}// if1 - LEAST_REQUIRED_DISTANCE
}// for x2y2
}// for xy
edges_canvas.getContext('2d').putImageData(hough_canvas_img_data, 0, 0);
writeCanvasToFile(edges_canvas, __dirname + '/hough.jpg', function() {
});
function getMaxMinor(accum_in){
var max_votes = -1,
max_votes_idx,
i,
accum_len = accum_in.length;
for(i in accum_in){
if (accum_in[i].accum > max_votes){
max_votes = accum_in[i].accum;
max_votes_idx = i;
} // if
}
if (max_votes > 0){
return {max_votes: max_votes, index: max_votes_idx};
}
return null;
}
function distance(point_a,point_b){
return Math.sqrt((point_a.x - point_b.x) * (point_a.x - point_b.x) + (point_a.y - point_b.y) * (point_a.y - point_b.y));
}
function getEdgesArr(canvas_in){
var x,
y,
width = canvas_in.width,
height = canvas_in.height,
pixel,
edges = [],
ctx = canvas_in.getContext('2d'),
img_data = ctx.getImageData(0, 0, width, height);
for(x = 0; x < width; x++){
for(y = 0; y < height; y++){
pixel = getPixel(img_data, x,y);
if (pixel.r !== 0 &&
pixel.g !== 0 &&
pixel.b !== 0 ){
edges.push({x: x, y: y});
}
} // for
}// for
return edges
} // getEdgesArr
function drawImgToCanvasSync(file) {
var data = fs.readFileSync(file)
var canvas = dataToCanvas(data);
return canvas;
}
function dataToCanvas(imagedata) {
img = new Canvas.Image();
img.src = new Buffer(imagedata, 'binary');
var canvas = new Canvas(img.width, img.height);
var ctx = canvas.getContext('2d');
ctx.patternQuality = "best";
ctx.drawImage(img, 0, 0, img.width, img.height,
0, 0, img.width, img.height);
return canvas;
}
function writeCanvasToFile(canvas, file, callback) {
var out = fs.createWriteStream(file)
var stream = canvas.createPNGStream();
stream.on('data', function(chunk) {
out.write(chunk);
});
stream.on('end', function() {
callback();
});
}
function setPixel(imageData, x, y, r, g, b, a) {
index = (x + y * imageData.width) * 4;
imageData.data[index+0] = r;
imageData.data[index+1] = g;
imageData.data[index+2] = b;
imageData.data[index+3] = a;
}
function getPixel(imageData, x, y) {
index = (x + y * imageData.width) * 4;
return {
r: imageData.data[index+0],
g: imageData.data[index+1],
b: imageData.data[index+2],
a: imageData.data[index+3]
}
}
It seems you try to implement the algorithm of Yonghong Xie; Qiang Ji (2002). A new efficient ellipse detection method 2. p. 957.
Ellipse removal suffers from several bugs
In your code, you perform the removal of found ellipse (step 12 of the original paper's algorithm) by resetting coordinates to {-1, -1}.
You need to add:
`if (arr_edges[x1y1].x === -1) break;`
at the end of the x2y2 block. Otherwise, the loop will consider -1, -1 as a white point.
More importantly, your algorithm consists in erasing every point which distance to the center is smaller than b. b supposedly is the minor axis half-length (per the original algorithm). But in your code, variable b actually is the latest (and not most frequent) half-length, and you erase points with a distance lower than b (instead of greater, since it's the minor axis). In other words, you clear all points inside a circle with a distance lower than latest computed axis.
Your sample image can actually be processed with a clearing of all points inside a circle with a distance lower than selected major axis with:
max_minor = arr_accum[max_votes.index].d;
Indeed, you don't have overlapping ellipses and they are spread enough. Please consider a better algorithm for overlapping or closer ellipses.
The algorithm mixes major and minor axes
Step 6 of the paper reads:
For each third pixel (x, y), if the distance between (x, y) and (x0,
y0) is greater than the required least distance for a pair of pixels
to be considered then carry out the following steps from (7) to (9).
This clearly is an approximation. If you do so, you will end up considering points further than the minor axis half length, and eventually on the major axis (with axes swapped). You should make sure the distance between the considered point and the tested ellipse center is smaller than currently considered major axis half-length (condition should be d <= a). This will help with the ellipse erasing part of the algorithm.
Also, if you also compare with the least distance for a pair of pixels, as per the original paper, 40 is too large for the smaller ellipse in your picture. The comment in your code is wrong, it should be at maximum half the smallest ellipse minor axis half-length.
LEAST_REQUIRED_ELLIPSES is too small
This parameter is also misnamed. It is the minimum number of votes an ellipse should get to be considered valid. Each vote corresponds to a pixel. So a value of 6 means that only 6+2 pixels make an ellipse. Since pixels coordinates are integers and you have more than 1 ellipse in your picture, the algorithm might detect ellipses that are not, and eventually clear edges (especially when combined with the buggy ellipse erasing algorithm). Based on tests, a value of 100 will find four of the five ellipses of your picture, while 80 will find them all. Smaller values will not find the proper centers of the ellipses.
Sample image is not black & white
Despite the comment, sample image is not exactly black and white. You should convert it or apply some threshold (e.g. RGB values greater than 10 instead of simply different form 0).
Diff of minimum changes to make it work is available here:
https://gist.github.com/pguyot/26149fec29ffa47f0cfb/revisions
Finally, please note that parseInt(x.toFixed(0)) could be rewritten Math.floor(x), and you probably want to not truncate all floats like this, but rather round them, and proceed where needed: the algorithm to erase the ellipse from the picture would benefit from non truncated values for the center coordinates. This code definitely could be improved further, for example it currently computes the distance between points x1y1 and x2y2 twice.
I asked this question earlier but didn't communicated it clearly, so forgive me for the duplicate. This should be better.
I need to figure the position of a coordinate, given three other coordinates and 2 slopes. Basically, the intersection point of two lines. However, I don't have all of the information normally available to solve this.
I have an arbitrary shape defined by a bunch of vertices. The user may drag a line between these vertices and the shape should react as the diagrams below show.
So in the first example, the user drags line EF from the position on the left to the position on the right (line E2F2). What needs to happen is that line EF grows/shrinks such that it's slope stays the same and that it's beginning and ending coordinates remain on the lines DE and AF respectively. This is shown as line E2F2.
This needs to be generic enough that it can handle any sort of strange or regular angles I throw at it. The second set of shapes shows a simpler approach. The user drags line CD to the position of C2D2. Notice how the slopes stay the same and D2 essentially slides down that diagonal line and B2C2 and C2D2 both extend in length. The result is that all 3 slopes stay the same but lines B2C2 and C2D2 grow in length to stay connected, while line D2E2 shrinks.
You'll need to understand that when dragging line EF, you're actually moving the coordinate "E". So, figuring the first coordinate is easy. And the previous and next one's never change. So I essentially have the slopes of the 3 relevant lines and 3 of the 4 necessary coordinates. I need the 4th, so in my example, F2 or D2.
This code is called on an event every time the coordinate moves. Lets say we're dragging line EF - the coordinate is E then.
var next = this.model.get("next"), // coordinate F
nextNext = next.get("next"), // coordinate A
nextDx = nextNext.get("x") - next.get("x"), // delta X of AF
nextDy = nextNext.get("y") - next.get("y"), // delta Y of AF
prev = this.model.get("prev"), // coordinate D
prevDx = prev.get("x") - this.model.get("x"), // delta X of DF
prevDy = prev.get("y") - this.model.get("y"), // delta Y of DF
selfDx = next.get("x") - this.model.get("x"), // delta X of EF
selfDy = next.get("y") - this.model.get("y"), // delta Y of EF
selfX = this.initialCoords.x + this.shape.getX(), // the new position of E
selfY = this.initialCoords.y + this.shape.getY(),
selfM, selfB, prevM, prevB, nextM, nextB, m, x, y, b;
// check for would-be infinities
if (selfDx == 0) {
// **** THIS WHOLE BLOCK IS CORRECT ****
// i'm vertical
// we can safely assume prev/next aren't also vertical. i think? right?
prevM = prev.get("slope");
prevB = prev.get("y") - prevM * prev.get("x");
var myX = selfX,
myY = prevM * myX + prevB;
this.model.set({
x: myX,
y: myY
});
nextM = next.get("slope");
nextB = next.get("y") - nextM * next.get("x");
var nextX = selfX,
nextY = nextM * nextX + nextB;
next.set({
x: nextX,
y: nextY
});
} else if (selfDy == 0) {
//***** THIS WHOLE BLOCK IS CORRECT **** //
// i'm horizontal
if (prevDx == 0) {
// prev is a vertical line
this.model.set({
y: selfY
});
} else {
prevM = prev.get("slope");
prevB = prev.get("y") - prevM * prev.get("x");
var myY = selfY,
myX = (selfY - prevB) / prevM;
this.model.set({
x: myX,
y: myY
});
}
if (nextDx == 0) {
// next is a vertical line
next.set({
y: selfY
});
} else {
nextM = next.get("slope");
nextB = next.get("y") - nextM * next.get("x");
var nextY = selfY,
nextX = (selfY - nextB) / nextM;
next.set({
x: nextX,
y: nextY
});
}
} else {
// HELP HERE - you've chosen to drag an arbitrarily angled line. Figure out the "next" coordinate given the "current" one.
selfM = this.model.get("slope");
selfB = this.model.get("y") - this.model.get("slope") * this.model.get("x");
if (selfM < 0) {
prevM = prev.get("slope");
prevB = prev.get("y") - prevM * prev.get("x");
var myY = selfY,
myX = (selfY - prevB) / prevM;
// CORRECT, but need "next" position based on this
this.model.set({
x: myX,
y: myY
});
} else {
// CORRECT but need "next" position based on this.
var myX = selfX;
this.model.set({
x: myX
});
}
}
I had a similar situation and had some success using this page as reference :
http://en.wikipedia.org/wiki/Line-line_intersection
You should be able to enumerate all your lines testing for any points where they cross your moving line. These will be the new coordinates.
The equations in the wiki article assume lines of infinite length which you should be aware of but should actually be what you want (I think - there are probably edge cases).
I don't know why but my script is returning the wrong value for alpha channel.
This is what i have:
function getPixel(x,y,px,py,i){
//user click: x y
//picture location: px py
//array key: i
//location of click has to be changed to be relevant to this temp canvas
//as image will now be at position 0,0
var x = Math.round(0 - (px - x) );
var y = Math.round(0 - (py - y) );
//temp canvas
var c = document.createElement('canvas');
var context = c.getContext('2d');
c.width = pArray[i].img.width;
c.height = pArray[i].img.height;
context.drawImage(pArray[i].img,0,0);
var d = context.getImageData(x,y,1,1);
if(d[3] != 0){
console.log('Not Alpha'); //always happens
} else {
console.log('Alpha'); // never happens
}
console.log(x + ', ' + y + ', ' + c.width + ', ' + c.height + ', ' + pArray[i].img.src);
}
My console output shows:
8, 42, 128, 128, [Full URL Hidden]/images/1.png
Here is also the image I am testing it with :
Can anyone see any glaring mistake that might explain why the alpha never equals 0 ?
JSFiddle testing location x1 and y1:
http://jsfiddle.net/darkyen/UCSU2/15/
Well as per specs the .getImageData(); returns an imagedata object. In that object there is an array data which has all your data.
You were missing the data , so basically since d is the imagedata element there for it has not element defined for index 3 => d[3] === undefined,
hence it was failing ,
try d[3].data
http://jsfiddle.net/UCSU2/16/ <- here is a working fiddle
context.getImageData() returns ImageData (see this page), not Pixel Array.
So change the line
var d = context.getImageData(x,y,1,1);
to
var d = context.getImageData(x,y,1,1).data;
will do the work.
Canvas ImageData reference: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#imagedata
Pixel manipulation reference: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#pixel-manipulation
MDN ImageData reference (not complete yet): https://developer.mozilla.org/en-US/docs/DOM/ImageData
I'm currently working on a horizontal blur algorithm in javascript, though I doubt the language matters.
I get the data from a canvas which is basically a huge array where every four (RGBA) values stand for one pixel. A value can contain an int ranging from 0 to 255.
When I blur the image, the area's between two different colours turn into strange colours! I've drawn a red rectangle on a black background. Using the algorithm below, I get the following result (4px size):
Though when a use a 1 or 2 pixel size, everything seems to work normally.
Please note this is somewhat messy build up. I'm planning to make this all OOP!
// s: size
// w: width
// h: height
function blur( s, w, h ) {
var src = ctx.getImageData( 0, 0, w, h ); // get imagedata from source
var dst = ctx.createImageData( w, h ); // create imagedata for dest
var x, y, xo, index, rgb; // predefine vars
// loop through y axis
for( y = 0; y < h; y++ ) {
// loop through x axis
for( x = 0; x < w; x++ ) {
rgb = 0; // set total to 0
// loop through area around current pixel
for( xo = 0 - s; xo <= s; xo++ ) {
// get specific index
index = getIndex( x + xo, y, w );
// add nothing if the value doesn't exist (borders)
// if( isNaN( src.data[index] ) ) continue;
if( typeof src.data[index] === 'undefined' ) continue;
// add the values to total
rgb += ( src.data[index] << 16 ) + ( src.data[index + 1] << 8 ) + src.data[index + 2];
}
// get the average of all pixels in that area
rgb = rgb / ( s * 2 + 1);
// get index of current pixel
index = getIndex( x, y, w );
// set pixel in dest
dst.data[index] = ( rgb & 0xff0000 ) >> 16; // red
dst.data[index + 1] = ( rgb & 0x00ff00 ) >> 8; // green
dst.data[index + 2] = ( rgb & 0x0000ff ); // blue
dst.data[index + 3] = 255; // alpha
}
}
// add the new image data
ctx.putImageData( dst, 0, 0 );
}
function getIndex( x, y, w ) {
// calculate the appropriate index, since every pixel has 4 array values
return ( y * ( w * 4 ) + ( x * 4 ) );
}
So what is wrong with my algorithm? I'm a bit lost. Please note that I'm not looking for existing objects/libraries/files for canvas blurring. I like to reinvent everything to educate myself.
Edit: I also like to add that the values I get back are truly the values that represent the colours shown on the canvas. That means that's definitely a miscalculation in my algorithm.
You should average your channels separately. Dividing a packed three-channel value is unlikely to keep each channel within its byte.
The average between 0x030000 (dark red) and 0x000000 (black) becomes 0x018000, which gets a lot of green (0x80)
You should average the channels separately.
I have the following simple blur algorithm:
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
total = 0
for (ky = -radius; ky <= radius; ++ky)
for (kx = -radius; kx <= radius; ++kx)
total += source(x + kx, y + ky)
dest(x, y) = total / (radius * 2 + 1) ^ 2
}
}
And I need to make this work with an array, generated by canvas with getImageData(). The problem is that the array from canvas is one dimentional, and the algorithm needs a two dimentional one, because "kx" and "ky" are distances from the current pixel, and in a two dimentional array you just change one of the indexes in order to move left or right on the grid, but in a one dimentional array (which is trying to represent a two-dim. one) you can't do that.
What do you think, how should I approach this problem? In my opinion, the entire problem is here:
total += source(x + kx, y + ky)
This line needs to get the correct pixels from the one-dimentional array and it should work fine.
P.S. I forgot to say that I'm trying to blur the image one channel at a time, like this:
for (var i=0; i<imgData.data.length; i+=4) {
red[index] = imgData.data[i];
green[index] = imgData.data[i+1];
blue[index] = imgData.data[i+2];
index++;
}
and I'm passing each array (red, green and blue) individually to the algorithm.
In order access a single dimensional array with two dimensional coordinates, you have the following formula:
var pixel = pixels[ ( ( y * width ) + x ) * 4 ];
The * 4 was added to account for the r,g,b,a values.
xResolution is the width if the image.
So source() would be:
function source( imageArray, x, y, imageWidth )
{
var pos = ( ( y * imageWidth ) + x ) * 4;
var val =
{
r : imageArray[ pos ],
g : imageArray[ pos + 1 ],
b : imageArray[ pos + 2 ],
a : imageArray[ pos + 3 ]
};
return val;
}