image-js 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/image-js.esm.js +7150 -6268
- package/dist/image-js.esm.js.map +1 -1
- package/dist/image-js.esm.min.js +2 -2
- package/dist/image-js.esm.min.js.map +1 -1
- package/dist/image-js.umd.js +8349 -7464
- package/dist/image-js.umd.js.map +1 -1
- package/dist/image-js.umd.min.js +2 -2
- package/dist/image-js.umd.min.js.map +1 -1
- package/dist-types/image-js.d.ts +42 -7
- package/lib/Stack.d.ts +1 -2
- package/lib/Stack.d.ts.map +1 -1
- package/lib/Stack.js.map +1 -1
- package/lib/align/affineTransfrom/getAffineTransform.d.ts +1 -2
- package/lib/align/affineTransfrom/getAffineTransform.d.ts.map +1 -1
- package/lib/align/affineTransfrom/getAffineTransform.js +4 -1
- package/lib/align/affineTransfrom/getAffineTransform.js.map +1 -1
- package/lib/featureMatching/keypoints/getEigenvaluesForScore.d.ts +2 -2
- package/lib/featureMatching/keypoints/getEigenvaluesForScore.d.ts.map +1 -1
- package/lib/featureMatching/keypoints/getEigenvaluesForScore.js +27 -22
- package/lib/featureMatching/keypoints/getEigenvaluesForScore.js.map +1 -1
- package/lib/featureMatching/keypoints/getFastKeypoints.d.ts +4 -0
- package/lib/featureMatching/keypoints/getFastKeypoints.d.ts.map +1 -1
- package/lib/featureMatching/keypoints/getFastKeypoints.js +20 -5
- package/lib/featureMatching/keypoints/getFastKeypoints.js.map +1 -1
- package/lib/featureMatching/keypoints/getHarrisScore.js +1 -1
- package/lib/featureMatching/keypoints/getHarrisScore.js.map +1 -1
- package/lib/featureMatching/keypoints/getShiTomasiScore.d.ts +1 -2
- package/lib/featureMatching/keypoints/getShiTomasiScore.d.ts.map +1 -1
- package/lib/featureMatching/keypoints/getShiTomasiScore.js +1 -1
- package/lib/featureMatching/keypoints/getShiTomasiScore.js.map +1 -1
- package/lib/featureMatching/visualize/drawKeypoints.d.ts +2 -1
- package/lib/featureMatching/visualize/drawKeypoints.d.ts.map +1 -1
- package/lib/featureMatching/visualize/drawKeypoints.js +13 -0
- package/lib/featureMatching/visualize/drawKeypoints.js.map +1 -1
- package/lib/load/decodeBmp.d.ts.map +1 -1
- package/lib/load/decodeBmp.js +17 -0
- package/lib/load/decodeBmp.js.map +1 -1
- package/lib/maskAnalysis/getContourMoore.d.ts.map +1 -1
- package/lib/maskAnalysis/getContourMoore.js +6 -4
- package/lib/maskAnalysis/getContourMoore.js.map +1 -1
- package/lib/maskAnalysis/getConvexityDefects.d.ts +17 -0
- package/lib/maskAnalysis/getConvexityDefects.d.ts.map +1 -0
- package/lib/maskAnalysis/getConvexityDefects.js +75 -0
- package/lib/maskAnalysis/getConvexityDefects.js.map +1 -0
- package/lib/maskAnalysis/getExternalContour.d.ts +1 -1
- package/lib/maskAnalysis/getExternalContour.d.ts.map +1 -1
- package/lib/maskAnalysis/index.d.ts +1 -0
- package/lib/maskAnalysis/index.d.ts.map +1 -1
- package/lib/maskAnalysis/index.js +1 -0
- package/lib/maskAnalysis/index.js.map +1 -1
- package/lib/roi/getExternalContour.d.ts +1 -1
- package/lib/roi/getExternalContour.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/Stack.ts +1 -3
- package/src/align/affineTransfrom/getAffineTransform.ts +5 -3
- package/src/featureMatching/keypoints/getEigenvaluesForScore.ts +29 -27
- package/src/featureMatching/keypoints/getFastKeypoints.ts +23 -5
- package/src/featureMatching/keypoints/getHarrisScore.ts +1 -1
- package/src/featureMatching/keypoints/getShiTomasiScore.ts +2 -3
- package/src/featureMatching/visualize/drawKeypoints.ts +20 -3
- package/src/load/decodeBmp.ts +22 -0
- package/src/maskAnalysis/getContourMoore.ts +6 -6
- package/src/maskAnalysis/getConvexityDefects.ts +105 -0
- package/src/maskAnalysis/index.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getContourMoore.js","sourceRoot":"","sources":["../../src/maskAnalysis/getContourMoore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"getContourMoore.js","sourceRoot":"","sources":["../../src/maskAnalysis/getContourMoore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Point } from '../index_full.ts';
|
|
2
|
+
export interface ConvexityDefectsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Depth threshold to remove smaller convexity defaults.
|
|
5
|
+
* @default 0
|
|
6
|
+
*/
|
|
7
|
+
depthThreshold?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Find border points that are farthest from its convex hull lines. Inspired by openCV's convexityDefects.
|
|
11
|
+
* @param borderPoints - Mask's border points.
|
|
12
|
+
* @param hullPoints - Mask's convex hull points.
|
|
13
|
+
* @param options - Convexity defects options.
|
|
14
|
+
* @returns Array of convexity defects.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getConvexityDefects(borderPoints: Point[], hullPoints: Point[], options?: ConvexityDefectsOptions): Point[];
|
|
17
|
+
//# sourceMappingURL=getConvexityDefects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getConvexityDefects.d.ts","sourceRoot":"","sources":["../../src/maskAnalysis/getConvexityDefects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,KAAK,EAAE,EACrB,UAAU,EAAE,KAAK,EAAE,EACnB,OAAO,GAAE,uBAA4B,GACpC,KAAK,EAAE,CAsET"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find border points that are farthest from its convex hull lines. Inspired by openCV's convexityDefects.
|
|
3
|
+
* @param borderPoints - Mask's border points.
|
|
4
|
+
* @param hullPoints - Mask's convex hull points.
|
|
5
|
+
* @param options - Convexity defects options.
|
|
6
|
+
* @returns Array of convexity defects.
|
|
7
|
+
*/
|
|
8
|
+
export function getConvexityDefects(borderPoints, hullPoints, options = {}) {
|
|
9
|
+
const { depthThreshold = 0 } = options;
|
|
10
|
+
const defects = [];
|
|
11
|
+
if (hullPoints.length === 0) {
|
|
12
|
+
throw new RangeError('No hull points were defined for convexity defects detection.');
|
|
13
|
+
}
|
|
14
|
+
if (borderPoints.length === 0) {
|
|
15
|
+
throw new RangeError('No border points were defined for convexity defects detection.');
|
|
16
|
+
}
|
|
17
|
+
const lastHullPoint = hullPoints.at(-1);
|
|
18
|
+
let currBorderIndex = borderPoints.findIndex((bp) => limitReached(bp, lastHullPoint));
|
|
19
|
+
if (currBorderIndex === -1) {
|
|
20
|
+
throw new RangeError('Could not find a border point matching the convex hull endpoint.');
|
|
21
|
+
}
|
|
22
|
+
for (let i = hullPoints.length - 1; i >= 0; i--) {
|
|
23
|
+
const currHullPoint = hullPoints.at(i);
|
|
24
|
+
const nextHullPoint = hullPoints.at(i - 1);
|
|
25
|
+
if (!currHullPoint || !nextHullPoint)
|
|
26
|
+
continue;
|
|
27
|
+
const v0x = nextHullPoint.column - currHullPoint.column;
|
|
28
|
+
const v0y = nextHullPoint.row - currHullPoint.row;
|
|
29
|
+
const edgeLenSq = v0x * v0x + v0y * v0y;
|
|
30
|
+
let maxDepth = 0;
|
|
31
|
+
let maxDepthIndex = -1;
|
|
32
|
+
const segmentStart = currBorderIndex;
|
|
33
|
+
let reached = false;
|
|
34
|
+
for (let j = 0; j < borderPoints.length; j++) {
|
|
35
|
+
const checkIndex = (segmentStart + j) % borderPoints.length;
|
|
36
|
+
const bp = borderPoints[checkIndex];
|
|
37
|
+
if (limitReached(bp, nextHullPoint)) {
|
|
38
|
+
currBorderIndex = (checkIndex + 1) % borderPoints.length;
|
|
39
|
+
reached = true;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
const v1x = bp.column - currHullPoint.column;
|
|
43
|
+
const v1y = bp.row - currHullPoint.row;
|
|
44
|
+
const t = Math.max(0, Math.min(1, (v1x * v0x + v1y * v0y) / edgeLenSq));
|
|
45
|
+
const closestX = currHullPoint.column + t * v0x;
|
|
46
|
+
const closestY = currHullPoint.row + t * v0y;
|
|
47
|
+
const depth = Math.hypot(bp.column - closestX, bp.row - closestY);
|
|
48
|
+
if (depth > maxDepth) {
|
|
49
|
+
maxDepth = depth;
|
|
50
|
+
maxDepthIndex = checkIndex;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!reached) {
|
|
54
|
+
throw new RangeError('Could not reach the next hull point while scanning border points; hull and border may be inconsistent.');
|
|
55
|
+
}
|
|
56
|
+
if (maxDepth > depthThreshold) {
|
|
57
|
+
const defectPoint = borderPoints[maxDepthIndex];
|
|
58
|
+
if (defectPoint)
|
|
59
|
+
defects.push(defectPoint);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return defects;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Checks if borderPoint reached convex hull's point.
|
|
66
|
+
* @param borderPoint - Border point.
|
|
67
|
+
* @param hullPoint - Endpoint of convex hull segment.
|
|
68
|
+
* @returns whether endpoint is reached.
|
|
69
|
+
*/
|
|
70
|
+
function limitReached(borderPoint, hullPoint) {
|
|
71
|
+
const tolerance = 1;
|
|
72
|
+
return (Math.abs(borderPoint.column - hullPoint.column) <= tolerance &&
|
|
73
|
+
Math.abs(borderPoint.row - hullPoint.row) <= tolerance);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=getConvexityDefects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getConvexityDefects.js","sourceRoot":"","sources":["../../src/maskAnalysis/getConvexityDefects.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,5 +7,5 @@ export type GetExternalContourOptions = Omit<GetBorderPointsOptions, 'innerBorer
|
|
|
7
7
|
* @param options - GetExternalContourOptions.
|
|
8
8
|
* @returns Array of contour points.
|
|
9
9
|
*/
|
|
10
|
-
export declare function getExternalContour(mask: Mask, options?: GetExternalContourOptions): import("../
|
|
10
|
+
export declare function getExternalContour(mask: Mask, options?: GetExternalContourOptions): import("../index.ts").Point[];
|
|
11
11
|
//# sourceMappingURL=getExternalContour.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getExternalContour.d.ts","sourceRoot":"","sources":["../../src/maskAnalysis/getExternalContour.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,sBAAsB,EACtB,aAAa,CACd,CAAC;AACF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,yBAA8B,
|
|
1
|
+
{"version":3,"file":"getExternalContour.d.ts","sourceRoot":"","sources":["../../src/maskAnalysis/getExternalContour.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,sBAAsB,EACtB,aAAa,CACd,CAAC;AACF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,yBAA8B,iCAQxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/maskAnalysis/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/maskAnalysis/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/maskAnalysis/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/maskAnalysis/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC"}
|
|
@@ -6,5 +6,5 @@ import type { Roi } from './Roi.ts';
|
|
|
6
6
|
* @param options - GetExternalContourOptions.
|
|
7
7
|
* @returns Array of contour points.
|
|
8
8
|
*/
|
|
9
|
-
export declare function getExternalContour(roi: Roi, options?: GetExternalContourOptions): import("../
|
|
9
|
+
export declare function getExternalContour(roi: Roi, options?: GetExternalContourOptions): import("../index.ts").Point[];
|
|
10
10
|
//# sourceMappingURL=getExternalContour.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getExternalContour.d.ts","sourceRoot":"","sources":["../../src/roi/getExternalContour.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AAEvF,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,yBAAyB,
|
|
1
|
+
{"version":3,"file":"getExternalContour.d.ts","sourceRoot":"","sources":["../../src/roi/getExternalContour.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AAEvF,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,yBAAyB,iCAIpC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "image-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Image processing and manipulation in JavaScript",
|
|
6
6
|
"author": "Michaël Zasso",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"rimraf": "^6.1.2",
|
|
109
109
|
"tailwindcss": "^4.1.18",
|
|
110
110
|
"typescript": "~5.9.3",
|
|
111
|
-
"vite": "^
|
|
111
|
+
"vite": "^8.1.0",
|
|
112
112
|
"vitest": "^4.0.16"
|
|
113
113
|
},
|
|
114
114
|
"repository": {
|
package/src/Stack.ts
CHANGED
|
@@ -60,9 +60,8 @@ export interface GetAffineTransformOptions {
|
|
|
60
60
|
debug?: boolean;
|
|
61
61
|
/**
|
|
62
62
|
* Path of the debug image.
|
|
63
|
-
* @default `${import.meta.dirname}/montage.png`
|
|
64
63
|
*/
|
|
65
|
-
debugImagePath?: string;
|
|
64
|
+
debugImagePath?: string | URL;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
export interface AffineTransform {
|
|
@@ -131,7 +130,7 @@ export function getAffineTransform(
|
|
|
131
130
|
destinationOrigin = { column: 0, row: 0 },
|
|
132
131
|
maxRansacNbIterations,
|
|
133
132
|
debug = false,
|
|
134
|
-
debugImagePath
|
|
133
|
+
debugImagePath,
|
|
135
134
|
} = options;
|
|
136
135
|
|
|
137
136
|
if (source.colorModel !== ImageColorModel.GREY) {
|
|
@@ -242,6 +241,9 @@ export function getAffineTransform(
|
|
|
242
241
|
|
|
243
242
|
// create debug image
|
|
244
243
|
if (debug) {
|
|
244
|
+
if (!debugImagePath) {
|
|
245
|
+
throw new RangeError('Debug image file path is not specified.');
|
|
246
|
+
}
|
|
245
247
|
const montage = new Montage(source, destination, {
|
|
246
248
|
disposition: MontageDisposition.VERTICAL,
|
|
247
249
|
});
|
|
@@ -1,51 +1,53 @@
|
|
|
1
|
-
import { EigenvalueDecomposition, Matrix
|
|
1
|
+
import { EigenvalueDecomposition, Matrix } from 'ml-matrix';
|
|
2
2
|
|
|
3
3
|
import type { Image } from '../../Image.ts';
|
|
4
|
+
import { rawDirectConvolution } from '../../filters/convolution.ts';
|
|
4
5
|
import type { Point } from '../../index_full.ts';
|
|
5
6
|
import { SOBEL_X, SOBEL_Y } from '../../utils/constants/kernels.js';
|
|
6
|
-
|
|
7
7
|
/**
|
|
8
8
|
* A function that calculates eigenvalues to calculate feature score for Harris and Shi-Tomasi algorithms.
|
|
9
9
|
* @param image - Image take data from.
|
|
10
10
|
* @param origin - Center of the window, where the corner should be.
|
|
11
|
-
* @param
|
|
11
|
+
* @param cropSize - Size of the window, where data should be scanned.
|
|
12
12
|
* @returns Array of two eigenvalues.
|
|
13
13
|
*/
|
|
14
14
|
export function getEigenvaluesForScore(
|
|
15
15
|
image: Image,
|
|
16
16
|
origin: Point,
|
|
17
|
-
|
|
17
|
+
cropSize = 5,
|
|
18
18
|
) {
|
|
19
|
-
if (!(
|
|
19
|
+
if (!(cropSize % 2)) {
|
|
20
20
|
throw new TypeError('windowSize must be an odd integer');
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
const kernelRadius = (SOBEL_X.length - 1) / 2;
|
|
23
|
+
const windowRadius = (cropSize - 1) / 2;
|
|
24
|
+
const padded = cropSize + 2 * kernelRadius;
|
|
23
25
|
const cropOrigin = {
|
|
24
|
-
row: origin.row -
|
|
25
|
-
column: origin.column -
|
|
26
|
+
row: origin.row - windowRadius - kernelRadius,
|
|
27
|
+
column: origin.column - windowRadius - kernelRadius,
|
|
26
28
|
};
|
|
27
29
|
const window = image.crop({
|
|
28
30
|
origin: cropOrigin,
|
|
29
|
-
width:
|
|
30
|
-
height:
|
|
31
|
-
});
|
|
32
|
-
const xDerivative = window.gradientFilter({ kernelX: SOBEL_X });
|
|
33
|
-
const yDerivative = window.gradientFilter({ kernelY: SOBEL_Y });
|
|
34
|
-
|
|
35
|
-
const xMatrix = new WrapperMatrix1D(xDerivative.getRawImage().data, {
|
|
36
|
-
rows: xDerivative.height,
|
|
31
|
+
width: padded,
|
|
32
|
+
height: padded,
|
|
37
33
|
});
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
const xDerivative = rawDirectConvolution(window, SOBEL_X);
|
|
35
|
+
const yDerivative = rawDirectConvolution(window, SOBEL_Y);
|
|
36
|
+
|
|
37
|
+
let xxSum = 0;
|
|
38
|
+
let xySum = 0;
|
|
39
|
+
let yySum = 0;
|
|
40
|
+
|
|
41
|
+
for (let i = kernelRadius; i < window.height - kernelRadius; i++) {
|
|
42
|
+
for (let j = kernelRadius; j < window.width - kernelRadius; j++) {
|
|
43
|
+
const idx = i * window.width + j;
|
|
44
|
+
const gx = xDerivative[idx];
|
|
45
|
+
const gy = yDerivative[idx];
|
|
46
|
+
xxSum += gx * gx;
|
|
47
|
+
xySum += gx * gy;
|
|
48
|
+
yySum += gy * gy;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
49
51
|
|
|
50
52
|
const structureTensor = new Matrix([
|
|
51
53
|
[xxSum, xySum],
|
|
@@ -41,6 +41,10 @@ export interface GetFastKeypointsOptions extends IsFastKeypointOptions {
|
|
|
41
41
|
* @default `'FAST'`
|
|
42
42
|
*/
|
|
43
43
|
scoreAlgorithm?: 'HARRIS' | 'FAST' | 'TOMASI';
|
|
44
|
+
/**
|
|
45
|
+
* Value between 0 and 1 that signifies a percentage from maximum score found in the image. Points can be filtered based on this relative maximum score.
|
|
46
|
+
*/
|
|
47
|
+
qualityThreshold?: number;
|
|
44
48
|
/**
|
|
45
49
|
* Options for the Harris score computation.
|
|
46
50
|
*/
|
|
@@ -72,7 +76,7 @@ export function getFastKeypoints(
|
|
|
72
76
|
options: GetFastKeypointsOptions = {},
|
|
73
77
|
): FastKeypoint[] {
|
|
74
78
|
const { fastRadius = 3, scoreAlgorithm = 'FAST', scoreOptions } = options;
|
|
75
|
-
|
|
79
|
+
const windowSize = scoreOptions?.windowSize ?? 7;
|
|
76
80
|
const circlePoints = getCirclePoints(fastRadius);
|
|
77
81
|
const compassPoints = getCompassPoints(fastRadius);
|
|
78
82
|
|
|
@@ -81,6 +85,7 @@ export function getFastKeypoints(
|
|
|
81
85
|
nbContiguousPixels = (3 / 4) * circlePoints.length,
|
|
82
86
|
threshold = 20,
|
|
83
87
|
nonMaxSuppression = true,
|
|
88
|
+
qualityThreshold = 0,
|
|
84
89
|
} = options;
|
|
85
90
|
|
|
86
91
|
checkProcessable(image, {
|
|
@@ -108,13 +113,14 @@ export function getFastKeypoints(
|
|
|
108
113
|
.with('TOMASI', () => tomasiScore)
|
|
109
114
|
.exhaustive();
|
|
110
115
|
|
|
116
|
+
const padding = scoreAlgorithm === 'FAST' ? 0 : (windowSize + 1) / 2;
|
|
111
117
|
const allKeypoints: FastKeypoint[] = [];
|
|
112
118
|
|
|
113
119
|
const scoreArray = new Float64Array(image.size).fill(
|
|
114
120
|
Number.NEGATIVE_INFINITY,
|
|
115
121
|
);
|
|
116
|
-
for (let row =
|
|
117
|
-
for (let column =
|
|
122
|
+
for (let row = padding; row < image.height - padding; row++) {
|
|
123
|
+
for (let column = padding; column < image.width - padding; column++) {
|
|
118
124
|
const corner = { row, column };
|
|
119
125
|
if (
|
|
120
126
|
isFastKeypoint(corner, image, circlePoints, compassPoints, {
|
|
@@ -157,8 +163,20 @@ export function getFastKeypoints(
|
|
|
157
163
|
}
|
|
158
164
|
}
|
|
159
165
|
}
|
|
166
|
+
let maxScore = -Infinity;
|
|
167
|
+
for (const k of keypoints) {
|
|
168
|
+
if (k.score > maxScore) maxScore = k.score;
|
|
169
|
+
}
|
|
170
|
+
const filtered: FastKeypoint[] = [];
|
|
171
|
+
// threshold by default negative infinity
|
|
172
|
+
const filterThreshold = qualityThreshold * maxScore;
|
|
160
173
|
|
|
161
|
-
|
|
174
|
+
for (const k of keypoints) {
|
|
175
|
+
if (k.score >= filterThreshold) {
|
|
176
|
+
k.score /= maxScore;
|
|
177
|
+
filtered.push(k);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
162
180
|
|
|
163
|
-
return
|
|
181
|
+
return filtered.toSorted((a, b) => b.score - a.score).slice(0, maxNbFeatures);
|
|
164
182
|
}
|
|
@@ -24,7 +24,7 @@ export function getHarrisScore(
|
|
|
24
24
|
origin: Point,
|
|
25
25
|
options: GetHarrisScoreOptions = {},
|
|
26
26
|
): number {
|
|
27
|
-
const { harrisConstant = 0.04, windowSize } = options;
|
|
27
|
+
const { harrisConstant = 0.04, windowSize = 5 } = options;
|
|
28
28
|
const eigenValues = getEigenvaluesForScore(image, origin, windowSize);
|
|
29
29
|
|
|
30
30
|
return (
|
|
@@ -4,11 +4,10 @@ import type { Point } from '../../index_full.ts';
|
|
|
4
4
|
import { getEigenvaluesForScore } from './getEigenvaluesForScore.js';
|
|
5
5
|
|
|
6
6
|
export interface GetShiTomasiScoreOptions {
|
|
7
|
-
qualityLevel?: number;
|
|
8
7
|
/**
|
|
9
8
|
* Size of the window to compute the Harris score.
|
|
10
9
|
* Should be an odd number so that the window can be centered on the corner.
|
|
11
|
-
* @default `
|
|
10
|
+
* @default `5`
|
|
12
11
|
*/
|
|
13
12
|
windowSize?: number;
|
|
14
13
|
}
|
|
@@ -29,7 +28,7 @@ export function getShiTomasiScore(
|
|
|
29
28
|
origin: Point,
|
|
30
29
|
options: GetShiTomasiScoreOptions = {},
|
|
31
30
|
): number {
|
|
32
|
-
const { windowSize =
|
|
31
|
+
const { windowSize = 5 } = options;
|
|
33
32
|
|
|
34
33
|
const eigenValues = getEigenvaluesForScore(image, origin, windowSize);
|
|
35
34
|
return Math.min(eigenValues[0], eigenValues[1]);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Image } from '../../Image.js';
|
|
2
2
|
import type { Point } from '../../geometry/index.js';
|
|
3
|
+
import type { DrawTextOptions } from '../../index_full.ts';
|
|
4
|
+
import { drawText } from '../../index_full.ts';
|
|
3
5
|
import { sum } from '../../utils/geometry/points.js';
|
|
4
6
|
import { getOutputImage } from '../../utils/getOutputImage.js';
|
|
5
7
|
import type { GetColorsOptions } from '../featureMatching.types.js';
|
|
@@ -47,7 +49,7 @@ export interface DrawKeypointsOptions {
|
|
|
47
49
|
/**
|
|
48
50
|
* Options for the coloring of the keypoints depending on their score (useful if showScore = true).
|
|
49
51
|
*/
|
|
50
|
-
showScoreOptions?: GetColorsOptions;
|
|
52
|
+
showScoreOptions?: GetColorsOptions & DrawTextOptions;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export interface DrawOrientedKeypointsOptions extends DrawKeypointsOptions {
|
|
@@ -89,7 +91,8 @@ export function drawKeypoints(
|
|
|
89
91
|
} = options;
|
|
90
92
|
let { maxNbKeypoints = keypoints.length } = options;
|
|
91
93
|
const { strokeColor = [255, 0, 0] } = options;
|
|
92
|
-
|
|
94
|
+
const fontColor = showScoreOptions?.fontColor ?? [0, 0, 255];
|
|
95
|
+
const font = showScoreOptions?.font ?? '20px Helvetica';
|
|
93
96
|
if (maxNbKeypoints > keypoints.length) {
|
|
94
97
|
maxNbKeypoints = keypoints.length;
|
|
95
98
|
}
|
|
@@ -118,6 +121,7 @@ export function drawKeypoints(
|
|
|
118
121
|
strokeColor: keypointColor,
|
|
119
122
|
out: newImage,
|
|
120
123
|
});
|
|
124
|
+
|
|
121
125
|
if (
|
|
122
126
|
isOrientedFastKeypoint(keypoint) &&
|
|
123
127
|
(options as DrawOrientedKeypointsOptions).showOrientation
|
|
@@ -135,7 +139,20 @@ export function drawKeypoints(
|
|
|
135
139
|
});
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
|
-
|
|
142
|
+
if (showScore) {
|
|
143
|
+
drawText(
|
|
144
|
+
newImage,
|
|
145
|
+
keypoints.map((k) => ({
|
|
146
|
+
content: (k.score * 100).toFixed(2),
|
|
147
|
+
position: sum(k.origin, origin),
|
|
148
|
+
font,
|
|
149
|
+
fontColor: [...fontColor, 255],
|
|
150
|
+
})),
|
|
151
|
+
{
|
|
152
|
+
out: newImage,
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
}
|
|
139
156
|
return newImage;
|
|
140
157
|
}
|
|
141
158
|
|
package/src/load/decodeBmp.ts
CHANGED
|
@@ -4,6 +4,10 @@ import { Image } from '../Image.ts';
|
|
|
4
4
|
import { Mask } from '../Mask.ts';
|
|
5
5
|
import type { ImageColorModel } from '../utils/constants/colorModels.ts';
|
|
6
6
|
|
|
7
|
+
import type { Resolution } from './load.types.ts';
|
|
8
|
+
|
|
9
|
+
type DecodedBmp = ReturnType<typeof decode>;
|
|
10
|
+
|
|
7
11
|
/**
|
|
8
12
|
* Decode a BMP. See the fast-bmp npm module.
|
|
9
13
|
* @param data - The data to decode.
|
|
@@ -32,6 +36,24 @@ export function decodeBmp(data: Uint8Array): Image {
|
|
|
32
36
|
return new Image(decodedData.width, decodedData.height, {
|
|
33
37
|
colorModel,
|
|
34
38
|
data: decodedData.data as Uint8Array,
|
|
39
|
+
resolution: getBmpResolution(decodedData),
|
|
35
40
|
});
|
|
36
41
|
}
|
|
37
42
|
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gets image's resolution from its parsed data. BMP stores resolution in
|
|
46
|
+
* pixels per meter.
|
|
47
|
+
* @param bmp - Parsed BMP image.
|
|
48
|
+
* @returns Object with resolution data if it is defined.
|
|
49
|
+
*/
|
|
50
|
+
function getBmpResolution(bmp: DecodedBmp): Resolution | undefined {
|
|
51
|
+
if (!bmp.xPixelsPerMeter || !bmp.yPixelsPerMeter) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
x: bmp.xPixelsPerMeter,
|
|
56
|
+
y: bmp.yPixelsPerMeter,
|
|
57
|
+
unit: 'meter',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import assert from 'node:assert';
|
|
2
|
-
|
|
3
1
|
import type { Mask } from '../Mask.ts';
|
|
4
2
|
import type { Point } from '../utils/geometry/points.ts';
|
|
3
|
+
import { assert } from '../utils/validators/assert.ts';
|
|
5
4
|
|
|
6
5
|
import {
|
|
7
6
|
getBitSafe,
|
|
@@ -56,9 +55,10 @@ export function getContourMoore(mask: Mask): Point[] {
|
|
|
56
55
|
const [row, col] = getCoordsFromIndex(index, mask.width);
|
|
57
56
|
const startingPoint = { column: col, row };
|
|
58
57
|
let currentPoint = startingPoint;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
if (!visited[index]) {
|
|
59
|
+
visited[index] = 1;
|
|
60
|
+
contour.push(currentPoint);
|
|
61
|
+
}
|
|
62
62
|
|
|
63
63
|
const firstNext = findNextPoint(mask, currentPoint, { column: col - 1, row });
|
|
64
64
|
if (!firstNext) {
|
|
@@ -78,7 +78,7 @@ export function getContourMoore(mask: Mask): Point[] {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
const next = findNextPoint(mask, currentPoint, backtrackPoint);
|
|
81
|
-
assert
|
|
81
|
+
assert(next, 'Next border point is undefined.');
|
|
82
82
|
backtrackPoint = next.prevPoint;
|
|
83
83
|
currentPoint = next.currPoint;
|
|
84
84
|
if (
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Point } from '../index_full.ts';
|
|
2
|
+
|
|
3
|
+
export interface ConvexityDefectsOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Depth threshold to remove smaller convexity defaults.
|
|
6
|
+
* @default 0
|
|
7
|
+
*/
|
|
8
|
+
depthThreshold?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Find border points that are farthest from its convex hull lines. Inspired by openCV's convexityDefects.
|
|
13
|
+
* @param borderPoints - Mask's border points.
|
|
14
|
+
* @param hullPoints - Mask's convex hull points.
|
|
15
|
+
* @param options - Convexity defects options.
|
|
16
|
+
* @returns Array of convexity defects.
|
|
17
|
+
*/
|
|
18
|
+
export function getConvexityDefects(
|
|
19
|
+
borderPoints: Point[],
|
|
20
|
+
hullPoints: Point[],
|
|
21
|
+
options: ConvexityDefectsOptions = {},
|
|
22
|
+
): Point[] {
|
|
23
|
+
const { depthThreshold = 0 } = options;
|
|
24
|
+
const defects = [];
|
|
25
|
+
|
|
26
|
+
if (hullPoints.length === 0) {
|
|
27
|
+
throw new RangeError(
|
|
28
|
+
'No hull points were defined for convexity defects detection.',
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (borderPoints.length === 0) {
|
|
32
|
+
throw new RangeError(
|
|
33
|
+
'No border points were defined for convexity defects detection.',
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const lastHullPoint = hullPoints.at(-1) as Point;
|
|
38
|
+
|
|
39
|
+
let currBorderIndex = borderPoints.findIndex((bp) =>
|
|
40
|
+
limitReached(bp, lastHullPoint),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (currBorderIndex === -1) {
|
|
44
|
+
throw new RangeError(
|
|
45
|
+
'Could not find a border point matching the convex hull endpoint.',
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
for (let i = hullPoints.length - 1; i >= 0; i--) {
|
|
49
|
+
const currHullPoint = hullPoints.at(i);
|
|
50
|
+
const nextHullPoint = hullPoints.at(i - 1);
|
|
51
|
+
if (!currHullPoint || !nextHullPoint) continue;
|
|
52
|
+
const v0x = nextHullPoint.column - currHullPoint.column;
|
|
53
|
+
const v0y = nextHullPoint.row - currHullPoint.row;
|
|
54
|
+
const edgeLenSq = v0x * v0x + v0y * v0y;
|
|
55
|
+
let maxDepth = 0;
|
|
56
|
+
let maxDepthIndex = -1;
|
|
57
|
+
const segmentStart = currBorderIndex;
|
|
58
|
+
let reached = false;
|
|
59
|
+
for (let j = 0; j < borderPoints.length; j++) {
|
|
60
|
+
const checkIndex = (segmentStart + j) % borderPoints.length;
|
|
61
|
+
const bp = borderPoints[checkIndex];
|
|
62
|
+
if (limitReached(bp, nextHullPoint)) {
|
|
63
|
+
currBorderIndex = (checkIndex + 1) % borderPoints.length;
|
|
64
|
+
reached = true;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const v1x = bp.column - currHullPoint.column;
|
|
69
|
+
const v1y = bp.row - currHullPoint.row;
|
|
70
|
+
|
|
71
|
+
const t = Math.max(0, Math.min(1, (v1x * v0x + v1y * v0y) / edgeLenSq));
|
|
72
|
+
const closestX = currHullPoint.column + t * v0x;
|
|
73
|
+
const closestY = currHullPoint.row + t * v0y;
|
|
74
|
+
const depth = Math.hypot(bp.column - closestX, bp.row - closestY);
|
|
75
|
+
if (depth > maxDepth) {
|
|
76
|
+
maxDepth = depth;
|
|
77
|
+
maxDepthIndex = checkIndex;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!reached) {
|
|
81
|
+
throw new RangeError(
|
|
82
|
+
'Could not reach the next hull point while scanning border points; hull and border may be inconsistent.',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (maxDepth > depthThreshold) {
|
|
86
|
+
const defectPoint = borderPoints[maxDepthIndex];
|
|
87
|
+
if (defectPoint) defects.push(defectPoint);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return defects;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Checks if borderPoint reached convex hull's point.
|
|
95
|
+
* @param borderPoint - Border point.
|
|
96
|
+
* @param hullPoint - Endpoint of convex hull segment.
|
|
97
|
+
* @returns whether endpoint is reached.
|
|
98
|
+
*/
|
|
99
|
+
function limitReached(borderPoint: Point, hullPoint: Point): boolean {
|
|
100
|
+
const tolerance = 1;
|
|
101
|
+
return (
|
|
102
|
+
Math.abs(borderPoint.column - hullPoint.column) <= tolerance &&
|
|
103
|
+
Math.abs(borderPoint.row - hullPoint.row) <= tolerance
|
|
104
|
+
);
|
|
105
|
+
}
|