Comparing version 2.0.0 to 2.0.1
@@ -12,3 +12,3 @@ const es = require('./estimators') | ||
// Defines the freedom of the transform to compute. | ||
// One of the following: | ||
// Must be one of the following: | ||
// ..'I', 'L', 'X', 'Y', 'T', 'S', 'R', 'TS', 'TR', 'SR', 'TSR' | ||
@@ -55,2 +55,6 @@ // domain | ||
// | ||
if (!params.estimator) { | ||
throw new Error('Unexpected estimator type: ' + params.estimator) | ||
} | ||
switch (params.estimator.toUpperCase()) { | ||
@@ -80,4 +84,4 @@ case 'I': | ||
default: | ||
throw new Error('Unknown estimator type: ' + params.type) | ||
throw new Error('Unexpected estimator type: ' + params.estimator) | ||
} | ||
} |
@@ -8,3 +8,6 @@ module.exports = function () { | ||
// | ||
// Why this trivial estimator exists? If the estimator type becomes a variable in your application | ||
// then it is convenient to be able to disable estimation by just switching the estimator type to I. | ||
// | ||
return { a: 1, b: 0, x: 0, y: 0 } | ||
} |
@@ -42,5 +42,6 @@ const TOLERANCE = require('../tolerance') | ||
const det = Math.sqrt(p * p + q * q) | ||
// Determinant. Always det >= 0 | ||
const det = p * p + q * q | ||
if (Math.abs(det) < TOLERANCE) { | ||
if (det < TOLERANCE) { | ||
// det === 0 | ||
@@ -56,4 +57,7 @@ // <=> q === 0 and p === 0. | ||
const ahat = p / det | ||
const bhat = q / det | ||
// Denominator. | ||
const den = Math.sqrt(det) | ||
const ahat = p / den | ||
const bhat = q / den | ||
const xhat = cx - cx * ahat + cy * bhat | ||
@@ -60,0 +64,0 @@ const yhat = cy - cx * bhat - cy * ahat |
@@ -20,2 +20,19 @@ const TOLERANCE = require('../tolerance') | ||
// If length is zero, no points provided, thus no estimation can be done. | ||
// We choose the identity transformation be the best quess. | ||
if (N === 0) { | ||
return { a: 1, b: 0, x: 0, y: 0 } | ||
} | ||
// If length is one, only single point pair was provided. | ||
// A translation is the optimal solution. | ||
// The general algorithm resolves single point pair to a translation, | ||
// but translation estimation is numerically much more stable and thus | ||
// worth a few lines of code. See PR#31. | ||
if (N === 1) { | ||
const dx = range[0].x - domain[0].x | ||
const dy = range[0].y - domain[0].y | ||
return { a: 1, b: 0, x: dx, y: dy } | ||
} | ||
let asum = 0 // sum | ||
@@ -46,12 +63,15 @@ let bsum = 0 | ||
// Denominator = determinant. | ||
const v = N * (ac + bd) - asum * csum - bsum * dsum | ||
const w = N * (ad - bc) - asum * dsum + bsum * csum | ||
const det = Math.sqrt(v * v + w * w) | ||
// Determinant. | ||
// For numerical stability near the zero determinant, note the order of the terms. | ||
// We want first a,c terms cancel each other and then b,d terms. | ||
// v = N * (ac + bd) - asum * csum - bsum * dsum | ||
// w = N * (ad - bc) - asum * dsum + bsum * csum | ||
// <=> | ||
// v = (N * ac - asum * csum) + (N * bd - bsum * dsum) | ||
// w = (N * ad - asum * dsum) + (bsum * csum - N * bc) | ||
const v = N * ac - asum * csum + (N * bd - bsum * dsum) | ||
const w = N * ad - asum * dsum + (bsum * csum - N * bc) | ||
const det = v * v + w * w | ||
if (det < TOLERANCE) { | ||
// N === 0 => det === 0 | ||
if (N === 0) { | ||
return { a: 1, b: 0, x: 0, y: 0 } | ||
} // else | ||
// det === 0 <=> undecidable | ||
@@ -68,5 +88,8 @@ // We guess the translation to the mean of the range to be the best guess. | ||
// Denominator is the square root of determinant. | ||
const den = Math.sqrt(det) | ||
// Estimators | ||
const ahat = v / det | ||
const bhat = w / det | ||
const ahat = v / den | ||
const bhat = w / den | ||
const xhat = (-asum * ahat + bsum * bhat + csum) / N | ||
@@ -73,0 +96,0 @@ const yhat = (-asum * bhat - bsum * ahat + dsum) / N |
@@ -20,2 +20,19 @@ const TOLERANCE = require('../tolerance') | ||
// If length is zero, no points provided, thus no estimation can be done. | ||
// We choose the identity transformation be the best quess. | ||
if (N === 0) { | ||
return { a: 1, b: 0, x: 0, y: 0 } | ||
} | ||
// If length is one, only single point pair was provided. | ||
// A translation is the optimal solution. | ||
// The general algorithm resolves single point pair to a translation, | ||
// but translation estimation is numerically much more stable and thus | ||
// worth a few lines of code. See PR#31. | ||
if (N === 1) { | ||
const dx = range[0].x - domain[0].x | ||
const dy = range[0].y - domain[0].y | ||
return { a: 1, b: 0, x: dx, y: dy } | ||
} | ||
let a1 = 0 | ||
@@ -50,5 +67,11 @@ let b1 = 0 | ||
const b12 = b1 * b1 | ||
const p = a2 + b2 | ||
const q = ac + bd | ||
const det = N2 * p - N * (a12 + b12) | ||
// For numerical stability near the zero determinant, reorder terms so | ||
// that we evaluate x and y specific terms separately. See PR#31. | ||
// p = a2 + b2 | ||
// q = ac + bd | ||
// det = N2 * p - N * (a12 + b12) | ||
// = N2 * (a2 + b2) - N * (a12 + b12) | ||
// = N2 * a2 + N2 * b2 - N * a12 - N * b12 | ||
// = N2 * a2 - N * a12 + N2 * b2 - N * b12 | ||
const det = N2 * a2 - N * a12 + N2 * b2 - N * b12 | ||
@@ -74,2 +97,6 @@ if (Math.abs(det) < TOLERANCE) { | ||
// Shorthand | ||
const p = a2 + b2 | ||
const q = ac + bd | ||
// Estimators | ||
@@ -76,0 +103,0 @@ const ahat = (N2 * q - N * (a1 * c1 + b1 * d1)) / det |
@@ -22,4 +22,4 @@ const TOLERANCE = require('../tolerance') | ||
// If length is zero, no estimation can be done. We choose the indentity | ||
// transformation be the best quess. | ||
// If length is zero, no points provided, thus no estimation can be done. | ||
// We choose the identity transformation be the best quess. | ||
if (N === 0) { | ||
@@ -29,2 +29,13 @@ return { a: 1, b: 0, x: 0, y: 0 } | ||
// If length is one, only single point pair was provided. | ||
// A translation is the optimal solution. | ||
// The general algorithm resolves single point pair to a translation, | ||
// but translation estimation is numerically much more stable and thus | ||
// worth a few lines of code. See PR#31. | ||
if (N === 1) { | ||
const dx = range[0].x - domain[0].x | ||
const dy = range[0].y - domain[0].y | ||
return { a: 1, b: 0, x: dx, y: dy } | ||
} | ||
let a1 = 0 | ||
@@ -62,3 +73,8 @@ let b1 = 0 | ||
// In other words, iff all the domain points are the same or there is only one domain point. | ||
const det = N * a2 + N * b2 - a1 * a1 - b1 * b1 | ||
// | ||
// Note the term order: first we want to evaluate x-specific terms and then y-specific terms. | ||
// The below term order brings numerical stability to near-zero cases. See PR#31. | ||
// We do not need to use parenthesis because the addition operator '+' in JavaScript is left-associative, | ||
// and thus for example the expression a+b+c+d is evalued like (((a + b) + c) + d). | ||
const det = N * a2 - a1 * a1 + N * b2 - b1 * b1 | ||
@@ -65,0 +81,0 @@ if (Math.abs(det) < TOLERANCE) { |
@@ -13,6 +13,3 @@ module.exports = (x, y) => { | ||
// | ||
return { | ||
x: x, | ||
y: y | ||
} | ||
return { x, y } | ||
} |
@@ -21,8 +21,3 @@ module.exports = function (a, b, x, y) { | ||
// | ||
return { | ||
a: a, | ||
b: b, | ||
x: x, | ||
y: y | ||
} | ||
return { a, b, x, y } | ||
} |
@@ -1,2 +0,2 @@ | ||
// generated by genversion | ||
module.exports = '2.0.0' | ||
// Generated by genversion. | ||
module.exports = '2.0.1' |
{ | ||
"name": "nudged", | ||
"version": "2.0.0", | ||
"version": "2.0.1", | ||
"description": "Affine transformation estimator e.g. for multi-touch gestures and calibration", | ||
@@ -12,2 +12,3 @@ "keywords": [ | ||
"pinch", | ||
"math", | ||
"affine", | ||
@@ -19,3 +20,6 @@ "multitouch", | ||
"similarity", | ||
"calibration" | ||
"calibration", | ||
"snapping", | ||
"match", | ||
"layout" | ||
], | ||
@@ -35,10 +39,10 @@ "homepage": "https://github.com/axelpale/nudged", | ||
"async": "^3.2.0", | ||
"browserify": "^17.0.0", | ||
"browserify": "^17.0.1", | ||
"component-emitter": "^1.2.0", | ||
"genversion": "^3.0.0", | ||
"genversion": "^3.2.0", | ||
"hammerjs": "^2.0.4", | ||
"loadimages": "^1.0.0", | ||
"lodash": "^4.17.21", | ||
"standard": "^16.0.3", | ||
"tap-spec": "^5.0.0", | ||
"standard": "^17.1.2", | ||
"tap-arc": "^1.3.2", | ||
"tape": "^5.2.2" | ||
@@ -50,3 +54,4 @@ }, | ||
"test": "npm run test:lint && npm run test:unit", | ||
"test:unit": "node test/index.test.js | tap-spec", | ||
"test:unit": "node test/index.test.js", | ||
"test:pretty": "node test/index.test.js | tap-arc", | ||
"test:lint": "standard index.js 'lib/**/*.js' 'test/**/*.js'", | ||
@@ -53,0 +58,0 @@ "test:lint:fix": "standard --fix index.js 'lib/**/*.js' 'test/**/*.js'", |
# nudged | ||
[![NPM Version](https://img.shields.io/npm/v/nudged.svg)](https://www.npmjs.com/package/nudged) | ||
[![Build Status](https://img.shields.io/travis/com/axelpale/nudged)](https://travis-ci.com/github/axelpale/nudged) | ||
![Dependency status](https://img.shields.io/badge/dependencies-none-lightgrey) | ||
[![License](https://img.shields.io/npm/l/nudged)](#license) | ||
[![GitHub Actions workflow status](https://img.shields.io/github/actions/workflow/status/axelpale/nudged/nudged-ci.yml)](https://github.com/axelpale/nudged/actions/workflows/nudged-ci.yml) | ||
![Nudged logo](doc/nudged-logo-2021-512.png) | ||
*Nudged* is **a JavaScript module** to efficiently estimate translation, scale, and/or rotation between two sets of 2D points. It has already been applied to **user interfaces, multi-touch recognition, geography, and eye tracker calibration**. | ||
*Nudged* is **a JavaScript module** to efficiently estimate translation, scale, and rotation between two sets of 2D points. It enables you to **capture transformations** that you can use for **motion dynamics, calibration, geometry snapping, and mapping between coordinate spaces**. It has already been applied to **user interface geometry [[1]](https://github.com/taataa/tapspace), multi-touch recognition [[1]](https://github.com/taataa/tapspace), and eye tracker calibration [[2]](https://github.com/infant-cognition-tampere/gazeanalysislib)**. | ||
@@ -21,2 +23,3 @@ ### Table of contents | ||
- [Licence](#licence) | ||
- [See also](#see-also) | ||
@@ -28,3 +31,3 @@ | ||
Install the functional nudged 2.0.0-beta: | ||
Install the functional nudged 2: | ||
@@ -42,3 +45,3 @@ $ npm install nudged | ||
In general, you can apply Nudged in any situation where you want to capture a 2D transformation based on a movement of any number of control points. See the image below for the available transformations Nudged can estimate. | ||
In general, you can apply Nudged in any situation where you want to capture a 2D transformation based on a movement of any number of control points. You have a set of points, and they move from some source coordinates to some target coordinates. You want to capture the movement pattern between the source and the target as a 2D transformation. You may want to capture the pattern in order to apply it to something else, such as a photo or some other object. See the image below for the available transformations Nudged can estimate, illustrated with two control points and a photo. | ||
@@ -269,6 +272,8 @@ <img src="doc/transformation-types.jpg" alt="Types of transformation estimators"/><br> | ||
Nudged source code is located at [GitHub](https://github.com/axelpale/nudged). | ||
Guidelines: | ||
- ES6 | ||
- [Standard style](https://standardjs.com/) | ||
- Use [ECMAScript 2015](https://en.wikipedia.org/wiki/ECMAScript) syntax with [CommonJS](https://en.wikipedia.org/wiki/CommonJS) module format. | ||
- Follow [Standard](https://standardjs.com/) style: | ||
- 2 space indent | ||
@@ -279,7 +284,7 @@ - max 80 chars per line | ||
- namespaces and functions instead of classes and methods | ||
- immutable and stateless data handling; no in-place manipulation. | ||
- immutable and stateless data handling; no in-place manipulation of arguments. | ||
- Minimal run-time type checking | ||
- Nudged is designed to be a low-level module with high performance. | ||
- Instead of run-time checks, the geometries provide a dedicated .validate function. | ||
- Write rich comments that answer the question why. | ||
- Write rich comments that answer the question "why". | ||
@@ -324,3 +329,4 @@ Run lint & unit tests: | ||
- Tanja for math photos. | ||
- Vilkku, Xiao, and Krista for finger photos. | ||
- Vilkku, Xiao, and Krista for illustrative finger photos. | ||
- All who have contributed to the codebase and issue resolution over the years. | ||
@@ -330,8 +336,14 @@ | ||
[Semantic Versioning 2.0.0](http://semver.org/) | ||
The versioning convention of the package follows [Semantic Versioning 2.0.0](http://semver.org/) and [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/). | ||
## Licence | ||
[MIT Licence](LICENSE) | ||
The nudged source code is open source and free to use. It is released under a [MIT licence](LICENSE). | ||
## See also | ||
- [Affineplane](https://axelpale.github.io/affineplane/) geometry library is directly compatible with Nudged object interfaces. It provides further tools to transform geometry and manipulate transformations in 2D and 3D. | ||
- [Tapspace.js](https://github.com/taataa/tapspace) is a toolkit for zoomable user interfaces. Tapspace.js heavily depends on the nudged algorithm in multi-touch recognition, web content layout, and tensor geometry. | ||
- [Apollonius](https://axelpale.github.io/apollonius/) is another math-heavy geometry package from the author of Nudged. Apollonius considers finding a circle that is simultaneously tangent to three other circles. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
77915
1811
343
0