diff --git a/hunks/adhoc/center.js b/hunks/adhoc/center.js new file mode 100644 index 0000000000000000000000000000000000000000..8fbc5c5d14987e872aac26885a120876d12c5d1c --- /dev/null +++ b/hunks/adhoc/center.js @@ -0,0 +1,99 @@ +/* + +find pixel space hotspot + +*/ + +import { + Hunkify, + Input, + Output, + State +} from '../hunks.js' + +import REG from '../../libs/regression.js' + +export default function HotSpotHunter(){ + Hunkify(this) + // will assume this is grayscale'd, and max-dr'd already + let ipImg = this.input('ImageData', 'image') + + let opX = this.output('number', 'x') + let opY = this.output('number', 'y') + + console.log('REG?', REG) + + this.dom = $('<div>').get(0) + // one to draw into: + let canvas = $('<canvas>').get(0) + let ctx = canvas.getContext('2d') + canvas.width = 395 + canvas.height = 395 + // and ah virtual friend: + let vc = $('<canvas>').get(0) + let vctx = vc.getContext('2d') + + this.onload = () => { + $(this.dom).append(canvas) + } + + this.loop = () => { + if(ipImg.io() && !opX.io() && !opY.io()){ + let img = ipImg.get() + // first, just hunt for abs. peak: + // draw in to virtual, + //ctx.putImageData(img, 0, 0) + vc.height = img.height + vc.width = img.width + let scale = canvas.width / img.width + vctx.putImageData(img, 0, 0) + canvas.height = canvas.width * (img.height / img.width) + ctx.drawImage(vc, 0, 0, canvas.width, canvas.height) + // ok, that was just drawing into the canvas - we + // want to find the center by inspecting the actual image data... + let max = -Infinity + let mp = {x: 0, y: 0} + for(let i = 0; i < img.data.length; i += 4){ + if(img.data[i] > max){ + max = img.data[i] + mp.x = i % (img.width * 4) / 4 + mp.y = Math.floor(i / (img.width * 4)) + } + } + // if we try to fit a quadratic across some domain, centered here... I need to collect + // *the appropriate data* + // this is easiest in the x-dir bf oc the data shape + let xSamples = [] + let ySamples = [] + let xCI = mp.y * (img.width * 4) + (mp.x * 4)// center index + for(let i = -10; i < 11; i ++){ + xSamples.push([i, img.data[xCI + i * 4]]) + ySamples.push([i, img.data[xCI + i * 4 * img.width]]) + } + //console.log('samp', xSamples) + let regX = REG.polynomial(xSamples, {order: 2, precision: 6}) + let regY = REG.polynomial(ySamples, {order: 2, precision: 6}) + //console.log('ret', ret) + // vertex is -b / 2a + let xVert = -regX.equation[1] / (2*regX.equation[0]) + let yVert = -regY.equation[1] / (2*regY.equation[0]) + mp.x += xVert + mp.y += yVert + //console.log('mp.x, .y', mp.x, mp.y) + // that's great, let's draw it: + ctx.beginPath() + ctx.moveTo(mp.x * scale, 0) + ctx.lineTo(mp.x * scale, canvas.height)//ctx.height) + ctx.stroke() + ctx.beginPath() + ctx.moveTo(0, mp.y * scale) + ctx.lineTo(canvas.width, mp.y * scale) + ctx.stroke() + //ctx.arc(mp.x * scale, mp.y * scale, 8, 0, 2*Math.PI) + //ctx.stroke() + // and ship em + opX.put(mp.x) + opY.put(mp.y) + } + } +} diff --git a/hunks/adhoc/correlate.js b/hunks/adhoc/correlate.js index 601ebbf317d68bd6db748dcf137f6283d8adf812..648f6f66e934f370eb68ce347e00a57df922b75d 100644 --- a/hunks/adhoc/correlate.js +++ b/hunks/adhoc/correlate.js @@ -1,6 +1,6 @@ /* -attempt of fft cross correlation for fast tracking / subpixel measurements +attempt for barebones template matching w/ cross correlation dot product */ @@ -99,8 +99,8 @@ function worker(){ let resY = b.height - a.height let numruns = resX * resY // the move now is to make an md array of these values, - let bArr = packRBGA(b) //packGrayscale(b) - let aArr = packRBGA(a) //packGrayscale(a) + let bArr = packGrayscale(b) //packRBGA(b) //packGrayscale(b) + let aArr = packGrayscale(a) //packGrayscale(a) // ok, results array like[x][y] let result = [] for (let x = 0; x < resX; x++) { @@ -109,7 +109,7 @@ function worker(){ // now fill, for (let x = 0; x < resX; x++) { for (let y = 0; y < resY; y++) { - result[x][y] = correlateRBGA(aArr, bArr, x, y) + result[x][y] = correlateGrayscale(aArr, bArr, x, y)//correlateRBGA(aArr, bArr, x, y) } } // make image from the result, @@ -160,11 +160,11 @@ export default function Correlate() { let resOut = this.output('ImageData', 'correlation') let canvasA = $('<canvas>').get(0) - canvasA.width = 50 - canvasA.height = 50 + canvasA.width = 24 + canvasA.height = 24 let ctxA = canvasA.getContext('2d') - ctxA.width = 50 - ctxA.height = 50 + ctxA.width = 24 + ctxA.height = 24 let canvasB = $('<canvas>').get(0) let ctxB = canvasB.getContext('2d') @@ -214,9 +214,18 @@ export default function Correlate() { // now we can pull the imagedata (scaled) from here, let b = ctxB.getImageData(0, 0, canvasB.width, canvasB.height) // and the thing we want to find, to test, just pick the middle: - let a = ctxB.getImageData(b.width / 2, b.height / 2, 50, 50) + //let a = ctxB.getImageData(b.width / 2, b.height / 2, 25, 25) // and write that out, to debug ... - ctxA.putImageData(a, 0, 0) + //ctxA.putImageData(a, 0, 0) + ctxA.fillStyle = 'white' + ctxA.fillRect(0,0,24,24) + ctxA.fillStyle = 'black' + // ctxA.arc(12,12,8, 0, 2*Math.PI) + // ctxA.fillStyle = 'black' + // ctxA.fill() + ctxA.fillRect(0,0,12,12) + ctxA.fillRect(12,12,24,24) + let a = ctxA.getImageData(0,0,24,24) webWorker.postMessage({a: a, b: b}) running = true } diff --git a/libs/regression.js b/libs/regression.js new file mode 100644 index 0000000000000000000000000000000000000000..7165405f76bec32b58b032797d15cd3dbb990c3a --- /dev/null +++ b/libs/regression.js @@ -0,0 +1,330 @@ +// http://tom-alexander.github.io/regression-js/ +// jake repackages for ES6 browser module + +function _toConsumableArray(arr) { + if (Array.isArray(arr)) { + for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { + arr2[i] = arr[i]; + } + + return arr2; + } else { + return Array.from(arr); + } +} + +var DEFAULT_OPTIONS = { order: 2, precision: 2, period: null }; + +/** + * Determine the coefficient of determination (r^2) of a fit from the observations + * and predictions. + * + * @param {Array<Array<number>>} data - Pairs of observed x-y values + * @param {Array<Array<number>>} results - Pairs of observed predicted x-y values + * + * @return {number} - The r^2 value, or NaN if one cannot be calculated. + */ +function determinationCoefficient(data, results) { + var predictions = []; + var observations = []; + + data.forEach(function(d, i) { + if (d[1] !== null) { + observations.push(d); + predictions.push(results[i]); + } + }); + + var sum = observations.reduce(function(a, observation) { + return a + observation[1]; + }, 0); + var mean = sum / observations.length; + + var ssyy = observations.reduce(function(a, observation) { + var difference = observation[1] - mean; + return a + difference * difference; + }, 0); + + var sse = observations.reduce(function(accum, observation, index) { + var prediction = predictions[index]; + var residual = observation[1] - prediction[1]; + return accum + residual * residual; + }, 0); + + return 1 - sse / ssyy; +} + +/** + * Determine the solution of a system of linear equations A * x = b using + * Gaussian elimination. + * + * @param {Array<Array<number>>} input - A 2-d matrix of data in row-major form [ A | b ] + * @param {number} order - How many degrees to solve for + * + * @return {Array<number>} - Vector of normalized solution coefficients matrix (x) + */ +function gaussianElimination(input, order) { + var matrix = input; + var n = input.length - 1; + var coefficients = [order]; + + for (var i = 0; i < n; i++) { + var maxrow = i; + for (var j = i + 1; j < n; j++) { + if (Math.abs(matrix[i][j]) > Math.abs(matrix[i][maxrow])) { + maxrow = j; + } + } + + for (var k = i; k < n + 1; k++) { + var tmp = matrix[k][i]; + matrix[k][i] = matrix[k][maxrow]; + matrix[k][maxrow] = tmp; + } + + for (var _j = i + 1; _j < n; _j++) { + for (var _k = n; _k >= i; _k--) { + matrix[_k][_j] -= matrix[_k][i] * matrix[i][_j] / matrix[i][i]; + } + } + } + + for (var _j2 = n - 1; _j2 >= 0; _j2--) { + var total = 0; + for (var _k2 = _j2 + 1; _k2 < n; _k2++) { + total += matrix[_k2][_j2] * coefficients[_k2]; + } + + coefficients[_j2] = (matrix[n][_j2] - total) / matrix[_j2][_j2]; + } + + return coefficients; +} + +/** + * Round a number to a precision, specificed in number of decimal places + * + * @param {number} number - The number to round + * @param {number} precision - The number of decimal places to round to: + * > 0 means decimals, < 0 means powers of 10 + * + * + * @return {numbr} - The number, rounded + */ +function round(number, precision) { + var factor = Math.pow(10, precision); + return Math.round(number * factor) / factor; +} + +/** + * The set of all fitting methods + * + * @namespace + */ +var methods = { + linear: function linear(data, options) { + var sum = [0, 0, 0, 0, 0]; + var len = 0; + + for (var n = 0; n < data.length; n++) { + if (data[n][1] !== null) { + len++; + sum[0] += data[n][0]; + sum[1] += data[n][1]; + sum[2] += data[n][0] * data[n][0]; + sum[3] += data[n][0] * data[n][1]; + sum[4] += data[n][1] * data[n][1]; + } + } + + var run = len * sum[2] - sum[0] * sum[0]; + var rise = len * sum[3] - sum[0] * sum[1]; + var gradient = run === 0 ? 0 : round(rise / run, options.precision); + var intercept = round(sum[1] / len - gradient * sum[0] / len, options.precision); + + var predict = function predict(x) { + return [round(x, options.precision), round(gradient * x + intercept, options.precision)]; + }; + + var points = data.map(function(point) { + return predict(point[0]); + }); + + return { + points: points, + predict: predict, + equation: [gradient, intercept], + r2: round(determinationCoefficient(data, points), options.precision), + string: intercept === 0 ? 'y = ' + gradient + 'x' : 'y = ' + gradient + 'x + ' + intercept + }; + }, + exponential: function exponential(data, options) { + var sum = [0, 0, 0, 0, 0, 0]; + + for (var n = 0; n < data.length; n++) { + if (data[n][1] !== null) { + sum[0] += data[n][0]; + sum[1] += data[n][1]; + sum[2] += data[n][0] * data[n][0] * data[n][1]; + sum[3] += data[n][1] * Math.log(data[n][1]); + sum[4] += data[n][0] * data[n][1] * Math.log(data[n][1]); + sum[5] += data[n][0] * data[n][1]; + } + } + + var denominator = sum[1] * sum[2] - sum[5] * sum[5]; + var a = Math.exp((sum[2] * sum[3] - sum[5] * sum[4]) / denominator); + var b = (sum[1] * sum[4] - sum[5] * sum[3]) / denominator; + var coeffA = round(a, options.precision); + var coeffB = round(b, options.precision); + var predict = function predict(x) { + return [round(x, options.precision), round(coeffA * Math.exp(coeffB * x), options.precision)]; + }; + + var points = data.map(function(point) { + return predict(point[0]); + }); + + return { + points: points, + predict: predict, + equation: [coeffA, coeffB], + string: 'y = ' + coeffA + 'e^(' + coeffB + 'x)', + r2: round(determinationCoefficient(data, points), options.precision) + }; + }, + logarithmic: function logarithmic(data, options) { + var sum = [0, 0, 0, 0]; + var len = data.length; + + for (var n = 0; n < len; n++) { + if (data[n][1] !== null) { + sum[0] += Math.log(data[n][0]); + sum[1] += data[n][1] * Math.log(data[n][0]); + sum[2] += data[n][1]; + sum[3] += Math.pow(Math.log(data[n][0]), 2); + } + } + + var a = (len * sum[1] - sum[2] * sum[0]) / (len * sum[3] - sum[0] * sum[0]); + var coeffB = round(a, options.precision); + var coeffA = round((sum[2] - coeffB * sum[0]) / len, options.precision); + + var predict = function predict(x) { + return [round(x, options.precision), round(round(coeffA + coeffB * Math.log(x), options.precision), options.precision)]; + }; + + var points = data.map(function(point) { + return predict(point[0]); + }); + + return { + points: points, + predict: predict, + equation: [coeffA, coeffB], + string: 'y = ' + coeffA + ' + ' + coeffB + ' ln(x)', + r2: round(determinationCoefficient(data, points), options.precision) + }; + }, + power: function power(data, options) { + var sum = [0, 0, 0, 0, 0]; + var len = data.length; + + for (var n = 0; n < len; n++) { + if (data[n][1] !== null) { + sum[0] += Math.log(data[n][0]); + sum[1] += Math.log(data[n][1]) * Math.log(data[n][0]); + sum[2] += Math.log(data[n][1]); + sum[3] += Math.pow(Math.log(data[n][0]), 2); + } + } + + var b = (len * sum[1] - sum[0] * sum[2]) / (len * sum[3] - Math.pow(sum[0], 2)); + var a = (sum[2] - b * sum[0]) / len; + var coeffA = round(Math.exp(a), options.precision); + var coeffB = round(b, options.precision); + + var predict = function predict(x) { + return [round(x, options.precision), round(round(coeffA * Math.pow(x, coeffB), options.precision), options.precision)]; + }; + + var points = data.map(function(point) { + return predict(point[0]); + }); + + return { + points: points, + predict: predict, + equation: [coeffA, coeffB], + string: 'y = ' + coeffA + 'x^' + coeffB, + r2: round(determinationCoefficient(data, points), options.precision) + }; + }, + polynomial: function polynomial(data, options) { + var lhs = []; + var rhs = []; + var a = 0; + var b = 0; + var len = data.length; + var k = options.order + 1; + + for (var i = 0; i < k; i++) { + for (var l = 0; l < len; l++) { + if (data[l][1] !== null) { + a += Math.pow(data[l][0], i) * data[l][1]; + } + } + + lhs.push(a); + a = 0; + + var c = []; + for (var j = 0; j < k; j++) { + for (var _l = 0; _l < len; _l++) { + if (data[_l][1] !== null) { + b += Math.pow(data[_l][0], i + j); + } + } + c.push(b); + b = 0; + } + rhs.push(c); + } + rhs.push(lhs); + + var coefficients = gaussianElimination(rhs, k).map(function(v) { + return round(v, options.precision); + }); + + var predict = function predict(x) { + return [round(x, options.precision), round(coefficients.reduce(function(sum, coeff, power) { + return sum + coeff * Math.pow(x, power); + }, 0), options.precision)]; + }; + + var points = data.map(function(point) { + return predict(point[0]); + }); + + var string = 'y = '; + for (var _i = coefficients.length - 1; _i >= 0; _i--) { + if (_i > 1) { + string += coefficients[_i] + 'x^' + _i + ' + '; + } else if (_i === 1) { + string += coefficients[_i] + 'x + '; + } else { + string += coefficients[_i]; + } + } + + return { + string: string, + points: points, + predict: predict, + equation: [].concat(_toConsumableArray(coefficients)).reverse(), + r2: round(determinationCoefficient(data, points), options.precision) + }; + } +}; + +export default methods diff --git a/save/contexts/cuttlefish/correlate.json b/save/contexts/cuttlefish/correlate.json index e68e608578f5ebce50c91d51870c5d48b172b4db..daeea9d192276a0f4824e830aeea89e9d7387623 100644 --- a/save/contexts/cuttlefish/correlate.json +++ b/save/contexts/cuttlefish/correlate.json @@ -89,10 +89,24 @@ { "inHunkIndex": "3", "inHunkInput": "0" + }, + { + "inHunkIndex": "5", + "inHunkInput": "0" } ] } ] + }, + { + "type": "adhoc/center", + "name": "adhoc/center_5", + "inputs": [ + { + "name": "image", + "type": "ImageData" + } + ] } ] } \ No newline at end of file