/
feretDiameters.js
101 lines (91 loc) · 3.04 KB
/
feretDiameters.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { rotate, difference, normalize } from '../../util/points';
import convexHullFunction from '../compute/monotoneChainConvexHull';
/**
* Computes the Feret diameters
* https://www.sympatec.com/en/particle-measurement/glossary/particle-shape/#
* http://portal.s2nano.org:8282/files/TEM_protocol_NANoREG.pdf
* @memberof Image
* @instance
* @param {object} [options]
* @param {Array<Array<number>>} [options.originalPoints]
* @return {object} Object with {min, max, minLine: {Array<Array<number>>}, maxLine: {Array<Array<number>>}}
*/
export default function feretDiameters(options = {}) {
const { originalPoints = convexHullFunction.call(this) } = options;
if (originalPoints.length === 0) {
return { min: 0, max: 0, minLine: [], maxLine: [], aspectRatio: 1 };
}
if (originalPoints.length === 1) {
return {
min: 1,
max: 1,
minLine: [originalPoints[0], originalPoints[0]],
maxLine: [originalPoints[0], originalPoints[0]],
aspectRatio: 1,
};
}
const temporaryPoints = new Array(originalPoints.length);
// CALCULATE MIN VALUE
let minWidth = +Infinity;
let minWidthAngle = 0;
let minLine = [];
for (let i = 0; i < originalPoints.length; i++) {
let angle = getAngle(
originalPoints[i],
originalPoints[(i + 1) % originalPoints.length],
);
// we rotate so that it is parallel to X axis
rotate(-angle, originalPoints, temporaryPoints);
let currentWidth = 0;
let currentMinLine = [];
for (let j = 0; j < originalPoints.length; j++) {
let absWidth = Math.abs(temporaryPoints[i][1] - temporaryPoints[j][1]);
if (absWidth > currentWidth) {
currentWidth = absWidth;
currentMinLine = [];
currentMinLine.push(
[temporaryPoints[j][0], temporaryPoints[i][1]],
[temporaryPoints[j][0], temporaryPoints[j][1]],
);
}
}
if (currentWidth < minWidth) {
minWidth = currentWidth;
minWidthAngle = angle;
minLine = currentMinLine;
}
}
rotate(minWidthAngle, minLine, minLine);
// CALCULATE MAX VALUE
let maxWidth = 0;
let maxLine = [];
let maxSquaredWidth = 0;
for (let i = 0; i < originalPoints.length - 1; i++) {
for (let j = i + 1; j < originalPoints.length; j++) {
let currentSquaredWidth =
(originalPoints[i][0] - originalPoints[j][0]) ** 2 +
(originalPoints[i][1] - originalPoints[j][1]) ** 2;
if (currentSquaredWidth > maxSquaredWidth) {
maxSquaredWidth = currentSquaredWidth;
maxWidth = Math.sqrt(currentSquaredWidth);
maxLine = [originalPoints[i], originalPoints[j]];
}
}
}
return {
min: minWidth,
minLine,
max: maxWidth,
maxLine,
aspectRatio: minWidth / maxWidth,
};
}
// the angle that allows to make the line going through p1 and p2 horizontal
// this is an optimized version because it assume one vector is horizontal
function getAngle(p1, p2) {
let diff = difference(p2, p1);
let vector = normalize(diff);
let angle = Math.acos(vector[0]);
if (vector[1] < 0) return -angle;
return angle;
}