You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

531 lines
12 KiB
JavaScript

import { logger } from "./log.js";
import {
matmul,
euler_angle_to_rotate_matrix_3by3,
transpose,
matmul2,
} from "./util.js";
import request from "./request.js";
const annMath = {
sub: function (a, b) {
//pos, rot, scale
let c = [];
for (let i in a) {
c[i] = a[i] - b[i];
}
return this.norm(c);
},
div: function (a, d) {
// d is scalar
let c = [];
for (let i in a) {
c[i] = a[i] / d;
}
return c;
},
add: function (a, b) {
let c = [];
for (let i in a) {
c[i] = a[i] + b[i];
}
return this.norm(c);
},
mul: function (
a,
d // d is scalar
) {
let c = [];
for (let i in a) {
c[i] = a[i] * d;
}
return this.norm(c);
},
norm: function (c) {
for (let i = 3; i < 6; i++) {
if (c[i] > Math.PI) {
c[i] -= Math.PI * 2;
} else if (c[i] < -Math.PI) {
c[i] += Math.PI * 2;
}
}
return c;
},
normAngle: function (a) {
if (a > Math.PI) {
return a - Math.PI * 2;
} else if (a < -Math.PI) {
return a + Math.PI * 2;
}
return a;
},
eleMul: function (
a,
b //element-wise multiplication
) {
let c = [];
for (let i in a) {
c[i] = a[i] * b[i];
}
return c;
},
};
var ml = {
backend: tf.getBackend(),
calibrate_axes: function (points) {
console.log("backend of tensorflow:", tf.getBackend());
console.log("number of points:", points.count);
var center_points = {};
for (var i = 0; i < points.count; i++) {
if (
points.array[i * 3] < 10 &&
points.array[i * 3] > -10 &&
points.array[i * 3 + 1] < 10 &&
points.array[i * 3 + 1] > -10
) {
// x,y in [-10,10]
var key =
(10 + Math.round(points.array[i * 3])) * 100 +
(Math.round(points.array[i * 3 + 1]) + 10);
if (center_points[key]) {
// save only minimal index
if (
points.array[i * 3 + 2] < points.array[center_points[key] * 3 + 2]
) {
center_points[key] = i;
}
} else {
center_points[key] = i;
}
}
}
var center_point_indices = [];
for (var i in center_points) {
center_point_indices.push(center_points[i]);
}
//console.log(center_point_indices);
var points_2d = center_point_indices.map((i) => [
points.array[i * 3],
points.array[i * 3 + 1],
points.array[i * 3 + 2],
]);
var points_array = points_2d.flatMap((x) => x);
var sum = points_2d.reduce(
function (s, x) {
return [s[0] + x[0], s[1] + x[1], s[2] + x[2]];
},
[0, 0, 0]
);
var count = points_2d.length;
var mean = [sum[0] / count, sum[1] / count, sum[2] / count];
var data_centered = points_2d.map(function (x) {
return [x[0] - mean[0], x[1] - mean[1], x[2] - mean[2]];
});
var normal_v = this.train(data_centered);
data.world.add_line(mean, [
-normal_v[0] * 10,
-normal_v[1] * 10,
normal_v[2] * 10,
]);
data.world.lidar.reset_points(points_array);
/*
var trans_matrix = transpose(euler_angle_to_rotate_matrix_3by3({x:Math.atan2(normal_v[1], -1), y: 0, z: 0}));
var transfromed_point_array = matmul(trans_matrix, points_array, 3);
data.world.lidar.reset_points(transfromed_point_array);
//data.world.lidar.set_spec_points_color(center_point_indices, {x:1,y:0,z:0});
//data.world.lidar.update_points_color();
*/
return center_point_indices;
},
train: function (
data_centered // data is ?*3 array.
) {
var XY = data_centered.map(function (x) {
return x.slice(0, 2);
});
var Z = data_centered.map(function (x) {
return x[2];
});
var x = tf.tensor2d(XY);
var para = tf.variable(tf.tensor2d([[Math.random(), Math.random()]]));
const learningRate = 0.00001;
const optimizer = tf.train.sgd(learningRate);
para.print();
for (var i = 0; i < 20; i++) {
optimizer.minimize(function () {
var dists = tf.matMul(para, x.transpose());
var sqrdiff = tf.squaredDifference(dists, Z);
var loss = tf.div(tf.sum(sqrdiff), sqrdiff.shape[0]);
loss.print();
return loss;
});
console.log(i);
para.print();
}
var pv = para.dataSync();
console.log("train result: ", pv);
return [pv[0], pv[1], 1];
},
// data is N*2 matrix,
l_shape_fit: function (data) {
// cos, sin
// -sin, cos
var A = tf.tensor2d(data);
//A = tf.expandDims(A, [0]);
var theta = [];
var min = 0;
var min_index = 0;
for (var i = 0; i <= 90; i += 1) {
var obj = cal_objetive(A, i);
if (min == 0 || min > obj) {
min_index = i;
min = obj;
}
}
console.log(min_index, min);
return min;
//end of func
function cal_objetive(A, theta) {
let r = (theta * Math.PI) / 180;
let bases = tf.tensor2d([
[Math.cos(r), -Math.sin(r)],
[Math.sin(r), Math.cos(r)],
]);
let proj = tf.matMul(A, bases); // n * 2
let max = tf.max(proj, 0); // 1*2
let min = tf.min(proj, 0); // 1*2
var dist_to_min = tf.sum(tf.square(tf.sub(proj, min)), 0);
var dist_to_max = tf.sum(tf.square(tf.sub(max, proj)), 0);
// axis 0
var dist0, dist1; // dist to axis 0, axis 1
if (dist_to_min.gather(0).dataSync() < dist_to_max.gather(0).dataSync()) {
dist0 = tf.sub(proj.gather([0], 1), min.gather(0));
} else {
dist0 = tf.sub(max.gather(0), proj.gather([0], 1));
}
if (dist_to_min.gather(1).dataSync() < dist_to_max.gather(1).dataSync()) {
dist1 = tf.sub(proj.gather([1], 1), min.gather(1));
} else {
dist1 = tf.sub(max.gather(1), proj.gather([1], 1));
}
// concat dist0, dist1
var min_dist = tf.concat([dist0, dist1], 1).min(1);
return min_dist.sum().dataSync()[0];
}
},
// predict_rotation_cb: function(data, callback){
// var xhr = new XMLHttpRequest();
// // we defined the xhr
// xhr.onreadystatechange = function () {
// if (this.readyState != 4)
// return;
// if (this.status == 200) {
// var ret = JSON.parse(this.responseText);
// console.log(ret);
// callback(ret.angle);
// }
// else{
// console.log(this);
// }
// };
// xhr.open('POST', "/predict_rotation", true);
// xhr.send(JSON.stringify({"points": data}));
// },
predict_rotation: function (data) {
const req = new Request(request + "/predict_rotation");
let init = {
method: "POST",
body: JSON.stringify({ points: data }),
};
// we defined the xhr
console.log("start predict rotatoin.", data.length, "points");
return fetch(req, init)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
console.log("predict rotatoin response received.");
return response.json();
}
})
.catch((reject) => {
console.log("error predicting yaw angle!");
});
},
// autoadj is async
interpolate_annotation: async function (anns, autoAdj, onFinishOneBox) {
let i = 0;
while (true) {
while (i + 1 < anns.length && !(anns[i] && !anns[i + 1])) {
i++;
}
let start = i;
i += 2;
while (i < anns.length && !anns[i]) {
i++;
}
if (i < anns.length) {
let end = i;
// insert (begin, end)
let interpolate_step = annMath.div(
annMath.sub(anns[end], anns[start]),
end - start
);
for (let inserti = start + 1; inserti < end; inserti++) {
let tempAnn = annMath.add(anns[inserti - 1], interpolate_step);
if (autoAdj) {
try {
let adjustedAnn = await autoAdj(inserti, tempAnn);
let adjustedYaw = annMath.normAngle(adjustedAnn[5] - tempAnn[5]);
if (Math.abs(adjustedYaw) > Math.PI / 2) {
console.log("adjust angle by Math.PI.");
adjustedAnn[5] = annMath.normAngle(adjustedAnn[5] + Math.PI);
}
if (!pointsGlobalConfig.enableAutoRotateXY) {
// adjustedAnn[3] = tempAnn[3];
// adjustedAnn[4] = tempAnn[4];
adjustedAnn[3] = 0;
adjustedAnn[4] = 0;
}
tempAnn = adjustedAnn;
} catch (e) {
console.log(e);
}
//
}
anns[inserti] = tempAnn;
// adjust step since we have finished annotate one more box.
interpolate_step = annMath.div(
annMath.sub(anns[end], anns[inserti]),
end - inserti
);
if (onFinishOneBox) onFinishOneBox(inserti);
}
} else {
break;
}
}
// interpolate finished
//forward
i = 0;
while (i < anns.length && !anns[i]) i++;
if (i < anns.length) {
let filter = new MaFilter(anns[i]);
i++;
while (i < anns.length && anns[i]) {
filter.update(anns[i]);
i++;
}
while (i < anns.length && !anns[i]) {
let tempAnn = filter.predict();
if (autoAdj) {
try {
let adjustedAnn = await autoAdj(i, tempAnn);
let adjustedYaw = annMath.normAngle(adjustedAnn[5] - tempAnn[5]);
if (Math.abs(adjustedYaw) > Math.PI / 2) {
console.log("adjust angle by Math.PI.");
adjustedAnn[5] = annMath.normAngle(adjustedAnn[5] + Math.PI);
}
tempAnn = adjustedAnn;
filter.update(tempAnn);
} catch (error) {
console.log(error);
filter.nextStep(tempAnn);
}
} else {
filter.nextStep(tempAnn);
}
anns[i] = tempAnn;
// we should update
if (onFinishOneBox) onFinishOneBox(i);
i++;
}
}
// now extrapolate
//backward
i = anns.length - 1;
while (i >= 0 && !anns[i]) i--;
if (i >= 0) {
let filter = new MaFilter(anns[i]);
i--;
while (i >= 0 && anns[i]) {
filter.update(anns[i]);
i--;
}
while (i >= 0 && !anns[i]) {
let tempAnn = filter.predict();
if (autoAdj) {
let adjustedAnn = await autoAdj(i, tempAnn).catch((e) => {
logger.log(e);
return tempAnn;
});
let adjustedYaw = annMath.normAngle(adjustedAnn[5] - tempAnn[5]);
if (Math.abs(adjustedYaw) > Math.PI / 2) {
console.log("adjust angle by Math.PI.");
adjustedAnn[5] = annMath.normAngle(adjustedAnn[5] + Math.PI);
}
tempAnn = adjustedAnn;
filter.update(tempAnn);
} else {
filter.nextStep(tempAnn);
}
anns[i] = tempAnn;
if (onFinishOneBox) onFinishOneBox(i);
i--;
}
}
return anns;
},
};
function MaFilter_tf(initX) {
// moving average filter
this.x = tf.tensor1d(initX); // pose
this.step = 0;
this.v = tf.zeros([9]); // velocity
this.decay = tf.tensor1d([0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]);
this.update = function (x) {
if (this.step == 0) {
this.v = tf.sub(x, this.x);
} else {
this.v = tf.add(
tf.mul(tf.sub(x, this.x), this.decay),
tf.mul(this.v, tf.sub(1, this.decay))
);
}
this.x = x;
this.step++;
};
this.predict = function () {
let pred = tf.concat([tf.add(this.x, this.v).slice(0, 6), this.x.slice(6)]);
return pred.dataSync();
};
this.nextStep = function (x) {
this.x = x;
this.step++;
};
}
function MaFilter(initX) {
// moving average filter
this.x = initX; // pose
this.step = 0;
this.v = [0, 0, 0, 0, 0, 0, 0, 0, 0]; // velocity
this.ones = [1, 1, 1, 1, 1, 1, 1, 1, 1];
this.decay = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5];
this.update = function (x) {
if (this.step == 0) {
this.v = annMath.sub(x, this.x);
} else {
this.v = annMath.add(
annMath.eleMul(annMath.sub(x, this.x), this.decay),
annMath.eleMul(this.v, annMath.sub(this.ones, this.decay))
);
}
this.x = x;
this.step++;
};
this.predict = function () {
let pred = [...annMath.add(this.x, this.v).slice(0, 6), ...this.x.slice(6)];
return pred;
};
this.nextStep = function (x) {
this.x = x;
this.step++;
};
}
export { ml, MaFilter };