I am continuing to animate a model of a transportation system. The code builds stations, connects them with lines, builds "pods" to run along them, then works out the shortest path between random start and end stations.
jsfiddle
The problems I am having are
a) the pods are traveling one at a time, rather than simultaneously.
b) the pod color is supposed to be related to the number of passengers, but this is clearly not happening.
c) I suspect I am producing a bottleneck in calculating the animation points, as it seems to run more hesitantly than it should.
I would also like to be able to flag the arrival of each pod, so that it can be reloaded, and start a new journey.
Something is wrong with the animation section, but I can't work out what. Any help would be appreciated!
animate();
var lastTime = 0;
var speed = 1; // higher is slower
function animate(time) {
for (var i = 0; i < podArray.length; i++) {
var aPod = podArray[i];
// calculate incremental points along the path
var points = calcWaypoints(aPod.wayStations);
// return if the desired time hasn't elapsed
if ((time - lastTime) < speed) {
requestAnimationFrame(animate);
return;
}
lastTime = time;
//function animate(){
ctx3.clearRect(0, 0, layer3.width, layer3.height);
if (t < points.length - 1) {
requestAnimationFrame(animate);
}
// draw pod from the last waypoint to the current waypoint
ctx3.beginPath();
ctx3.moveTo(aPod.startX, aPod.startY); //(points[t - 1].x, points[t - 1].y);
ctx3.arc(points[t].x, points[t].y, 4, 0, Math.PI * 2, true);
ctx3.fillStyle = aPod.color;
ctx3.fill();
t++;
}
}
a) Your calcWaypoints function takes a parameter that is a list of waystation, and then you ignore that parameter and calculate all points for all pods. This is creating one big array of all points, starting with the first pod's points, then the 2nd's, etc. This is why it starts with the first, then the 2nd then the third.
function calcWaypoints(nextArray) {
var frams = 100;
var waypoints = [];
for (var i = 1; i < nextArray.length; i++) {
var pt0 = nextArray[i - 1];
var pt1 = nextArray[i];
var dx = pt1[0] - pt0[0];
var dy = pt1[1] - pt0[1];
for (var j = 0; j < frams; j++) {
var x = pt0[0] + dx * j / frams //+ dxOff;
var y = pt0[1] + dy * j / frams //+ dyOff;
waypoints.push({
x: x,
y: y
});
};
}
return waypoints;
}
b) This has the same cause as the previous, because you are using the same points for each pod, the pods are written over one another. In addition, you are clearing the canvas between each point. This clear should be moved out of the loop. ctx3.clearRect(0, 0, layer3.width, layer3.height);
c) For some reason you are re-calculating all points with every call to animate. You should calculate them all upfront once and then not in animate. You are also creating way too many calls to requestAnimationFrame in your loop.
I hacked together a few of the above changes and it is working. You should still clean it up a lot more:
layer1 = document.getElementById('layer1');
ctx1 = layer1.getContext('2d');
layer2 = document.getElementById('layer2');
ctx2 = layer2.getContext('2d');
layer3 = document.getElementById('layer3');
ctx3 = layer3.getContext('2d');
window.requestAnimFrame = (function (callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
//STATIONS*************************************
var station = [
['A', 150, 100],
['B', 300, 100],
['C', 200, 175],
['D', 100, 250],
['E', 300, 250],
['G', 400, 250],
['F', 350, 200],
['Airport', 500, 200],
['Central', 500, 350]
];
function draw2() {
for (var i = 0; i < station.length; i++) {
var radius = 10;
ctx2.beginPath();
ctx2.arc(station[i][1], station[i][2], radius, 0, 2 * Math.PI);
ctx2.stroke();
ctx2.fillStyle = 'yellow';
ctx2.fill();
//ADD STATION LETTERS
ctx2.font = '10pt Calibri';
ctx2.fillStyle = 'black';
ctx2.textAlign = 'center';
ctx2.fillText(station[i][0], station[i][1], (station[i][2]) + 4);
}
}
draw2();
//END STATIONS*************************************
//START LINES**************************************
var lineArray = [
['A', 'B', ],
['B', 'A', 'C'],
['C', 'B', 'D', 'E'],
['D', 'C', 'E'],
['E', 'D', 'F', 'G', 'Central'],
['F', 'E', 'G'],
['G', 'F', 'E', 'Airport'],
['Airport', 'G', 'Central'],
['Central', 'E', 'Airport']
];
function drawLines() {
for (m = 0; m < lineArray.length; m++) {
ctx1.lineWidth = 1;
ctx1.beginPath();
for (p = 1; p < lineArray[m].length; p++) {
var startStat = lineArray[m][0];
var stat = lookUp(startStat);
var beginX = station[stat][1];
var beginY = station[stat][2];
ctx1.moveTo(beginX, beginY);
var endStat = lineArray[m][p];
var endSt = lookUp(endStat);
var closeX = station[endSt][1];
var closeY = station[endSt][2];
ctx1.lineTo(closeX, closeY);
}
ctx1.stroke();
}
}
drawLines();
//END LINES*************************************
//SHORTEST PATH*********************************
function Graph() {
var neighbors = this.neighbors = {}; // Key = vertex, value = array of neighbors.
this.addEdge = function (u, v) {
if (neighbors[u] === undefined) { // Add the edge u -> v.
neighbors[u] = [];
}
neighbors[u].push(v);
if (neighbors[v] === undefined) { // Also add the edge v -> u in order
neighbors[v] = []; // to implement an undirected graph.
} // For a directed graph, delete
neighbors[v].push(u); // these four lines.
};
return this;
}
function shortestPath(graph, source, target) {
var stationPath = [];
var coordPath = [];
if (source == target) { // Delete these four lines if
print(source); // you want to look for a cycle
return; // when the source is equal to
} // the target.
var queue = [source],
visited = {
source: true
},
predecessor = {},
tail = 0;
while (tail < queue.length) {
var u = queue[tail++], // Pop a vertex off the queue.
neighbors = graph.neighbors[u];
for (var i = 0; i < neighbors.length; ++i) {
var v = neighbors[i];
if (visited[v]) {
continue;
}
visited[v] = true;
if (v === target) { // Check if the path is complete.
var path = [v]; // If so, backtrack through the path.
while (u !== source) {
path.push(u);
u = predecessor[u];
}
path.push(u);
path.reverse();
print(path.join(' → '));
//console.log('Path: ' + path);
for (s = 1; s < path.length; s++) {
stationPath.push(path[s]);
}
//console.log('Waypoints: ' + stationPath);
for (t = 0; t < stationPath.length; t++) {
staCo = lookUp(stationPath[t]);
staCoX = station[staCo][1];
staCoY = station[staCo][2];
coordPath.push([staCoX, staCoY]);
}
//console.log(coordPath);
return coordPath;
}
predecessor[v] = u;
queue.push(v);
}
}
print('there is no path from ' + source + ' to ' + target);
}
function print(s) { // A quick and dirty way to display output.
s = s || '';
document.getElementById('display').innerHTML += s + '<br>';
}
function findShortestPath(s1, s2) {
var graph = new Graph();
for (w = 0; w < lineArray.length; w++) {
var baseStation = lineArray[w][0];
for (z = 1; z < lineArray[w].length; z++) {
graph.addEdge(baseStation, lineArray[w][z]);
}
}
return (shortestPath(graph, s1, s2));
};
function lookUp(sta) {
//console.log(sta);
for (n = 0; n < station.length; n++) {
if (sta == station[n][0]) {
return n;
break;
}
}
}
//BUILD PODS*************************************
var podArray = [];
function Pod(startX, startY, wayStations, riders, color) {
this.startX = startX;
this.startY = startY;
this.wayStations = wayStations;
this.riders = riders;
this.color = color;
}
var colorArray = ['gold', 'orange', 'red', 'green', 'blue', 'black'];
function randomPass() {
occ = 1 + Math.floor(Math.random() * 6);
return occ;
}
//PROGRAM PODS*********************************************
for (i = 0; i < 3; i++) { //NUMBER OF PODS
//Start Station
var startOff = Math.floor(Math.random() * station.length);
var begSta = station[startOff][0];
var fromX = station[startOff][1];
var fromY = station[startOff][2];
//END STATION
var destNum = Math.floor(Math.random() * station.length);
while (startOff == destNum) {
destNum = Math.floor(Math.random() * station.length);
}
var endSta = station[destNum][0];
function getWayStations(beg, end) {
var fsp = findShortestPath(beg, end);
var nextArray = [];
nextArray.push([fromX, fromY]);
for (var f = 0; f < fsp.length; f++) {
nextArray.push(fsp[f]);
}
return nextArray;
}
//LOAD POD DATA
podArray.push(new Pod(
startX = fromX,
startY = fromY,
wayStations = getWayStations(begSta, endSta),
riders = randomPass(),
color = colorArray[riders - 1]))
}
//END PROGRAM PODS*********************************************
// calc waypoints traveling along nextArray
function calcWaypoints(nextArray) {
var frams = 100;
var waypoints = [];
for (var i = 1; i < nextArray.length; i++) {
var pt0 = nextArray[i - 1];
var pt1 = nextArray[i];
var dx = pt1[0] - pt0[0];
var dy = pt1[1] - pt0[1];
for (var j = 0; j < frams; j++) {
var x = pt0[0] + dx * j / frams //+ dxOff;
var y = pt0[1] + dy * j / frams //+ dyOff;
waypoints.push({
x: x,
y: y
});
};
}
return (waypoints);
}
animate();
var lastTime = 0;
var speed = 1; // higher is slower
function animate(time) {
// return if the desired time hasn't elapsed
if ((time - lastTime) < speed) {
requestAnimationFrame(animate);
return;
}
lastTime = time;
ctx3.clearRect(0, 0, layer3.width, layer3.height);
var callAgain = false;
for (var i = 0; i < podArray.length; i++) {
var aPod = podArray[i];
// calculate incremental points along the path
var points = calcWaypoints(aPod.wayStations);
if (t < points.length - 1) {
callAgain = true;
}
// draw pod from the last waypoint to the current waypoint
if(points.length > t){
ctx3.beginPath();
ctx3.moveTo(aPod.startX, aPod.startY); //(points[t - 1].x, points[t - 1].y);
ctx3.fillStyle = aPod.color;
ctx3.arc(points[t].x, points[t].y, 4, 0, Math.PI * 2, true);
ctx3.fill();
}
}
t++;
if(callAgain) requestAnimationFrame(animate);
}
<canvas id='layer1' style='z-index: 2;
position:absolute;
left:0px;
top:0px;
' height='600px' width='1000'>This text is displayed if your browser does not support HTML5 Canvas.</canvas>
<canvas id='layer2' style='z-index: 3;
position:absolute;
left:0px;
top:0px;
' height='600px' width='1000'>This text is displayed if your browser does not support HTML5 Canvas.</canvas>
<canvas id='layer3' style='z-index: 1;
position:absolute;
left:0px;
top:0px;
' height='600px' width='1000'>This text is displayed if your browser does not support HTML5 Canvas.</canvas>
<div id="display">
</id>
Related
(https://codepen.io/johnslipper/details/eYgZMRL)
var context = new AudioContext();
let dampening = 1;
function pluck(frequency) {
var pluck = context.createScriptProcessor(4096, 0, 1);
var N = Math.round(context.sampleRate / frequency);
var y = new Float32Array(N);
for (let i = 0; i < N; i++) {if (window.CP.shouldStopExecution(0)) break;
y[i] = Math.random() * 2 - 1;
}
window.CP.exitedLoop(0);let n = 0;
pluck.onaudioprocess = function (e) {
var output = e.outputBuffer.getChannelData(0);
for (let i = 0; i < e.outputBuffer.length; i++) {if (window.CP.shouldStopExecution(1)) break;
y[n] = (y[n] + y[(n + 1) % N]) / 2;
output[i] = y[n];
y[n] *= dampening;
n++;
if (n >= N) n = 0;
}window.CP.exitedLoop(1);
};
var bandpass = context.createBiquadFilter();
bandpass.type = "bandpass";
bandpass.frequency.value = frequency;
bandpass.Q.value = 1;
pluck.connect(bandpass);
setTimeout(() => {
pluck.disconnect();
}, 2000);
setTimeout(() => {
bandpass.disconnect();
}, 2000);
return bandpass;
}
function strum(fret, stringCount = 6, stagger = 150) {
dampening = 1;
var dst = context.destination;
for (let index = 0; index < stringCount; index++) {if (window.CP.shouldStopExecution(2)) break;
if (Number.isFinite(fret[index])) {
setTimeout(() => {
pluck(getFrequency(index, fret[index])).connect(dst);
}, stagger * index);
}
}window.CP.exitedLoop(2);
}
function getFrequency(string, fret) {
var A = 120;
var offsets = [-5, 0, 5, 10, 14, 19];
return A * Math.pow(2, (fret + offsets[string]) / 12);
}
context.resume().then(strum([0,2,2,1,0,0]));
!!! The ScriptProcessorNode is deprecated. Use AudioWorkletNode instead.
(The ScriptProcessorNode is deprecated. Use AudioWorkletNode instead.)
It is a very successful webaudio guitar application. How can I change it?
I'm trying to filter out all lines as a result from HoughLine function in opencv. Currently just using getLines() function to get all lines then sorting them according to their rho value for each line. Then applying for loop to remove duplicates, problem is as follows:
In code if in (r1, θ1) and (r2, θ2) which I'm comparing in inner for loop for both either (r1,r2) positive or (r1,r2) negative (based on html canvas) then it's easy to filter them. But if either of them is positve or negative respectively then that line also gets included in finalLines array. Which can be explained through following figure:
function:
function getLines(minthresh = 8) {
let t = 300,
j = 0;
linesMat = new cv.Mat();
let copy = org_img.clone();
while (t > 0 && j < minthresh) {
try {
cv.HoughLines(tmp, linesMat, 1, Math.PI / 180, t);
j = linesMat.matSize[0]
console.log(`t=${t} j=${j}`, linesMat.matSize);
} catch (err) {
console.log(`error for t=${t}`);
j = 0;
} finally {
t = t - 10;
}
}
console.log('hough lines', linesMat.rows);
let bool = true;
let lines = linesMat.data32F;
let rows = linesMat.rows;
let foundLines = [];
finalLines = [];
for (let i = 0; i < rows; i++) {
let l = [lines[i * 2], lines[i * 2 + 1]];
foundLines.push(l);
}
foundLines.sort((a, b) => a[0] - b[0]);
console.log('filtering lines starts, sorted found lines:', foundLines);
for (let [index, line] of foundLines.entries()) {
let rho = line[0];
let theta = line[1];
let a = Math.cos(theta);
let b = Math.sin(theta);
let x0 = a * rho;
let y0 = b * rho;
let startPoint = {
x: x0 - 1000 * b,
y: y0 + 1000 * a
};
let endPoint = {
x: x0 + 1000 * b,
y: y0 - 1000 * a
};
let color = [random(), random(), random(), 255];
cv.line(copy, startPoint, endPoint, color);
bool = false;
for (let [jindex, linet] of foundLines.slice(index + 1).entries()) {
console.log(`for index=${index} jindex=${jindex+1} linet=${linet} line=${line}`);
bool = false;
if (linet[0] !== line[0]) {
k = [Math.abs(linet[0] - line[0]) < 50, Math.abs(linet[1] - line[1]) < 0.5];
console.log(`k=${k}`);
if (k[0] && k[1]) {
bool = true;
break;
}
}
}
if (bool) {
continue;
}
finalLines.push(line);
console.log(`push for i=${index}`);
console.log(`%c rho=${rho} deg=${theta * (180/Math.PI)}`, `background: rgba(${color[0]}, ${color[1]}, ${color[2]}, 1); color: black`);
}
cv.imshow('op', copy);
}
I am creating a chrome extension and I have a canvas element inside of the popup.html file and some js in an external js file as Chrome does not allow for inline scripts on chrome extension pages. The canvas element loads sucessfully as I can see the border that I put around it but the code that fills it in is executing but not doing anything. I have tested the code using inline js on a plain html file using same parameters and it works. What am I doing wrong?
HTML file:
<button id="playTetrisButton" onclick="executeGame()">Play now!</button>(this button does not actually do anything as I stripped away its function for debugging and code will run without it)
<center><canvas width="320" height="500" id="game"></canvas></center>
<script src="tetris game code.js"></script>
JS FILE:
// This is the game code for tetris so not part of the actual website
// https://tetris.fandom.com/wiki/Tetris_Guideline
// get a random integer between the range of [min,max]
document.addEventListener('DOMCONTENTLOADED',domloaded,false);
function domloaded(){
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// generate a new tetromino sequence
// #see https://tetris.fandom.com/wiki/Random_Generator
function generateSequence() {
const sequence = ['I', 'J', 'L', 'O', 'S', 'T', 'Z'];
while (sequence.length) {
const rand = getRandomInt(0, sequence.length - 1);
const name = sequence.splice(rand, 1)[0];
tetrominoSequence.push(name);
}
}
// get the next tetromino in the sequence
function getNextTetromino() {
if (tetrominoSequence.length === 0) {
generateSequence();
}
const name = tetrominoSequence.pop();
const matrix = tetrominos[name];
// I and O start centered, all others start in left-middle
const col = playfield[0].length / 2 - Math.ceil(matrix[0].length / 2);
// I starts on row 21 (-1), all others start on row 22 (-2)
const row = name === 'I' ? -1 : -2;
return {
name: name, // name of the piece (L, O, etc.)
matrix: matrix, // the current rotation matrix
row: row, // current row (starts offscreen)
col: col // current col
};
}
// rotate an NxN matrix 90deg
// #see https://codereview.stackexchange.com/a/186834
function rotate(matrix) {
const N = matrix.length - 1;
const result = matrix.map((row, i) =>
row.map((val, j) => matrix[N - j][i])
);
return result;
}
// check to see if the new matrix/row/col is valid
function isValidMove(matrix, cellRow, cellCol) {
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] && (
// outside the game bounds
cellCol + col < 0 ||
cellCol + col >= playfield[0].length ||
cellRow + row >= playfield.length ||
// collides with another piece
playfield[cellRow + row][cellCol + col])
) {
return false;
}
}
}
return true;
}
// place the tetromino on the playfield
function placeTetromino() {
for (let row = 0; row < tetromino.matrix.length; row++) {
for (let col = 0; col < tetromino.matrix[row].length; col++) {
if (tetromino.matrix[row][col]) {
// game over if piece has any part offscreen
if (tetromino.row + row < 0) {
return showGameOver();
}
playfield[tetromino.row + row][tetromino.col + col] = tetromino.name;
}
}
}
// check for line clears starting from the bottom and working our way up
for (let row = playfield.length - 1; row >= 0; ) {
if (playfield[row].every(cell => !!cell)) {
// drop every row above this one
for (let r = row; r >= 0; r--) {
for (let c = 0; c < playfield[r].length; c++) {
playfield[r][c] = playfield[r-1][c];
}
}
}
else {
row--;
}
}
tetromino = getNextTetromino();
}
// show the game over screen
function showGameOver() {
cancelAnimationFrame(rAF);
gameOver = true;
context.fillStyle = 'black';
context.globalAlpha = 0.75;
context.fillRect(0, canvas.height / 2 - 30, canvas.width, 60);
context.globalAlpha = 1;
context.fillStyle = 'white';
context.font = '36px monospace';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('GAME OVER!', canvas.width / 2, canvas.height / 2);
}
const canvas = document.getElementById('game');
const context = canvas.getContext('2d');
const grid = 32;
const tetrominoSequence = [];
// keep track of what is in every cell of the game using a 2d array
// tetris playfield is 10x20, with a few rows offscreen
const playfield = [];
// populate the empty state
for (let row = -2; row < 20; row++) {
playfield[row] = [];
for (let col = 0; col < 10; col++) {
playfield[row][col] = 0;
}
}
// how to draw each tetromino
// #see https://tetris.fandom.com/wiki/SRS
const tetrominos = {
'I': [
[0,0,0,0],
[1,1,1,1],
[0,0,0,0],
[0,0,0,0]
],
'J': [
[1,0,0],
[1,1,1],
[0,0,0],
],
'L': [
[0,0,1],
[1,1,1],
[0,0,0],
],
'O': [
[1,1],
[1,1],
],
'S': [
[0,1,1],
[1,1,0],
[0,0,0],
],
'Z': [
[1,1,0],
[0,1,1],
[0,0,0],
],
'T': [
[0,1,0],
[1,1,1],
[0,0,0],
]
};
// color of each tetromino
const colors = {
'I': 'cyan',
'O': 'yellow',
'T': 'purple',
'S': 'green',
'Z': 'red',
'J': 'blue',
'L': 'orange'
};
let count = 0;
let tetromino = getNextTetromino();
let rAF = null; // keep track of the animation frame so we can cancel it
let gameOver = false;
// game loop
function loop() {
rAF = requestAnimationFrame(loop);
context.clearRect(0,0,canvas.width,canvas.height);
// draw the playfield
for (let row = 0; row < 20; row++) {
for (let col = 0; col < 10; col++) {
if (playfield[row][col]) {
const name = playfield[row][col];
context.fillStyle = colors[name];
// drawing 1 px smaller than the grid creates a grid effect
context.fillRect(col * grid, row * grid, grid-1, grid-1);
}
}
}
// draw the active tetromino
if (tetromino) {
// tetromino falls every 35 frames
if (++count > 35) {
tetromino.row++;
count = 0;
// place piece if it runs into anything
if (!isValidMove(tetromino.matrix, tetromino.row, tetromino.col)) {
tetromino.row--;
placeTetromino();
}
}
context.fillStyle = colors[tetromino.name];
for (let row = 0; row < tetromino.matrix.length; row++) {
for (let col = 0; col < tetromino.matrix[row].length; col++) {
if (tetromino.matrix[row][col]) {
// drawing 1 px smaller than the grid creates a grid effect
context.fillRect((tetromino.col + col) * grid, (tetromino.row + row) * grid, grid-1, grid-1);
}
}
}
}
}
// listen to keyboard events to move the active tetromino
document.addEventListener('keydown', function(e) {
if (gameOver) return;
// left and right arrow keys (move)
if (e.which === 37 || e.which === 39) {
const col = e.which === 37
? tetromino.col - 1
: tetromino.col + 1;
if (isValidMove(tetromino.matrix, tetromino.row, col)) {
tetromino.col = col;
}
}
// up arrow key (rotate)
if (e.which === 38) {
const matrix = rotate(tetromino.matrix);
if (isValidMove(matrix, tetromino.row, tetromino.col)) {
tetromino.matrix = matrix;
}
}
// down arrow key (drop)
if(e.which === 40) {
const row = tetromino.row + 1;
if (!isValidMove(tetromino.matrix, row, tetromino.col)) {
tetromino.row = row - 1;
placeTetromino();
return;
}
tetromino.row = row;
}
});
}
I get convergence, nevertheless, the results are never the same when the algorithm is refreshed. This occurs even when the data observations is the same dataset. Can anyone tell me where my methodology is wrong? For the life of me I can't figure out where the process is wrong.
function kmeans2(k, data, canvas, converge) {
this.canvas = jsHS.GetDimensions(canvas);
this.k = k;
this.centroids = []; // Array of centroids
this.centroids2compare = [];
this.data = data;
this.converge = converge;
this.init();
}
kmeans2.prototype.distance = function () {
var dif = 0,
iArray = jsHS.isArray(arguments);
if (iArray) {
if (arguments.length > 2) {
for (var i = 0; i < arguments.length; i+2) {
var p0 = arguments[i],
p1 = arguments[i + 1];
dif += Math.pow(p0[0] - p1[0], 2);
dif += Math.pow(p0[1] - p1[1], 2);
}
}
else {
var pd0 = arguments[0],
pd1 = arguments[1];
dif += Math.pow(pd0[0] - pd1[0], 2);
dif += Math.pow(pd0[1] - pd1[1], 2);
}
}
return Math.sqrt(dif);
};
kmeans2.prototype.Means = function (Array) {
var bin = 0;
[].forEach.call(Array, function(a){
bin += a;
});
return bin / Array.length;
};
kmeans2.prototype.init = function () {
for (var l = 0; l < this.k; l++) {
var dataItem = this.data[Math.floor(Math.random() * this.data.length)];
this.centroids.push(dataItem);
}
for (var i = 0; i < this.centroids.length; i++) {
if (i > 0) {
var distance = this.distance(this.centroids[i], this.centroids[i - 1]);
console.log(distance);
}
}
this.clusterCentroids(); // return centroid center after calculating means.
};
kmeans2.prototype.clusterCentroids = function () {
var points0 = [];
this.centroids2compare = this.centroids;
// Find distances between centroid and observations.
for (var d = 0; d < this.data.length; d++) {
var cinbin = [];
for (var c0 = 0; c0 < this.k; c0++) {
var dis = this.distance(this.centroids[c0], this.data[d]);
cinbin.push({ 'cid': c0, 'distance': dis });
}
var minResult = cinbin.reduce((cid, obj) => {
return obj.distance < cid.distance ? obj : cid;
});
points0.push({ 'id': d, 'datapoint': this.data[d], 'centroid': minResult.cid });
}
// Assign observations their appropriate centroid.
var centroidBin = [];
for (var c = 0; c < this.k; c++) {
var cb = [];
for (var p = 0; p < points0.length; p++) {
if (c === points0[p].centroid) {
cb.push(points0[p]);
}
}
centroidBin.push(cb);
}
// Calculate the mean distance between centroids and its assigned observations.
this.centroids = [];
for (var bin = 0; bin < centroidBin.length; bin++) {
var xAxis = [],
yAxis = [],
cb0 = centroidBin[bin];
[].forEach.call(cb0, function (dp) {
xAxis.push(dp.datapoint[0]);
yAxis.push(dp.datapoint[1]);
});
var xMean = this.Means(xAxis);
var yMean = this.Means(yAxis);
this.centroids.push([xMean, yMean]);
}
// Test for convergence. If stored centroids equal new centroids then convergence is achieved.
if (JSON.stringify(this.centroids2compare) !== JSON.stringify(this.centroids)) {
this.centroids2compare = [];
points0 = [];
this.clusterCentroids();
}
else {
this.converge(centroidBin, this.centroids);
}
};
window['jsHS']['kmeans2'] = kmeans2;
Implementation
var k50 = new jsi.kmeans2(5, Array50, canvas, function (con, centroids) {
var count50 = 0;
var cmark = {
x: 0,
y: 0,
rad:0,
clr: null,
setArc: function () {
ctx.beginPath();
ctx.arc(this.x, this.y, this.rad, 0, Math.PI * 2, true);
ctx.fillStyle = this.clr;
ctx.fill();
}
};
[].forEach.call(centroids, (c) => {
cmark.x = c[0];
cmark.y = c[1];
cmark.clr = '#0B6623';
cmark.rad = 25;
cmark.setArc();
});
});
This example plots the centroids on a canvas area fine enough but when the browser refreshes the centroids change.
I haven't looked much at your code, but I know that the k-means algorithm tends to give different results when you run it several times. This is because it's highly dependent on where the first centroids (which are selected randomly) are located.
The algorithm can find a local minimum and get "stuck" there, and terminate.
There's no guarantee that you will find the global minimum the first time you run it.
var imagething;
var kernel = [[1, 2, 1], [2, 4, 2], [1, 2, 1]];
var sum_of_elements = 0;
var mult_factor = 0.0625;
var bias = 0;
function preload() {
imagething = loadImage('data/image.png');
}
function setup() {
createCanvas(1000, 1000);
imagething.loadPixels();
image(imagething, 0, 0);
makeBlurred(imagething, 1);
image(imagething, 512, 0);
}
function imageIndex(img, x, y) {
return 4 * (x + y * img.width);
}
function makeBlurred() {
for(var i = 0; i < kernel.length; i++) {
for(var j = 0; j < kernel.length; j++) {
sum_of_elements += kernel[i][j];
}
}
for(var x = 0; x < imagething.width; x++) {
for(var y = 0; y < imagething.height; y++) {
var red = 0;
var green = 0;
var blue = 0;
for(var i = 0; i < kernel.length; i++) {
for(var j = 0; j < kernel.length; j++) {
var imageX = (x - kernel.length / 2 + i + imagething.width) % imagething.width;
var imageY = (y - kernel.length / 2 + j + imagething.height) % imagething.height;
var RGB = imageIndex(imagething, imageX, imageY);
var pix = imagething.pixels;
var R = pix[RGB];
var G = pix[RGB + 1];
var B = pix[RGB + 2];
console.log("Start: ",R,G,B);
red += (R * kernel[i][j]);
green += (G * kernel[i][j]);
blue += (B * kernel[i][j]);
}
}
var outR;
var outG;
var outB;
outR = Math.min(Math.max(Math.round(red * mult_factor), 0), 255);
outG = Math.min(Math.max(Math.round(green * mult_factor), 0), 255);
outB = Math.min(Math.max(Math.round(blue * mult_factor), 0), 255);
console.log("End: ", outR, outG, outB);
pix[RGB] = outR;
pix[RGB + 1] = outG;
pix[RGB + 2] = outB;
imagething.updatePixels();
}
}
}
So I am referencing this blog post on kernel-based image processing and I am trying to blur my image. I am using the blur matrix and I use the original image and it should turn out like this but it ends up like this so I must be doing something wrong. Help is much appreciated :)