@ai-on-browser/data-analysis-models
Version:
Data analysis model package without any dependencies
196 lines (188 loc) • 4.8 kB
JavaScript
/**
* Snakes (active contour model)
*/
export default class Snakes {
// http://www.sanko-shoko.net/note.php?id=wc1z
// https://www.slideshare.net/Arumaziro/ss-37035661
// https://en.wikipedia.org/wiki/Active_contour_model
/**
* @param {number} alpha Penalty for length
* @param {number} beta Penalty for curvature
* @param {number} gamma Penalty for conformity with image
* @param {number} [k] Number of vertices
*/
constructor(alpha, beta, gamma, k = 100) {
this._alpha = alpha
this._beta = beta
this._gamma = gamma
this._k = k
this._v = []
}
_convolute(x, kernel) {
const a = []
for (let i = 0; i < x.length; i++) {
a[i] = []
for (let j = 0; j < x[i].length; j++) {
const v = Array(x[i][j].length).fill(0)
for (let s = 0; s < kernel.length; s++) {
let n = i + s - Math.floor(kernel.length / 2)
n = Math.max(0, Math.min(x.length - 1, n))
for (let t = 0; t < kernel[s].length; t++) {
let m = j + t - Math.floor(kernel[s].length / 2)
m = Math.max(0, Math.min(x[n].length - 1, m))
for (let u = 0; u < x[n][m].length; u++) {
v[u] += x[n][m][u] * kernel[s][t]
}
}
}
a[i][j] = v
}
}
return a
}
/**
* Initialize model.
* @param {Array<Array<Array<number>>>} x Training data
*/
init(x) {
const gx = this._convolute(x, [
[1, 0, -1],
[2, 0, -2],
[1, 0, -1],
])
const gy = this._convolute(x, [
[1, 2, 1],
[0, 0, 0],
[-1, -2, -1],
])
this._g = []
for (let i = 0; i < gx.length; i++) {
this._g[i] = []
for (let j = 0; j < gx[i].length; j++) {
this._g[i][j] = 0
for (let u = 0; u < gx[i][j].length; u++) {
this._g[i][j] += Math.sqrt(gx[i][j][u] ** 2 + gy[i][j][u] ** 2)
}
this._g[i][j] /= gx[i][j].length
}
}
this._v = []
const r = []
for (let k = 0; k < 4; k++) {
r.push(Math.round((k * this._k) / 4))
}
r.push(this._k)
for (let i = 0; i < r[1] - r[0]; i++) {
this._v.push([0, Math.round((i * (this._g[0].length - 1)) / (r[1] - r[0]))])
}
for (let i = 0; i < r[2] - r[1]; i++) {
this._v.push([Math.round((i * (this._g.length - 1)) / (r[2] - r[1])), this._g[0].length - 1])
}
for (let i = 0; i < r[3] - r[2]; i++) {
this._v.push([
this._g.length - 1,
this._g[0].length - 1 - Math.round((i * (this._g[0].length - 1)) / (r[3] - r[2])),
])
}
for (let i = 0; i < r[4] - r[3]; i++) {
this._v.push([this._g.length - 1 - Math.round((i * (this._g.length - 1)) / (r[2] - r[1])), 0])
}
}
_energy(v) {
let elen = 0
let ecurv = 0
let eimg = 0
for (let i = 0; i < v.length; i++) {
const jb = i === 0 ? v.length - 1 : i - 1
const ja = i === v.length - 1 ? 0 : i + 1
elen += v[i].reduce((s, a, d) => s + (a - v[jb][d]) ** 2, 0)
ecurv += v[i].reduce((s, a, d) => s + (v[ja][d] + v[jb][d] - 2 * a) ** 2, 0)
eimg -= this._g[v[i][0]][v[i][1]]
}
return this._alpha * elen + this._beta * ecurv + this._gamma * eimg
}
/**
* Fit model.
*/
fit() {
const d = [
[0, 1],
[1, 1],
[1, 0],
[1, -1],
[0, -1],
[-1, -1],
[-1, 0],
[-1, 1],
]
for (let i = 0; i < this._v.length; i++) {
let min_e = this._energy(this._v)
let min_v = null
for (let k = 0; k < d.length; k++) {
const nv = this._v[i].map((a, t) => a + d[k][t])
if (nv[0] < 0 || nv[1] < 0 || nv[0] >= this._g.length || nv[1] >= this._g[0].length) {
continue
}
const v = this._v.concat()
v[i] = nv
const e = this._energy(v)
if (e < min_e) {
min_e = e
min_v = nv
}
}
if (min_v) {
this._v[i] = min_v
}
}
}
/**
* Returns predicted edge flags.
* @returns {Array<Array<boolean>>} Predicted values. `true` if a pixel is edge.
*/
predict() {
const p = []
for (let i = 0; i < this._g.length; i++) {
p[i] = Array(this._g[i].length).fill(false)
}
for (let i = 0; i < this._v.length; i++) {
const x0 = this._v[i][0]
const y0 = this._v[i][1]
const x1 = this._v[i === this._v.length - 1 ? 0 : i + 1][0]
const y1 = this._v[i === this._v.length - 1 ? 0 : i + 1][1]
const dx = Math.abs(x1 - x0)
const dy = Math.abs(y1 - y0)
p[x0][y0] = true
if (dx > dy) {
let d = 2 * dy - dx
let y = y0
const xs = x1 > x0 ? 1 : -1
for (let x = x0 + xs; xs > 0 ? x < x1 : x > x1; x += xs) {
if (d > 0) {
y += y1 > y0 ? 1 : -1
p[x][y] = true
d += 2 * dy - 2 * dx
} else {
p[x][y] = true
d += 2 * dy
}
}
} else {
let d = 2 * dx - dy
let x = x0
const ys = y1 > y0 ? 1 : -1
for (let y = y0 + ys; ys > 0 ? y < y1 : y > y1; y += ys) {
if (d > 0) {
x += x1 > x0 ? 1 : -1
p[x][y] = true
d += 2 * dx - 2 * dy
} else {
p[x][y] = true
d += 2 * dx
}
}
}
}
return p
}
}