image-js
Version:
Image processing and manipulation in JavaScript
181 lines • 6.36 kB
JavaScript
import { toDegrees } from '../utils/geometry/angles.js';
import { rotate } from '../utils/geometry/points.js';
import { getAngle } from './utils/getAngle.js';
/**
* Computes the Feret diameters.
* @see {@link https://www.sympatec.com/en/particle-measurement/glossary/particle-shape/#}
* @see {@link http://portal.s2nano.org:8282/files/TEM_protocol_NANoREG.pdf}
* @param mask - The mask of the ROI.
* @returns The Feret diameters.
*/
export function getFeret(mask) {
const hull = mask.getConvexHull();
const hullPoints = hull.points;
if (hull.surface === 0) {
return {
minDiameter: {
length: 0,
points: [
{ column: 0, row: 0 },
{ column: 0, row: 0 },
],
angle: 0,
calliperLines: [
[
{ column: 0, row: 0 },
{ column: 0, row: 0 },
],
[
{ column: 0, row: 0 },
{ column: 0, row: 0 },
],
],
},
maxDiameter: {
length: 0,
points: [
{ column: 0, row: 0 },
{ column: 0, row: 0 },
],
angle: 0,
calliperLines: [
[
{ column: 0, row: 0 },
{ column: 0, row: 0 },
],
[
{ column: 0, row: 0 },
{ column: 0, row: 0 },
],
],
},
aspectRatio: 1,
};
}
// Compute minimum diameter
let minWidth = Number.POSITIVE_INFINITY;
let minWidthAngle = 0;
let minLinePoints = [];
let minLines;
for (let i = 0; i < hullPoints.length; i++) {
const angle = getAngle(hullPoints[i], hullPoints[(i + 1) % hullPoints.length]);
// We rotate so that it is parallel to X axis.
const rotatedPoints = rotate(-angle, hullPoints);
let currentWidth = 0;
let currentMinLinePoints = [];
for (let j = 0; j < hullPoints.length; j++) {
const absWidth = Math.abs(rotatedPoints[i].row - rotatedPoints[j].row);
if (absWidth > currentWidth) {
currentWidth = absWidth;
currentMinLinePoints = [rotatedPoints[i], rotatedPoints[j]];
}
}
if (currentWidth < minWidth) {
minWidth = currentWidth;
minWidthAngle = angle;
minLinePoints = currentMinLinePoints;
const { minIndex: currentMin, maxIndex: currentMax } = findPointIndexesOfExtremeColumns(rotatedPoints);
minLines = getMinLines(minWidthAngle, currentMin, currentMax, rotatedPoints, minLinePoints);
}
}
const minDiameter = {
points: rotate(minWidthAngle, minLinePoints),
length: minWidth,
angle: toDegrees(minWidthAngle),
calliperLines: minLines,
};
// Compute maximum diameter
let maxLinePoints = [];
let maxSquaredWidth = 0;
let maxLineIndex = [];
for (let i = 0; i < hullPoints.length - 1; i++) {
for (let j = i + 1; j < hullPoints.length; j++) {
const currentSquaredWidth = (hullPoints[i].column - hullPoints[j].column) ** 2 +
(hullPoints[i].row - hullPoints[j].row) ** 2;
if (currentSquaredWidth > maxSquaredWidth) {
maxSquaredWidth = currentSquaredWidth;
maxLinePoints = [hullPoints[i], hullPoints[j]];
maxLineIndex = [i, j];
}
}
}
const maxAngle = getAngle(maxLinePoints[0], maxLinePoints[1]);
const rotatedMaxPoints = rotate(-maxAngle, hullPoints);
const { minIndex: currentMin, maxIndex: currentMax } = findPointsIndexesOfExtremeRows(rotatedMaxPoints);
const maxLines = getMaxLines(maxAngle, currentMin, currentMax, rotatedMaxPoints, maxLineIndex);
const maxDiameter = {
length: Math.sqrt(maxSquaredWidth),
angle: toDegrees(getAngle(maxLinePoints[0], maxLinePoints[1])),
points: maxLinePoints,
calliperLines: maxLines,
};
return {
minDiameter,
maxDiameter,
aspectRatio: minDiameter.length / maxDiameter.length,
};
}
function findPointIndexesOfExtremeColumns(points) {
let maxIndex = 0;
let minIndex = 0;
for (let i = 0; i < points.length; i++) {
if (points[i].column > points[maxIndex].column) {
maxIndex = i;
}
if (points[i].column < points[minIndex].column) {
minIndex = i;
}
}
return { minIndex, maxIndex };
}
function findPointsIndexesOfExtremeRows(points) {
let maxIndex = 0;
let minIndex = 0;
for (let i = 0; i < points.length; i++) {
if (points[i].row > points[maxIndex].row) {
maxIndex = i;
}
if (points[i].row < points[minIndex].row) {
minIndex = i;
}
}
return { minIndex, maxIndex };
}
function getMinLines(angle, min, max, rotatedPoints, feretPoints) {
const minLine1 = [
{ column: rotatedPoints[min].column, row: feretPoints[0].row },
{
column: rotatedPoints[max].column,
row: feretPoints[0].row,
},
];
const minLine2 = [
{
column: rotatedPoints[min].column,
row: feretPoints[1].row,
},
{
column: rotatedPoints[max].column,
row: feretPoints[1].row,
},
];
return [rotate(angle, minLine1), rotate(angle, minLine2)];
}
function getMaxLines(angle, min, max, rotatedPoints, index) {
const maxLine1 = [
{ column: rotatedPoints[index[0]].column, row: rotatedPoints[min].row },
{
column: rotatedPoints[index[0]].column,
row: rotatedPoints[max].row,
},
];
const maxLine2 = [
{ column: rotatedPoints[index[1]].column, row: rotatedPoints[min].row },
{
column: rotatedPoints[index[1]].column,
row: rotatedPoints[max].row,
},
];
return [rotate(angle, maxLine1), rotate(angle, maxLine2)];
}
//# sourceMappingURL=getFeret.js.map