Skip to content

Commit 5f37730

Browse files
make testcases runnable in nodejs env
1 parent c8b3410 commit 5f37730

26 files changed

+244
-518
lines changed

.travis.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ language: node_js
33
node_js:
44
- "10"
55
env:
6-
- BACKEND_CPU=true EXCLUDE_UNCOMPRESSED=true
6+
global:
7+
- BACKEND_CPU=true EXCLUDE_UNCOMPRESSED=true
8+
matrix:
9+
- ENV=browser
10+
- ENV=node
711
addons:
812
chrome: stable
913
before_install:
1014
- export DISPLAY=:99.0
1115
- sh -e /etc/init.d/xvfb start
12-
- sleep 3 # give xvfb some time to start
1316
script:
14-
- npm run test-travis
17+
- if [ $ENV == 'browser' ]; then npm run test-browser; fi
18+
- if [ $ENV == 'node' ]; then npm run test-node; fi
1519
- npm run build

jasmine-node.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
const spec_files = ['**/*.test.ts'].concat(
3+
process.env.EXCLUDE_UNCOMPRESSED
4+
? ['!**/*.uncompressed.test.ts']
5+
: []
6+
)
7+
8+
module.exports = {
9+
spec_dir: 'test',
10+
spec_files,
11+
random: false
12+
}

karma.conf.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ let exclude = (
2222
'faceRecognitionNet',
2323
'ssdMobilenetv1',
2424
'tinyFaceDetector',
25-
'mtcnn',
26-
'tinyYolov2'
25+
'mtcnn'
2726
]
28-
: ['tinyYolov2']
27+
: []
2928
)
3029
.filter(ex => ex !== process.env.UUT)
3130
.map(ex => `test/tests/${ex}/*.ts`)

test/env.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as tf from '@tensorflow/tfjs-core';
2+
import { fetchNetWeights, NeuralNetwork } from 'tfjs-image-recognition-base';
3+
4+
import { env, fetchImage, fetchJson } from '../src';
5+
6+
export let fs: any, path: any, canvas: any
7+
8+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000
9+
10+
if (env.isNodejs()) {
11+
require('@tensorflow/tfjs-node')
12+
fs = require('fs')
13+
path = require('path')
14+
canvas = require('canvas')
15+
16+
const { Canvas, Image, ImageData } = canvas
17+
env.monkeyPatch({ Canvas, Image, ImageData })
18+
} else {
19+
if ((window['__karma__'].config.jasmine.args as string[]).some(arg => arg === 'backend_cpu')) {
20+
tf.setBackend('cpu')
21+
}
22+
}
23+
24+
export async function initNet<TNet extends NeuralNetwork<any>>(
25+
net: TNet,
26+
uncompressedFilename: string | boolean,
27+
isUnusedModel: boolean = false
28+
) {
29+
if (env.isNodejs()) {
30+
await net.loadFromDisk(path.resolve(__dirname, '../weights'))
31+
} else {
32+
const url = uncompressedFilename
33+
? await fetchNetWeights(`base/weights_uncompressed/${uncompressedFilename}`)
34+
: (isUnusedModel ? 'base/weights_unused' : 'base/weights')
35+
await net.load(url)
36+
}
37+
}
38+
39+
export async function loadImage(uri: string): Promise<HTMLImageElement> {
40+
if (env.isNodejs()) {
41+
return canvas.loadImage(path.resolve(__dirname, '../', uri))
42+
}
43+
return fetchImage(`base${uri.startsWith('/') ? '' : '/'}${uri}`)
44+
}
45+
46+
export async function loadJson<T>(uri: string): Promise<T> {
47+
if (env.isNodejs()) {
48+
return JSON.parse(fs.readFileSync(path.resolve(__dirname, '../', uri)).toString())
49+
}
50+
return fetchJson<T>(`base${uri.startsWith('/') ? '' : '/'}${uri}`)
51+
}
52+

test/tests/dom/extractFaceTensors.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { bufferToImage, extractFaceTensors, Rect, tf } from '../../../src';
1+
import { createCanvasFromMedia, extractFaceTensors, Rect, tf } from '../../../src';
2+
import { loadImage } from '../../env';
23

34
describe('extractFaceTensors', () => {
45

56
let imgTensor: tf.Tensor3D
67

78
beforeAll(async () => {
8-
const img = await (await fetch('base/test/images/face1.png')).blob()
9-
imgTensor = tf.fromPixels(await bufferToImage(img))
9+
imgTensor = tf.fromPixels(createCanvasFromMedia(await loadImage('test/images/face1.png')))
1010
})
1111

1212
describe('extracts tensors', () => {

test/tests/dom/extractFaces.test.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import { bufferToImage, createCanvasFromMedia, extractFaces, Rect } from '../../../src';
1+
import { createCanvasFromMedia, env, extractFaces, Rect } from '../../../src';
2+
import { loadImage } from '../../env';
23

34
describe('extractFaces', () => {
45

5-
let imgEl: HTMLImageElement, canvasEl: HTMLCanvasElement
6+
let imgEl: HTMLImageElement, canvasEl: HTMLCanvasElement, Canvas: typeof HTMLCanvasElement
67

78
beforeAll(async () => {
8-
const img = await (await fetch('base/test/images/face1.png')).blob()
9-
imgEl = await bufferToImage(img)
9+
imgEl = await loadImage('test/images/face1.png')
1010
canvasEl = createCanvasFromMedia(imgEl)
11+
Canvas = env.getEnv().Canvas
1112
})
1213

1314
describe('extracts canvases', () => {
@@ -17,7 +18,7 @@ describe('extractFaces', () => {
1718
const canvases = await extractFaces(imgEl, [rect])
1819

1920
expect(canvases.length).toEqual(1)
20-
expect(canvases[0] instanceof HTMLCanvasElement).toBe(true)
21+
expect(canvases[0] instanceof Canvas).toBe(true)
2122
expect(canvases[0].width).toEqual(50)
2223
expect(canvases[0].height).toEqual(60)
2324
})
@@ -30,10 +31,10 @@ describe('extractFaces', () => {
3031
const canvases = await extractFaces(imgEl, rects)
3132

3233
expect(canvases.length).toEqual(2)
33-
expect(canvases[0] instanceof HTMLCanvasElement).toBe(true)
34+
expect(canvases[0] instanceof Canvas).toBe(true)
3435
expect(canvases[0].width).toEqual(50)
3536
expect(canvases[0].height).toEqual(60)
36-
expect(canvases[1] instanceof HTMLCanvasElement).toBe(true)
37+
expect(canvases[1] instanceof Canvas).toBe(true)
3738
expect(canvases[1].width).toEqual(70)
3839
expect(canvases[1].height).toEqual(80)
3940
})
@@ -43,7 +44,7 @@ describe('extractFaces', () => {
4344
const canvases = await extractFaces(canvasEl, [rect])
4445

4546
expect(canvases.length).toEqual(1)
46-
expect(canvases[0] instanceof HTMLCanvasElement).toBe(true)
47+
expect(canvases[0] instanceof Canvas).toBe(true)
4748
expect(canvases[0].width).toEqual(50)
4849
expect(canvases[0].height).toEqual(60)
4950
})
@@ -56,10 +57,10 @@ describe('extractFaces', () => {
5657
const canvases = await extractFaces(canvasEl, rects)
5758

5859
expect(canvases.length).toEqual(2)
59-
expect(canvases[0] instanceof HTMLCanvasElement).toBe(true)
60+
expect(canvases[0] instanceof Canvas).toBe(true)
6061
expect(canvases[0].width).toEqual(50)
6162
expect(canvases[0].height).toEqual(60)
62-
expect(canvases[1] instanceof HTMLCanvasElement).toBe(true)
63+
expect(canvases[1] instanceof Canvas).toBe(true)
6364
expect(canvases[1].width).toEqual(70)
6465
expect(canvases[1].height).toEqual(80)
6566
})

test/tests/faceLandmarkNet/FaceLandmark68NetBase.test.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@ import * as tf from '@tensorflow/tfjs-core';
33
import { FaceLandmark68NetBase } from '../../../src/faceLandmarkNet/FaceLandmark68NetBase';
44

55
class FakeFaceLandmark68NetBase extends FaceLandmark68NetBase<any> {
6-
public runNet(_: any): any {
7-
}
8-
9-
protected getDefaultModelName(): any {
10-
throw new Error('FakeNeuralNetwork - getDefaultModelName not implemented')
6+
protected getDefaultModelName(): string {
7+
throw new Error('FakeFaceLandmark68NetBase - getDefaultModelName not implemented')
118
}
129

1310
protected extractParams(_: any): any {
14-
throw new Error('FakeNeuralNetwork - extractParams not implemented')
11+
throw new Error('FakeFaceLandmark68NetBase - extractParams not implemented')
1512
}
1613

1714
protected extractParamsFromWeigthMap(_: any): any {
18-
throw new Error('FakeNeuralNetwork - extractParamsFromWeigthMap not implemented')
15+
throw new Error('FakeFaceLandmark68NetBase - extractParamsFromWeigthMap not implemented')
16+
}
17+
18+
public runNet(): any {
19+
throw new Error('FakeFaceLandmark68NetBase - extractParamsFromWeigthMap not implemented')
1920
}
20-
}
21+
}
2122

2223
describe('FaceLandmark68NetBase', () => {
2324

test/tests/faceLandmarkNet/faceLandmark68Net.test.ts

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as tf from '@tensorflow/tfjs-core';
22

3-
import { fetchImage, fetchJson, IDimensions, isTensor3D, NetInput, Point, TMediaElement, toNetInput } from '../../../src';
3+
import { createCanvasFromMedia, IDimensions, isTensor3D, NetInput, Point, TMediaElement, toNetInput } from '../../../src';
44
import { FaceLandmarks68 } from '../../../src/classes/FaceLandmarks68';
5-
import { createFaceLandmarkNet } from '../../../src/faceLandmarkNet';
65
import { FaceLandmark68Net } from '../../../src/faceLandmarkNet/FaceLandmark68Net';
6+
import { loadImage, loadJson } from '../../env';
77
import { describeWithNets, expectAllTensorsReleased, expectMaxDelta, expectPointClose } from '../../utils';
88

99
function getInputDims (input: tf.Tensor | TMediaElement): IDimensions {
@@ -24,12 +24,12 @@ describe('faceLandmark68Net', () => {
2424
let faceLandmarkPositionsRect: Point[]
2525

2626
beforeAll(async () => {
27-
imgEl1 = await fetchImage('base/test/images/face1.png')
28-
imgEl2 = await fetchImage('base/test/images/face2.png')
29-
imgElRect = await fetchImage('base/test/images/face_rectangular.png')
30-
faceLandmarkPositions1 = await fetchJson<Point[]>('base/test/data/faceLandmarkPositions1.json')
31-
faceLandmarkPositions2 = await fetchJson<Point[]>('base/test/data/faceLandmarkPositions2.json')
32-
faceLandmarkPositionsRect = await fetchJson<Point[]>('base/test/data/faceLandmarkPositionsRect.json')
27+
imgEl1 = await loadImage('test/images/face1.png')
28+
imgEl2 = await loadImage('test/images/face2.png')
29+
imgElRect = await loadImage('test/images/face_rectangular.png')
30+
faceLandmarkPositions1 = await loadJson<Point[]>('test/data/faceLandmarkPositions1.json')
31+
faceLandmarkPositions2 = await loadJson<Point[]>('test/data/faceLandmarkPositions2.json')
32+
faceLandmarkPositionsRect = await loadJson<Point[]>('test/data/faceLandmarkPositionsRect.json')
3333
})
3434

3535
describeWithNets('quantized weights', { withFaceLandmark68Net: { quantized: true } }, ({ faceLandmark68Net }) => {
@@ -85,14 +85,14 @@ describe('faceLandmark68Net', () => {
8585
expect(result.shift.x).toEqual(0)
8686
expect(result.shift.y).toEqual(0)
8787
result.positions.forEach(({ x, y }, i) => {
88-
expectMaxDelta(x, faceLandmarkPositions[batchIdx][i].x, 2)
89-
expectMaxDelta(y, faceLandmarkPositions[batchIdx][i].y, 2)
88+
expectMaxDelta(x, faceLandmarkPositions[batchIdx][i].x, 5)
89+
expectMaxDelta(y, faceLandmarkPositions[batchIdx][i].y, 5)
9090
})
9191
})
9292
})
9393

9494
it('computes face landmarks for batch of tf.Tensor3D', async () => {
95-
const inputs = [imgEl1, imgEl2, imgElRect].map(el => tf.fromPixels(el))
95+
const inputs = [imgEl1, imgEl2, imgElRect].map(el => tf.fromPixels(createCanvasFromMedia(el)))
9696

9797
const faceLandmarkPositions = [
9898
faceLandmarkPositions1,
@@ -117,7 +117,7 @@ describe('faceLandmark68Net', () => {
117117
})
118118

119119
it('computes face landmarks for batch of mixed inputs', async () => {
120-
const inputs = [imgEl1, tf.fromPixels(imgEl2), tf.fromPixels(imgElRect)]
120+
const inputs = [imgEl1, tf.fromPixels(createCanvasFromMedia(imgEl2)), tf.fromPixels(createCanvasFromMedia(imgElRect))]
121121

122122
const faceLandmarkPositions = [
123123
faceLandmarkPositions1,
@@ -145,18 +145,6 @@ describe('faceLandmark68Net', () => {
145145

146146
describeWithNets('no memory leaks', { withFaceLandmark68Net: { quantized: true } }, ({ faceLandmark68Net }) => {
147147

148-
describe('NeuralNetwork, quantized model', () => {
149-
150-
it('disposes all param tensors', async () => {
151-
await expectAllTensorsReleased(async () => {
152-
const net = new FaceLandmark68Net()
153-
await net.load('base/weights')
154-
net.dispose()
155-
})
156-
})
157-
158-
})
159-
160148
describe('forwardInput', () => {
161149

162150
it('single image element', async () => {
@@ -176,7 +164,7 @@ describe('faceLandmark68Net', () => {
176164
})
177165

178166
it('single tf.Tensor3D', async () => {
179-
const tensor = tf.fromPixels(imgEl1)
167+
const tensor = tf.fromPixels(createCanvasFromMedia(imgEl1))
180168

181169
await expectAllTensorsReleased(async () => {
182170
const netInput = new NetInput([tensor])
@@ -188,7 +176,7 @@ describe('faceLandmark68Net', () => {
188176
})
189177

190178
it('multiple tf.Tensor3Ds', async () => {
191-
const tensors = [imgEl1, imgEl1, imgEl1].map(el => tf.fromPixels(el))
179+
const tensors = [imgEl1, imgEl1, imgEl1].map(el => tf.fromPixels(createCanvasFromMedia(el)))
192180

193181
await expectAllTensorsReleased(async () => {
194182
const netInput = new NetInput(tensors)
@@ -200,7 +188,7 @@ describe('faceLandmark68Net', () => {
200188
})
201189

202190
it('single batch size 1 tf.Tensor4Ds', async () => {
203-
const tensor = tf.tidy(() => tf.fromPixels(imgEl1).expandDims()) as tf.Tensor4D
191+
const tensor = tf.tidy(() => tf.fromPixels(createCanvasFromMedia(imgEl1)).expandDims()) as tf.Tensor4D
204192

205193
await expectAllTensorsReleased(async () => {
206194
const outTensor = await faceLandmark68Net.forwardInput(await toNetInput(tensor))
@@ -212,7 +200,7 @@ describe('faceLandmark68Net', () => {
212200

213201
it('multiple batch size 1 tf.Tensor4Ds', async () => {
214202
const tensors = [imgEl1, imgEl1, imgEl1]
215-
.map(el => tf.tidy(() => tf.fromPixels(el).expandDims())) as tf.Tensor4D[]
203+
.map(el => tf.tidy(() => tf.fromPixels(createCanvasFromMedia(el)).expandDims())) as tf.Tensor4D[]
216204

217205
await expectAllTensorsReleased(async () => {
218206
const outTensor = await faceLandmark68Net.forwardInput(await toNetInput(tensors))
@@ -239,7 +227,7 @@ describe('faceLandmark68Net', () => {
239227
})
240228

241229
it('single tf.Tensor3D', async () => {
242-
const tensor = tf.fromPixels(imgEl1)
230+
const tensor = tf.fromPixels(createCanvasFromMedia(imgEl1))
243231

244232
await expectAllTensorsReleased(async () => {
245233
await faceLandmark68Net.detectLandmarks(tensor)
@@ -249,7 +237,7 @@ describe('faceLandmark68Net', () => {
249237
})
250238

251239
it('multiple tf.Tensor3Ds', async () => {
252-
const tensors = [imgEl1, imgEl1, imgEl1].map(el => tf.fromPixels(el))
240+
const tensors = [imgEl1, imgEl1, imgEl1].map(el => tf.fromPixels(createCanvasFromMedia(el)))
253241

254242

255243
await expectAllTensorsReleased(async () => {
@@ -260,7 +248,7 @@ describe('faceLandmark68Net', () => {
260248
})
261249

262250
it('single batch size 1 tf.Tensor4Ds', async () => {
263-
const tensor = tf.tidy(() => tf.fromPixels(imgEl1).expandDims()) as tf.Tensor4D
251+
const tensor = tf.tidy(() => tf.fromPixels(createCanvasFromMedia(imgEl1)).expandDims()) as tf.Tensor4D
264252

265253
await expectAllTensorsReleased(async () => {
266254
await faceLandmark68Net.detectLandmarks(tensor)
@@ -271,7 +259,7 @@ describe('faceLandmark68Net', () => {
271259

272260
it('multiple batch size 1 tf.Tensor4Ds', async () => {
273261
const tensors = [imgEl1, imgEl1, imgEl1]
274-
.map(el => tf.tidy(() => tf.fromPixels(el).expandDims())) as tf.Tensor4D[]
262+
.map(el => tf.tidy(() => tf.fromPixels(createCanvasFromMedia(el)).expandDims())) as tf.Tensor4D[]
275263

276264
await expectAllTensorsReleased(async () => {
277265
await faceLandmark68Net.detectLandmarks(tensors)

0 commit comments

Comments
 (0)