Skip to content

Commit 988a245

Browse files
committed
Updated finder interface and implementation according to restructured code
1 parent 4a5a6f6 commit 988a245

File tree

3 files changed

+45
-125
lines changed

3 files changed

+45
-125
lines changed
Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { Image } from "../../image.class";
21
import { MatchRequest } from "../../match-request.class";
32
import { MatchResult } from "../../match-result.class";
4-
import { Region } from "../../region.class";
53

64
/**
75
* A Finder should provide an abstraction layer to perform
@@ -10,15 +8,6 @@ import { Region } from "../../region.class";
108
* @interface FinderInterface
119
*/
1210
export interface FinderInterface {
13-
/**
14-
* loadImage should allow to load an image from filesystem
15-
*
16-
* @param {string} path The filesystem path to the image
17-
* @returns {*} An image
18-
* @memberof VisionProviderInterface
19-
*/
20-
loadImage(path: string): any;
21-
2211
/**
2312
* findMatch should provide an abstraction to search for an image needle
2413
* in another image haystack
@@ -38,35 +27,4 @@ export interface FinderInterface {
3827
* @memberof FinderInterface
3928
*/
4029
findMatches(matchRequest: MatchRequest): Promise<MatchResult[]>;
41-
42-
/**
43-
* fromImageWithAlphaChannel should provide a way to create a library specific
44-
* image with alpha channel from an abstract Image object holding raw data and image dimension
45-
*
46-
* @param {Image} img The input Image
47-
* @param {Region} [roi] An optional Region to specify a ROI
48-
* @returns {Promise<any>} An image
49-
* @memberof VisionProviderInterface
50-
*/
51-
fromImageWithAlphaChannel(img: Image, roi?: Region): Promise<any>;
52-
53-
/**
54-
* fromImageWithoutAlphaChannel should provide a way to create a library specific
55-
* image without alpha channel from an abstract Image object holding raw data and image dimension
56-
*
57-
* @param {Image} img The input Image
58-
* @param {Region} [roi] An optional Region to specify a ROI
59-
* @returns {Promise<any>} An image
60-
* @memberof VisionProviderInterface
61-
*/
62-
fromImageWithoutAlphaChannel(img: Image, roi?: Region): Promise<any>;
63-
64-
/**
65-
* rgbToGrayScale should provide a way to convert an image from RGB to grayscale
66-
*
67-
* @param {*} img Input image, RGB
68-
* @returns {Promise<any>} Output image, grayscale
69-
* @memberof VisionProviderInterface
70-
*/
71-
rgbToGrayScale(img: any): Promise<any>;
7230
}

lib/provider/opencv/template-matching-finder.class.spec.ts

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,19 @@ import * as path from "path";
22
import { Image } from "../../image.class";
33
import { MatchRequest } from "../../match-request.class";
44
import { Region } from "../../region.class";
5+
import { ImageReader } from "./image-reader.class";
56
import { TemplateMatchingFinder } from "./template-matching-finder.class";
67

78
describe("Template-matching finder", () => {
8-
it("loadImage should resolve to a non-empty Mat on successful load", async () => {
9-
// GIVEN
10-
const SUT = new TemplateMatchingFinder();
11-
const imagePath = path.resolve(__dirname, "./__mocks__/mouse.png");
12-
13-
// WHEN
14-
const result = await SUT.loadImage(imagePath);
15-
16-
// THEN
17-
expect(result.rows).toBeGreaterThan(0);
18-
expect(result.cols).toBeGreaterThan(0);
19-
expect(result.empty).toBeFalsy();
20-
});
21-
22-
it("loadImage should reject on unsuccessful load", async () => {
23-
// GIVEN
24-
const SUT = new TemplateMatchingFinder();
25-
const imagePath = "./__mocks__/foo.png";
26-
27-
// WHEN
28-
const call = SUT.loadImage;
29-
30-
// THEN
31-
await expect(call(imagePath)).rejects.toEqual("empty Mat");
32-
});
33-
349
it("findMatch should return a match when present in image", async () => {
3510
// GIVEN
11+
const imageLoader = new ImageReader();
3612
const SUT = new TemplateMatchingFinder();
3713
const imagePath = path.resolve(__dirname, "./__mocks__/mouse.png");
38-
const needle = await SUT.loadImage(imagePath);
14+
const needle = await imageLoader.load(imagePath);
3915
const minConfidence = 0.99;
40-
const searchRegion = new Region(0, 0, needle.cols, needle.rows);
41-
const haystack = new Image(needle.cols, needle.rows, needle.getData(), 3);
16+
const searchRegion = new Region(0, 0, needle.width, needle.height);
17+
const haystack = new Image(needle.width, needle.height, needle.data, 3);
4218
const matchRequest = new MatchRequest(haystack, imagePath, searchRegion, minConfidence);
4319

4420
// WHEN
@@ -51,12 +27,13 @@ describe("Template-matching finder", () => {
5127

5228
it("findMatch should return a match within a search region when present in image", async () => {
5329
// GIVEN
30+
const imageLoader = new ImageReader();
5431
const SUT = new TemplateMatchingFinder();
5532
const imagePath = path.resolve(__dirname, "./__mocks__/mouse.png");
56-
const needle = await SUT.loadImage(imagePath);
33+
const needle = await imageLoader.load(imagePath);
5734
const minConfidence = 0.99;
5835
const searchRegion = new Region(10, 20, 100, 100);
59-
const haystack = new Image(needle.cols, needle.rows, needle.getData(), 3);
36+
const haystack = new Image(needle.width, needle.height, needle.data, 3);
6037
const matchRequest = new MatchRequest(haystack, imagePath, searchRegion, minConfidence);
6138

6239
// WHEN
@@ -69,13 +46,20 @@ describe("Template-matching finder", () => {
6946

7047
it("findMatch should throw on invalid image paths", async () => {
7148
// GIVEN
49+
const imageLoader = new ImageReader();
7250
const SUT = new TemplateMatchingFinder();
73-
const imagePath = path.resolve(__dirname, "./__mocks__/foo.png");
51+
const pathToNeedle = path.resolve(__dirname, "./__mocks__/mouse.png");
52+
const pathToHaystack = "./__mocks__/foo.png";
53+
const needle = await imageLoader.load(pathToNeedle);
54+
const minConfidence = 0.99;
55+
const searchRegion = new Region(0, 0, 100, 100);
56+
const haystack = new Image(needle.width, needle.height, needle.data, 3);
57+
const matchRequest = new MatchRequest(haystack, pathToHaystack, searchRegion, minConfidence);
7458

7559
// WHEN
76-
const call = await SUT.loadImage;
60+
const result = SUT.findMatch(matchRequest);
7761

7862
// THEN
79-
await expect(call(imagePath)).rejects.toEqual("empty Mat");
63+
expect(result).rejects.toEqual(`Failed to load image from '${pathToHaystack}'`);
8064
});
8165
});

lib/provider/opencv/template-matching-finder.class.ts

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { Image } from "../../image.class";
44
import { MatchRequest } from "../../match-request.class";
55
import { MatchResult } from "../../match-result.class";
66
import { Region } from "../../region.class";
7+
import { DataSource } from "./data-source.interface";
78
import { FinderInterface } from "./finder.interface";
9+
import { ImageProcessor } from "./image-processor.class";
10+
import { ImageReader } from "./image-reader.class";
811

912
export class TemplateMatchingFinder implements FinderInterface {
1013
private static scaleStep = 0.5;
@@ -101,19 +104,23 @@ export class TemplateMatchingFinder implements FinderInterface {
101104
}
102105

103106
private static async debugResult(image: cv.Mat, result: MatchResult, filename: string, suffix?: string) {
104-
const roiRect = new cv.Rect(
105-
result.location.left,
106-
result.location.top,
107-
result.location.width,
108-
result.location.height);
109-
this.debugImage(image.getRegion(roiRect), filename, suffix);
107+
const roiRect = new cv.Rect(
108+
result.location.left,
109+
result.location.top,
110+
result.location.width,
111+
result.location.height);
112+
this.debugImage(image.getRegion(roiRect), filename, suffix);
110113
}
111114

112-
constructor() {
115+
constructor(
116+
private source: DataSource = new ImageReader(),
117+
) {
113118
}
114119

115120
public async findMatches(matchRequest: MatchRequest, debug: boolean = false): Promise<MatchResult[]> {
116-
const needle = await this.loadImage(matchRequest.pathToNeedle);
121+
const needle = await this.loadNeedle(
122+
await this.source.load(matchRequest.pathToNeedle)
123+
);
117124
if (needle.empty) {
118125
throw new Error(
119126
`Failed to load ${matchRequest.pathToNeedle}, got empty image.`,
@@ -178,59 +185,30 @@ export class TemplateMatchingFinder implements FinderInterface {
178185

179186
public async findMatch(matchRequest: MatchRequest, debug: boolean = false): Promise<MatchResult> {
180187
const matches = await this.findMatches(matchRequest, debug);
181-
if (matches.length === 0) {
182-
throw new Error(
183-
`Unable to locate ${matchRequest.pathToNeedle}, no match!`,
184-
);
185-
}
186-
return matches[0];
187-
}
188-
189-
public async loadImage(imagePath: string): Promise<cv.Mat> {
190-
return cv.imreadAsync(imagePath);
191-
}
192-
193-
public async fromImageWithAlphaChannel(
194-
img: Image,
195-
roi?: Region,
196-
): Promise<cv.Mat> {
197-
const mat = await new cv.Mat(img.data, img.height, img.width, cv.CV_8UC4).cvtColorAsync(cv.COLOR_BGRA2BGR);
198-
if (roi) {
199-
return Promise.resolve(
200-
mat.getRegion(new cv.Rect(roi.left, roi.top, roi.width, roi.height)),
201-
);
202-
} else {
203-
return Promise.resolve(mat);
204-
}
205-
}
206-
207-
public async rgbToGrayScale(img: cv.Mat): Promise<cv.Mat> {
208-
return img.cvtColorAsync(cv.COLOR_BGR2GRAY);
188+
return new Promise<MatchResult>((resolve, reject) => {
189+
if (matches.length === 0) {
190+
reject(`Unable to locate ${matchRequest.pathToNeedle}, no match!`);
191+
}
192+
resolve(matches[0]);
193+
});
209194
}
210195

211-
public async fromImageWithoutAlphaChannel(
212-
img: Image,
213-
roi?: Region,
214-
): Promise<cv.Mat> {
215-
const mat = new cv.Mat(img.data, img.height, img.width, cv.CV_8UC3);
216-
if (roi) {
217-
return Promise.resolve(
218-
mat.getRegion(new cv.Rect(roi.left, roi.top, roi.width, roi.height)),
219-
);
220-
} else {
221-
return Promise.resolve(mat);
196+
private async loadNeedle(image: Image): Promise<cv.Mat> {
197+
if (image.hasAlphaChannel) {
198+
return ImageProcessor.fromImageWithAlphaChannel(image);
222199
}
200+
return ImageProcessor.fromImageWithoutAlphaChannel(image);
223201
}
224202

225203
private async loadHaystack(matchRequest: MatchRequest): Promise<cv.Mat> {
226204
const searchRegion = TemplateMatchingFinder.determineScaledSearchRegion(matchRequest);
227205
if (matchRequest.haystack.hasAlphaChannel) {
228-
return await this.fromImageWithAlphaChannel(
206+
return ImageProcessor.fromImageWithAlphaChannel(
229207
matchRequest.haystack,
230208
searchRegion,
231209
);
232210
} else {
233-
return await this.fromImageWithoutAlphaChannel(
211+
return ImageProcessor.fromImageWithoutAlphaChannel(
234212
matchRequest.haystack,
235213
searchRegion,
236214
);

0 commit comments

Comments
 (0)