├── demos
├── models
│ ├── .gitkeep
│ ├── mnist
│ │ ├── download
│ │ └── mnist.onnx
│ ├── emotion_ferplus
│ │ └── download
│ └── squeezenet
│ │ └── download
├── img
│ ├── cat.jpg
│ ├── dog.jpg
│ ├── mnist_5.png
│ └── person.jpg
├── index.html
├── squeezenet.html
├── draw.js
├── mnist.html
├── emotion.html
├── utils.js
└── ILSVRC2012.js
├── .npmignore
├── src
├── index.ts
├── compat
│ ├── merge.ts
│ └── core.ts
├── layers
│ ├── advanced_activations.ts
│ ├── activations.ts
│ ├── merge.ts
│ ├── pooling.ts
│ ├── convolution.ts
│ └── core.ts
├── layer_util.ts
├── node.ts
├── util.ts
├── model.ts
└── onnx_util.ts
├── .vscode
├── tasks.json
└── settings.json
├── CHANGELOG.md
├── .travis.yml
├── tsconfig.json
├── .gitignore
├── README.md
├── package.json
├── gulpfile.js
└── LICENSE
/demos/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demos/models/mnist/download:
--------------------------------------------------------------------------------
1 | https://www.cntk.ai/OnnxModels/mnist.tar.gz
2 |
--------------------------------------------------------------------------------
/demos/img/cat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaosmail/tfjs-onnx/HEAD/demos/img/cat.jpg
--------------------------------------------------------------------------------
/demos/img/dog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaosmail/tfjs-onnx/HEAD/demos/img/dog.jpg
--------------------------------------------------------------------------------
/demos/img/mnist_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaosmail/tfjs-onnx/HEAD/demos/img/mnist_5.png
--------------------------------------------------------------------------------
/demos/img/person.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaosmail/tfjs-onnx/HEAD/demos/img/person.jpg
--------------------------------------------------------------------------------
/demos/models/emotion_ferplus/download:
--------------------------------------------------------------------------------
1 | https://www.cntk.ai/OnnxModels/emotion_ferplus.tar.gz
2 |
--------------------------------------------------------------------------------
/demos/models/squeezenet/download:
--------------------------------------------------------------------------------
1 | https://s3.amazonaws.com/download.onnx/models/opset_6/squeezenet.tar.gz
2 |
--------------------------------------------------------------------------------
/demos/models/mnist/mnist.onnx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaosmail/tfjs-onnx/HEAD/demos/models/mnist/mnist.onnx
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | test/
3 | coverage/
4 | demos/
5 | docs/
6 | bower_components/
7 | node_modules/
8 | bower.json
9 | karma.conf.js
10 | dist/src/**/*.js
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { loadModel, loadModelFromBuffer } from './model';
2 | import * as util from './util';
3 |
4 | export * from '@tensorflow/tfjs';
5 |
6 | export const onnx = {
7 | 'util': util,
8 | 'loadModel': loadModel,
9 | 'loadModelFromBuffer': loadModelFromBuffer
10 | };
11 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "command": "yarn",
6 | "label": "lint",
7 | "type": "shell",
8 | "args": ["lint"],
9 | "problemMatcher": {
10 | "base": "$tslint5",
11 | "owner": "tslint-type-checked",
12 | "fileLocation": "absolute"
13 | }
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | * 0.1.0
4 | * Core framework to parse `Models`, `Nodes` and `Weights`
5 | * Implementation of common layers
6 | * Add SqueezeNet demo
7 |
8 | * 0.1.1
9 | * Add `Add`, `Mul`, `Flatten` and `Reshape` layers
10 |
11 | * 0.1.2
12 | * Add `Constant`, `MatMul`, `Div` and `Sub` layers
13 | * Fix `Reshape` layer and 2D tensors
14 | * Add `topK` in SqueezeNet demo
15 | * Add MNIST and Emotion Recognition demos
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | language: node_js
3 | dist: trusty
4 |
5 | node_js:
6 | - '8.9.1'
7 |
8 | addons:
9 | chrome: stable
10 | firefox: stable
11 |
12 | services:
13 | - docker
14 |
15 | before_install:
16 | # We need a display for the browsers
17 | - export DISPLAY=:99.0
18 | - sh -e /etc/init.d/xvfb start
19 | - sleep 3
20 |
21 | install:
22 | - npm install
23 |
24 | script:
25 | - npm run build
26 | # - npm run test-e2e-travis
27 |
--------------------------------------------------------------------------------
/demos/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Onnx Models
4 |
5 |
6 | Please make sure to download the models in demos/models/* first. The model link is provided in the download file in each model directory or can be found via https://github.com/onnx/models .
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "noImplicitAny": true,
5 | "sourceMap": true,
6 | "removeComments": true,
7 | "preserveConstEnums": true,
8 | "declaration": true,
9 | "target": "es5",
10 | "lib": ["es2015", "dom"],
11 | "outDir": "./dist",
12 | "noUnusedLocals": true,
13 | "noImplicitReturns": true,
14 | "noImplicitThis": true,
15 | "noUnusedParameters": false,
16 | "pretty": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "allowUnreachableCode": false
19 | },
20 | "exclude": [
21 | "node_modules/",
22 | "dist/"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "search.exclude": {
4 | "**/node_modules": true,
5 | "**/bower_components": true,
6 | "coverage/": true,
7 | "dist/": true,
8 | "**/bundle.js": true,
9 | "**/yarn.lock": true
10 | },
11 | "tslint.enable": true,
12 | "tslint.run": "onType",
13 | "tslint.configFile": "tslint.json",
14 | "files.trimTrailingWhitespace": true,
15 | "editor.tabSize": 2,
16 | "editor.insertSpaces": true,
17 | "[typescript]": {
18 | "editor.formatOnSave": true
19 | },
20 | "clang-format.style": "Google",
21 | "clang-format.executable": "${workspaceRoot}/node_modules/.bin/clang-format",
22 | "files.insertFinalNewline": true,
23 | "editor.detectIndentation": false,
24 | "editor.wrappingIndent": "none",
25 | "typescript.tsdk": "node_modules/typescript/lib"
26 | }
27 |
--------------------------------------------------------------------------------
/src/compat/merge.ts:
--------------------------------------------------------------------------------
1 | import * as tf from '@tensorflow/tfjs';
2 | import {Tensor} from '@tensorflow/tfjs';
3 | import {LayerConfig} from '@tensorflow/tfjs-layers/dist/engine/topology';
4 | import {Merge} from '@tensorflow/tfjs-layers/dist/layers/merge';
5 |
6 | // TODO port back to tfjs
7 | export class SubCompat extends Merge {
8 | static className = 'SubCompat';
9 | constructor(config?: LayerConfig) {
10 | super(config as LayerConfig);
11 | }
12 |
13 | protected mergeFunction(inputs: Tensor[]): Tensor {
14 | let output = tf.zeros(inputs[0].shape);
15 | for (const input of inputs) {
16 | output = tf.sub(output, input);
17 | }
18 | return output;
19 | }
20 | }
21 |
22 | // TODO port back to tfjs
23 | export class DivCompat extends Merge {
24 | static className = 'DivCompat';
25 | constructor(config?: LayerConfig) {
26 | super(config as LayerConfig);
27 | }
28 |
29 | protected mergeFunction(inputs: Tensor[]): Tensor {
30 | let output = tf.ones(inputs[0].shape);
31 | for (const input of inputs) {
32 | output = tf.div(output, input);
33 | }
34 | return output;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/layers/advanced_activations.ts:
--------------------------------------------------------------------------------
1 | import * as tf from '@tensorflow/tfjs';
2 | import {SymbolicTensor} from '@tensorflow/tfjs';
3 | import {Layer} from '@tensorflow/tfjs-layers/dist/engine/topology';
4 | import {SoftmaxLayerConfig} from '@tensorflow/tfjs-layers/dist/layers/advanced_activations';
5 | import {onnx} from 'onnx-proto';
6 |
7 | import {OnnxNode} from '../node';
8 | import {parseAttrOrDefault, parseAxis} from '../onnx_util';
9 | import {getNamedAttrs} from '../util';
10 |
11 | export interface SoftmaxNodeConfig {
12 | axis?: onnx.AttributeProto;
13 | }
14 |
15 | export class Softmax extends OnnxNode {
16 | getTfjsLayerConfig(node: onnx.INodeProto, input?: SymbolicTensor[]):
17 | SoftmaxLayerConfig {
18 | const conf = getNamedAttrs(node.attribute) as SoftmaxNodeConfig;
19 | const axis = parseAttrOrDefault(conf.axis, 0) as number;
20 | const inShape = input[0].shape;
21 |
22 | return {
23 | axis: parseAxis(axis, inShape)
24 | }
25 | }
26 |
27 | getTfjsLayer(node: onnx.INodeProto, input?: SymbolicTensor[]): Layer {
28 | const conf = this.getTfjsConfig(node, input) as SoftmaxLayerConfig;
29 | return tf.layers.softmax(conf)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | *.onnx
3 | *.npz
4 | *.pb
5 | e2e/
6 |
7 | # Logs
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Typescript v1 declaration files
46 | typings/
47 |
48 | # Optional npm cache directory
49 | .npm
50 |
51 | # Optional eslint cache
52 | .eslintcache
53 |
54 | # Optional REPL history
55 | .node_repl_history
56 |
57 | # Output of 'npm pack'
58 | *.tgz
59 |
60 | # Yarn Integrity file
61 | .yarn-integrity
62 |
63 | # dotenv environment variables file
64 | .env
65 | /.vs
66 |
--------------------------------------------------------------------------------
/demos/squeezenet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
39 |
--------------------------------------------------------------------------------
/demos/draw.js:
--------------------------------------------------------------------------------
1 | function makeCanvasDrawable(canvas) {
2 | const ctx = canvas.getContext('2d');
3 | const fillColor = "rgba(0,0,0,255)";
4 | ctx.fillStyle = fillColor;
5 | ctx.lineWidth = 20;
6 | ctx.lineCap = "round";
7 | ctx.imageSmoothingEnabled = true;
8 |
9 | let isDrawing = false;
10 | let prevPos = null;
11 |
12 | fillCanvas("rgba(255,255,255,255)");
13 |
14 | function draw(event) {
15 | if (isDrawing) {
16 | drawLineTo(getCursorPos(event));
17 | }
18 | }
19 |
20 | function getCursorPos(event) {
21 | return [event.offsetX, event.offsetY];
22 | }
23 |
24 | function drawLineTo(pos) {
25 | if (prevPos) {
26 | ctx.beginPath();
27 | ctx.moveTo(prevPos[0], prevPos[1]);
28 | ctx.lineTo(pos[0], pos[1]);
29 | ctx.stroke();
30 | }
31 | prevPos = pos;
32 | }
33 |
34 | const size = 10;
35 | function drawPixel(pos) {
36 | ctx.fillRect(pos[0], pos[1], size, size);
37 | }
38 |
39 | function fillCanvas(color) {
40 | ctx.fillStyle = color;
41 | ctx.fillRect(0, 0, canvas.width, canvas.height);
42 | ctx.fillStyle = fillColor;
43 | }
44 |
45 | canvas.onmousedown = () => isDrawing = true;
46 | canvas.onmouseup = () => {
47 | isDrawing = false;
48 | prevPos = null;
49 | }
50 |
51 | return {
52 | start: () => canvas.onmousemove = draw,
53 | stop: () => canvas.onmousemove = null,
54 | clean: () => fillCanvas("rgba(255,255,255,255)")
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/chaosmail/tfjs-onnx)
2 |
3 | # Tensorflow.js Onnx Runner
4 |
5 | Run and finetune pretrained Onnx models in the browser with GPU support via the wonderful [Tensorflow.js][tfjs] library.
6 |
7 | ## Usage
8 |
9 | ### Installation
10 |
11 | You can use this as standalone es5 bundle like this:
12 |
13 | ```html
14 |
15 |
16 | ```
17 |
18 | Then, loading model is a simple as referencing the path to the `model.onnx` file.
19 |
20 | Here is an example of loading SqueezeNet:
21 |
22 | ```js
23 | var modelUrl = 'models/squeezenet/model.onnx';
24 |
25 | // Initialize the tf.model
26 | var model = new onnx.loadModel(modelUrl);
27 |
28 | // Now use tf.model
29 | const pixels = tf.fromPixels(img);
30 | const predictions = model.predict(pixels);
31 | ```
32 |
33 | ### Run Demos
34 |
35 | To run the demo, use the following:
36 |
37 | ```bash
38 | npm run build
39 |
40 | # Start a webserver
41 | npm run serve
42 | ```
43 |
44 | Now navigate to http://localhost:8080/demos.
45 |
46 | > Hint: some of the models are quite big (>30MB). You have to download the Onnx models and place them into the `demos/models` directory to save bandwith.
47 |
48 | ## Development
49 |
50 | ```sh
51 | npm install
52 | ```
53 |
54 | To build a standalone bundle run
55 |
56 | ```sh
57 | npm run build
58 | ```
59 |
60 | [tfjs]: https://github.com/tensorflow/tfjs
61 |
--------------------------------------------------------------------------------
/demos/mnist.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 | Reset
17 |
18 |
19 |
20 |
21 |
22 |
23 |
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tfjs-onnx",
3 | "version": "0.1.2",
4 | "description": "Run pretrained Onnx models in Tensorflow.js",
5 | "main": "dist/node/index.js",
6 | "types": "dist/node/index.d.ts",
7 | "unpkg": "dist/browser/tfjs-onnx.min.js",
8 | "dependencies": {
9 | "onnx-proto": "^3.1.1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/chaosmail/tfjs-onnx.git"
14 | },
15 | "peerDependencies": {
16 | "@tensorflow/tfjs": "^0.10.3"
17 | },
18 | "devDependencies": {
19 | "@types/jasmine": "^2.8.4",
20 | "@types/node": "^9.3.0",
21 | "@tensorflow/tfjs": "^0.10.3",
22 | "browserify": "^16.2.2",
23 | "browserify-shim": "^3.8.14",
24 | "clang-format": "^1.2.2",
25 | "del": "^3.0.0",
26 | "gulp": "^3.9.1",
27 | "gulp-rename": "^1.2.2",
28 | "gulp-typescript": "^3.2.3",
29 | "http-server": "^0.10.0",
30 | "jasmine-co": "^1.2.2",
31 | "jasmine-core": "~2.6.4",
32 | "karma": "^1.7.0",
33 | "karma-chrome-launcher": "^2.2.0",
34 | "karma-firefox-launcher": "^1.0.1",
35 | "karma-jasmine": "~1.1.0",
36 | "karma-typescript": "^3.0.8",
37 | "protobufjs": "~6.8.0",
38 | "run-sequence": "^2.2.0",
39 | "tinyify": "^2.4.0",
40 | "tsify": "^3.0.3",
41 | "tslint": "~5.6.0",
42 | "typescript": "^2.6.2",
43 | "vinyl-source-stream": "^1.1.0"
44 | },
45 | "browserify-shim": {
46 | "@tensorflow/tfjs": "global:tf",
47 | "@tensorflow/tfjs-core": {"depends": ["@tensorflow/tfjs"]},
48 | "@tensorflow/tfjs-layers": {"depends": ["@tensorflow/tfjs"]}
49 | },
50 | "scripts": {
51 | "build": "gulp build",
52 | "serve": "http-server",
53 | "publish-npm": "npm install && gulp build && npm publish"
54 | },
55 | "license": "Apache-2.0"
56 | }
57 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const browserify = require('browserify');
2 | const gulp = require('gulp');
3 | const source = require('vinyl-source-stream');
4 | const rename = require('gulp-rename');
5 | const ts = require('gulp-typescript');
6 | const runSequence = require('run-sequence');
7 | const del = require('del');
8 | const p = require('./package.json');
9 |
10 | const config = {
11 | src: "./src",
12 | pkgname: 'tf',
13 | filename: p.name + ".js",
14 | dst: "./dist"
15 | };
16 |
17 | const tsProject = ts.createProject('tsconfig.json');
18 |
19 | function getBrowserify(tinyify) {
20 | const b = browserify({
21 | entries: config.src + '/index.ts',
22 | standalone: config.pkgname
23 | })
24 | //.transform('browserify-shim')
25 | .plugin('tsify');
26 |
27 | return tinyify === true ? b.plugin('tinyify').bundle() : b.bundle();
28 | }
29 |
30 | // Clean the output folder
31 | gulp.task('clean', () => del([config.dst]));
32 |
33 | // Bundle the project for the browser
34 | gulp.task('browserify', () => {
35 | return getBrowserify()
36 | .pipe(source(config.filename))
37 | .pipe(gulp.dest(config.dst + '/browser'));
38 | });
39 |
40 | // Bundle the project and tinify (flatPack, treeShake) it for the browser
41 | gulp.task('tinyify', () => {
42 | return getBrowserify(true)
43 | .pipe(source(config.filename))
44 | .pipe(rename({suffix: '.min'}))
45 | .pipe(gulp.dest(config.dst + '/browser'));
46 | });
47 |
48 | // Compile the project for Node and Typescript
49 | gulp.task('tsc', () => {
50 | return gulp.src([config.src + '/**/*.ts'])
51 | .pipe(tsProject())
52 | .pipe(gulp.dest(config.dst + '/node'));
53 | });
54 |
55 | // Build step: build JS, tiny JS and TS declaration in parallel
56 | gulp.task('build', (cb) => {
57 | return runSequence('clean', ['browserify', 'tinyify', 'tsc'], cb);
58 | });
59 |
60 | // default task
61 | gulp.task('default', ['browserify']);
62 |
--------------------------------------------------------------------------------
/src/layer_util.ts:
--------------------------------------------------------------------------------
1 | import * as tf from '@tensorflow/tfjs';
2 | import {Shape, SymbolicTensor} from '@tensorflow/tfjs';
3 | import {PaddingMode} from '@tensorflow/tfjs-layers/dist/common';
4 | import {Layer, LayerConfig} from '@tensorflow/tfjs-layers/dist/engine/topology';
5 | import {onnx} from 'onnx-proto';
6 |
7 | import {ConstantCompat} from './compat/core';
8 | import {AutoPad, ConvNodeConfig} from './layers/convolution';
9 | import {parseAttrOrDefault, parseShape} from './onnx_util';
10 | import {getLayerName, getNamedAttrs} from './util';
11 |
12 | export function getInputName(graph: onnx.IGraphProto) {
13 | return getLayerName(graph.input[graph.input.length - 1]);
14 | }
15 |
16 | export function input(name: string, shape: Shape): SymbolicTensor {
17 | const conf = {name: name, shape: shape};
18 | return tf.input(conf);
19 | }
20 |
21 | export function getInputShape(shape: number[]) {
22 | const outShape = parseShape(shape);
23 | // we need to remove the batch dimensions
24 | return outShape.length == 4 ? outShape.slice(1) : outShape;
25 | }
26 |
27 | export function isConstantLayer(layer: Layer) {
28 | return layer instanceof ConstantCompat && layer.outboundNodes.length > 0;
29 | }
30 |
31 | export function getCommonConfig(node: onnx.INodeProto): LayerConfig {
32 | return {name: node.name};
33 | }
34 |
35 | // TODO this code could be removed once we add compat/conv
36 | export function getTfjsPadding(pads: number[], auto_pad: AutoPad): PaddingMode {
37 | const checkAutoPad = auto_pad !== null && auto_pad != 'VALID';
38 | const checkPads = pads !== null && pads.length > 0 && pads[0] != 0;
39 | return checkAutoPad || checkPads ? 'same' : 'valid'
40 | }
41 |
42 | // TODO this code could move to layers/conv
43 | export function getConvDim(node: onnx.INodeProto): number {
44 | const conf = getNamedAttrs(node.attribute) as ConvNodeConfig;
45 | return parseAttrOrDefault(conf.kernel_shape, []).length || 2;
46 | }
47 |
--------------------------------------------------------------------------------
/src/compat/core.ts:
--------------------------------------------------------------------------------
1 | import {Shape} from '@tensorflow/tfjs';
2 | import {Tensor} from '@tensorflow/tfjs-core/dist';
3 | import {DType, Rank} from '@tensorflow/tfjs-core/dist/types';
4 | import {InputLayer, Layer, LayerConfig} from '@tensorflow/tfjs-layers/dist/engine/topology';
5 |
6 | export interface ConstantLayerConfig extends LayerConfig {
7 | value: Tensor, sparse?: boolean, inputShape?: number[]
8 | }
9 |
10 | export class ConstantCompat extends InputLayer {
11 | static readonly className = 'ConstantCompat';
12 | public value: Tensor;
13 |
14 | constructor(config: ConstantLayerConfig) {
15 | super({
16 | name: config.name,
17 | dtype: config.value.dtype as DType,
18 | inputShape: config.inputShape,
19 | sparse: config.sparse
20 | });
21 |
22 | this.value = config.value;
23 | }
24 |
25 | call(inputs: Tensor|Tensor[]): Tensor|Tensor[] {
26 | return this.value;
27 | }
28 |
29 | getClassName(): string {
30 | return ConstantCompat.className;
31 | }
32 | }
33 |
34 | // TODO port back to tfjs
35 | export class MatMulCompat extends Layer {
36 | static className = 'MatMulCompat';
37 | constructor(config?: LayerConfig) {
38 | super(config as LayerConfig);
39 | }
40 |
41 | computeOutputShape(inputShape: Shape[]): Shape {
42 | // TODO add checks for computing the output shape
43 | const aShape = inputShape[0];
44 | const bShape = inputShape[1];
45 | return aShape.slice(0, 1).concat(aShape[1], bShape[2]);
46 | }
47 |
48 | call(inputs: Tensor[]): Tensor|Tensor[] {
49 | if (inputs.length !== 2) {
50 | throw new Error(`Layer 'MatMul' requires 2 inputs`);
51 | }
52 |
53 | // We need to remove the batch dimension for this operation
54 | const a = inputs[0].squeeze([0]) as Tensor;
55 | const b = inputs[1].squeeze([0]) as Tensor;
56 | return a.matMul(b).expandDims(0);
57 | }
58 |
59 | getClassName(): string {
60 | return MatMulCompat.className;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/demos/emotion.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
66 |
--------------------------------------------------------------------------------
/src/layers/activations.ts:
--------------------------------------------------------------------------------
1 | import * as tf from '@tensorflow/tfjs';
2 | import {ActivationIdentifier} from '@tensorflow/tfjs-layers/dist/activations';
3 | import {Layer} from '@tensorflow/tfjs-layers/dist/engine/topology';
4 | import {ActivationLayerConfig} from '@tensorflow/tfjs-layers/dist/layers/core';
5 | import {onnx} from 'onnx-proto';
6 |
7 | import {OnnxNode} from '../node';
8 |
9 | export abstract class Activation extends OnnxNode {
10 | getTfjsLayer(node: onnx.INodeProto): Layer {
11 | return tf.layers.activation(
12 | this.getTfjsConfig(node) as ActivationLayerConfig)
13 | }
14 | }
15 |
16 | export class Relu extends Activation {
17 | getTfjsLayerConfig(node: onnx.INodeProto): ActivationLayerConfig {
18 | return {activation: 'relu' as ActivationIdentifier};
19 | }
20 | }
21 |
22 | export class Tanh extends Activation {
23 | getTfjsLayerConfig(node: onnx.INodeProto): ActivationLayerConfig {
24 | return {activation: 'tanh' as ActivationIdentifier};
25 | }
26 | }
27 |
28 | export class Sigmoid extends Activation {
29 | getTfjsLayerConfig(node: onnx.INodeProto): ActivationLayerConfig {
30 | return {activation: 'sigmoid' as ActivationIdentifier};
31 | }
32 | }
33 |
34 | export class Elu extends Activation {
35 | getTfjsLayerConfig(node: onnx.INodeProto): ActivationLayerConfig {
36 | return {activation: 'elu' as ActivationIdentifier};
37 | }
38 | }
39 |
40 | export class Softplus extends Activation {
41 | getTfjsLayerConfig(node: onnx.INodeProto): ActivationLayerConfig {
42 | return {activation: 'softplus' as ActivationIdentifier};
43 | }
44 | }
45 |
46 | export class Softsign extends Activation {
47 | getTfjsLayerConfig(node: onnx.INodeProto): ActivationLayerConfig {
48 | return {activation: 'softsign' as ActivationIdentifier};
49 | }
50 | }
51 |
52 | export class HardSigmoid extends Activation {
53 | getTfjsLayerConfig(node: onnx.INodeProto): ActivationLayerConfig {
54 | return {activation: 'hardsigmoid' as ActivationIdentifier};
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/layers/merge.ts:
--------------------------------------------------------------------------------
1 | import * as tf from '@tensorflow/tfjs';
2 | import {SymbolicTensor} from '@tensorflow/tfjs';
3 | import {Layer} from '@tensorflow/tfjs-layers/dist/engine/topology';
4 | import {ConcatenateLayerConfig} from '@tensorflow/tfjs-layers/dist/layers/merge';
5 | import {onnx} from 'onnx-proto';
6 |
7 | import {DivCompat, SubCompat} from '../compat/merge';
8 | import {OnnxNode} from '../node';
9 | import {parseAttrOrDefault, parseAxis} from '../onnx_util';
10 | import {getNamedAttrs} from '../util';
11 |
12 | export interface ConcatNodeConfig {
13 | axis?: onnx.AttributeProto;
14 | }
15 |
16 | export class Concat extends OnnxNode {
17 | getTfjsLayerConfig(node: onnx.INodeProto, input?: SymbolicTensor[]):
18 | ConcatenateLayerConfig {
19 | const conf = getNamedAttrs(node.attribute) as ConcatNodeConfig;
20 | const axis = parseAttrOrDefault(conf.axis, 0) as number;
21 | const inShape = input[0].shape;
22 |
23 | return {
24 | axis: parseAxis(axis, inShape)
25 | }
26 | }
27 |
28 | getTfjsLayer(node: onnx.INodeProto, input?: SymbolicTensor[]): Layer {
29 | const conf = this.getTfjsConfig(node, input) as ConcatenateLayerConfig;
30 | return tf.layers.concatenate(conf)
31 | }
32 | }
33 |
34 | export class Add extends OnnxNode {
35 | getTfjsLayer(node: onnx.INodeProto): Layer {
36 | const conf = this.getTfjsConfig(node);
37 | return tf.layers.add(conf);
38 | }
39 | }
40 |
41 | export class Sub extends OnnxNode {
42 | getTfjsLayer(node: onnx.INodeProto): Layer {
43 | const conf = this.getTfjsConfig(node);
44 | return new SubCompat(conf);
45 | }
46 | }
47 |
48 | export class Mul extends OnnxNode {
49 | getTfjsLayer(node: onnx.INodeProto): Layer {
50 | const conf = this.getTfjsConfig(node);
51 | return tf.layers.multiply(conf);
52 | }
53 | }
54 |
55 | export class Div extends OnnxNode {
56 | getTfjsLayer(node: onnx.INodeProto): Layer {
57 | const conf = this.getTfjsConfig(node);
58 | return new DivCompat(conf);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/node.ts:
--------------------------------------------------------------------------------
1 | import * as tf from '@tensorflow/tfjs';
2 | import {SymbolicTensor, Tensor} from '@tensorflow/tfjs';
3 | import {DType} from '@tensorflow/tfjs-core/dist/types';
4 | import {InputLayer, Layer, LayerConfig} from '@tensorflow/tfjs-layers/dist/engine/topology';
5 | import {Initializer} from '@tensorflow/tfjs-layers/dist/initializers';
6 | import {onnx} from 'onnx-proto';
7 |
8 | import {ConstantCompat} from './compat/core';
9 | import {getCommonConfig} from './layer_util';
10 | import {OnnxModel} from './model';
11 |
12 | export type StaticThis = {
13 | new (model: OnnxModel): T
14 | };
15 |
16 | export abstract class OnnxNode {
17 | protected constructor(public model: OnnxModel) {};
18 | static from(this: StaticThis, model: OnnxModel): T {
19 | const that = new this(model);
20 | return that;
21 | }
22 | abstract getTfjsLayer(node: onnx.INodeProto, input?: SymbolicTensor[]): Layer;
23 |
24 | getTfjsLayerConfig(node: onnx.INodeProto, input?: SymbolicTensor[]):
25 | LayerConfig {
26 | return {};
27 | }
28 |
29 | getTfjsConfig(node: onnx.INodeProto, input?: SymbolicTensor[]): LayerConfig {
30 | const commonConfig = getCommonConfig(node);
31 | const layerConfig = this.getTfjsLayerConfig(node, input);
32 | return Object.assign({}, commonConfig, layerConfig);
33 | }
34 |
35 | prepareInput(input?: SymbolicTensor[]): SymbolicTensor[] {
36 | return input;
37 | }
38 |
39 | setup(node: onnx.INodeProto, input?: SymbolicTensor[]):
40 | [Layer, SymbolicTensor]|SymbolicTensor[] {
41 | const layer = this.getTfjsLayer(node, input);
42 |
43 | if (layer instanceof ConstantCompat || layer instanceof InputLayer) {
44 | const outputs = layer.inboundNodes[0].outputTensors;
45 | return [layer, outputs[0]];
46 | }
47 |
48 | return [layer, layer.apply(this.prepareInput(input)) as SymbolicTensor];
49 | }
50 | }
51 |
52 | export class WeightInitializer extends Initializer {
53 | constructor(protected weights: Tensor) {
54 | super();
55 | };
56 | apply(shape: number[], dtype?: DType): tf.Tensor {
57 | return this.weights;
58 | }
59 | getClassName(): string {
60 | return 'WeightInitializer';
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/demos/utils.js:
--------------------------------------------------------------------------------
1 |
2 | function getImageData(player, options) {
3 | options = options || {};
4 | options.shape = options.shape || null;
5 | options.gray = options.gray || false;
6 | options.crop = options.crop || false;
7 |
8 | return tf.tidy(() => {
9 |
10 | const img = tf.fromPixels(player);
11 |
12 | const croppedImg = options.crop ?
13 | cropImage(img) : img;
14 |
15 | const resizedImg = options.shape ?
16 | tf.image.resizeBilinear(croppedImg, options.shape) :
17 | croppedImg;
18 |
19 | const grayImg = options.gray ?
20 | rgbToGrayscale(resizedImg) :
21 | resizedImg;
22 |
23 | // Convert to float and add batch dimension
24 | return grayImg.cast('float32').expandDims();
25 | });
26 | }
27 |
28 | function displayImage(data, elemId) {
29 | const elem = document.getElementById(elemId);
30 | const pixels = tf.squeeze(data).div(tf.scalar(255));
31 | tf.toPixels(pixels, elem);
32 | }
33 |
34 | function displayLabel(probs, labels, elemId) {
35 | const elem = document.getElementById(elemId);
36 | const f = n => n.toLocaleString(undefined, { minimumFractionDigits: 2 });
37 |
38 | const row = labels.map((d,i) => {
39 | return labels[i] + ": " + f(probs[i] * 100) + "%";
40 | })
41 |
42 | elem.innerHTML = row.join(" ");
43 | }
44 |
45 | // TODO port back to tfjs
46 | // @src https://github.com/tensorflow/tensorflow/blob/r1.8/tensorflow/python/ops/image_ops_impl.py#L1255
47 | function rgbToGrayscale(input) {
48 | return tf.tidy(() => {
49 | const rgbWeights = tf.tensor1d([0.2989, 0.5870, 0.1140]);
50 | const floatInput = input.cast('float32');
51 | const grayImage = floatInput.mul(rgbWeights).sum(-1);
52 | return grayImage.expandDims(input.shape.length - 1);
53 | });
54 | }
55 |
56 | // TODO port back to tfjs
57 | // @src https://github.com/tensorflow/tfjs-examples/blob/master/webcam-transfer-learning/webcam.js#L56
58 | function cropImage(img) {
59 | const size = Math.min(img.shape[0], img.shape[1]);
60 | const centerHeight = img.shape[0] / 2;
61 | const beginHeight = centerHeight - (size / 2);
62 | const centerWidth = img.shape[1] / 2;
63 | const beginWidth = centerWidth - (size / 2);
64 | return img.slice([beginHeight, beginWidth, 0], [size, size, img.shape[2]]);
65 | }
66 |
67 | async function getTopKClasses(logits, topK) {
68 | const values = await logits.data();
69 |
70 | const valuesAndIndices = [];
71 | for (let i = 0; i < values.length; i++) {
72 | valuesAndIndices.push({value: values[i], index: i});
73 | }
74 | valuesAndIndices.sort((a, b) => {
75 | return b.value - a.value;
76 | });
77 | const topkValues = new Float32Array(topK);
78 | const topkIndices = new Int32Array(topK);
79 |
80 | for (let i = 0; i < topK; i++) {
81 | topkValues[i] = valuesAndIndices[i].value;
82 | topkIndices[i] = valuesAndIndices[i].index;
83 | }
84 |
85 | return [topkValues, topkIndices];
86 | }
87 |
--------------------------------------------------------------------------------
/src/layers/pooling.ts:
--------------------------------------------------------------------------------
1 | import * as tf from '@tensorflow/tfjs';
2 | import {Layer} from '@tensorflow/tfjs-layers/dist/engine/topology';
3 | import {Pooling1DLayerConfig, Pooling2DLayerConfig} from '@tensorflow/tfjs-layers/dist/layers/pooling';
4 | import {onnx} from 'onnx-proto';
5 |
6 | import {getTfjsPadding} from '../layer_util';
7 | import {OnnxNode} from '../node';
8 | import {parseAttrOrDefault} from '../onnx_util';
9 | import {getNamedAttrs} from '../util';
10 |
11 | export type PoolingLayerConfig = Pooling1DLayerConfig|Pooling2DLayerConfig;
12 |
13 | export interface PoolNodeConfig {
14 | auto_pad?: onnx.AttributeProto;
15 | kernel_shape?: onnx.AttributeProto;
16 | pads?: onnx.AttributeProto;
17 | strides?: onnx.AttributeProto;
18 | }
19 |
20 | export abstract class Pool extends OnnxNode {
21 | getTfjsLayerConfig(node: onnx.INodeProto): PoolingLayerConfig {
22 | const conf = getNamedAttrs(node.attribute) as PoolNodeConfig;
23 | const poolSize = parseAttrOrDefault(conf.kernel_shape) as number;
24 | const strides = parseAttrOrDefault(conf.strides, 1) as number;
25 | const pads = parseAttrOrDefault(conf.pads, null);
26 | const autoPad = parseAttrOrDefault(conf.auto_pad, null);
27 | const padding = getTfjsPadding(pads, autoPad);
28 |
29 | return {
30 | poolSize: poolSize, strides: strides, padding: padding,
31 | }
32 | }
33 |
34 | static getPoolDim(node: onnx.INodeProto): number {
35 | const conf = getNamedAttrs(node.attribute) as PoolNodeConfig;
36 | return parseAttrOrDefault(conf.kernel_shape, []).length || 2;
37 | }
38 | }
39 |
40 | export class MaxPool extends Pool {
41 | getTfjsLayer(node: onnx.INodeProto): Layer {
42 | const dim = Pool.getPoolDim(node);
43 | const conf = this.getTfjsConfig(node) as PoolingLayerConfig;
44 | return dim == 1 ? tf.layers.maxPooling1d(conf as Pooling1DLayerConfig) :
45 | tf.layers.maxPooling2d(conf as Pooling2DLayerConfig);
46 | }
47 | }
48 |
49 | export class AveragePool extends Pool {
50 | getTfjsLayer(node: onnx.INodeProto): Layer {
51 | const dim = Pool.getPoolDim(node);
52 | const conf = this.getTfjsConfig(node) as PoolingLayerConfig;
53 | return dim == 1 ? tf.layers.averagePooling1d(conf as Pooling1DLayerConfig) :
54 | tf.layers.averagePooling2d(conf as Pooling2DLayerConfig);
55 | }
56 | }
57 |
58 | export class GlobalMaxPool extends Pool {
59 | getTfjsLayer(node: onnx.INodeProto): Layer {
60 | const dim = Pool.getPoolDim(node);
61 | const conf = this.getTfjsConfig(node) as PoolingLayerConfig;
62 | return dim == 1 ?
63 | tf.layers.globalMaxPooling1d(conf as Pooling1DLayerConfig) :
64 | tf.layers.globalMaxPooling2d(conf as Pooling2DLayerConfig);
65 | }
66 | }
67 |
68 | export class GlobalAveragePool extends Pool {
69 | getTfjsLayer(node: onnx.INodeProto): Layer {
70 | const dim = Pool.getPoolDim(node);
71 | const conf = this.getTfjsConfig(node) as PoolingLayerConfig;
72 | return dim == 1 ?
73 | tf.layers.globalAveragePooling1d(conf as Pooling1DLayerConfig) :
74 | tf.layers.globalAveragePooling2d(conf as Pooling2DLayerConfig);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/layers/convolution.ts:
--------------------------------------------------------------------------------
1 | import * as tf from '@tensorflow/tfjs';
2 | import {SymbolicTensor, Tensor} from '@tensorflow/tfjs';
3 | import {Layer} from '@tensorflow/tfjs-layers/dist/engine/topology';
4 | import {ConvLayerConfig} from '@tensorflow/tfjs-layers/dist/layers/convolutional';
5 | import {onnx} from 'onnx-proto';
6 |
7 | import {getConvDim, getTfjsPadding} from '../layer_util';
8 | import {OnnxNode, WeightInitializer} from '../node';
9 | import {parseAttrOrDefault} from '../onnx_util';
10 | import {getNamedAttrs} from '../util';
11 |
12 | import {Constant} from './core';
13 |
14 | export type AutoPad = 'SAME_UPPER'|'SAME_LOWER'|'VALID';
15 |
16 | export interface ConvNodeConfig {
17 | auto_pad?: onnx.AttributeProto;
18 | dilations?: onnx.AttributeProto;
19 | group?: onnx.AttributeProto;
20 | kernel_shape?: onnx.AttributeProto;
21 | pads?: onnx.AttributeProto;
22 | strides?: onnx.AttributeProto;
23 | }
24 |
25 | export class Conv extends OnnxNode {
26 | getTensorAttr(name: string): Tensor {
27 | if (this.model.blobValues !== undefined &&
28 | this.model.blobValues.hasOwnProperty(name)) {
29 | return this.model.blobValues[name];
30 | } else if (
31 | this.model.nodes !== undefined &&
32 | this.model.nodes.hasOwnProperty(name)) {
33 | const node = this.model.nodes[name];
34 | if (node.opType == 'Constant') {
35 | return Constant.getConstantAttr(node);
36 | }
37 | throw new Error(`Cannot extract tensor attribute '${
38 | name}' from layer other than 'Constant'`);
39 | }
40 | // TODO if model is not trained, we can use the
41 | // this.model.blobShapes to extract tensor shape
42 | else {
43 | throw new Error(`Cannot find tensor attribute '${name}'`);
44 | }
45 | }
46 |
47 | getTfjsLayerConfig(node: onnx.INodeProto): ConvLayerConfig {
48 | const conf = getNamedAttrs(node.attribute) as ConvNodeConfig;
49 | const kernelSize = parseAttrOrDefault(conf.kernel_shape) as number[];
50 | const strides = parseAttrOrDefault(conf.strides, 1) as number[];
51 | const pads = parseAttrOrDefault(conf.pads, null);
52 | const autoPad = parseAttrOrDefault(conf.auto_pad, null);
53 | const padding = getTfjsPadding(pads, autoPad);
54 | const dilationRate = parseAttrOrDefault(conf.dilations, 1);
55 |
56 | // tfjs shape: numChannels, inHeight, inWidth, inChannels
57 | // conv shape: inHeight, inWidth, inChannels, numChannels
58 | const kernel = this.getTensorAttr(node.input[1]).transpose([2, 1, 3, 0]);
59 | const bias = node.input[2] ? this.getTensorAttr(node.input[2]) : null;
60 | const filters = kernel.shape[3];
61 |
62 | return {
63 | kernelSize: kernelSize, strides: strides, padding: padding,
64 | dilationRate: dilationRate, filters: filters,
65 | kernelInitializer: new WeightInitializer(kernel),
66 | useBias: Boolean(bias),
67 | biasInitializer: bias ? new WeightInitializer(bias) : undefined,
68 | }
69 | }
70 |
71 | getTfjsLayer(node: onnx.INodeProto): Layer {
72 | const dim = getConvDim(node);
73 | const conf = this.getTfjsConfig(node) as ConvLayerConfig;
74 | return dim == 1 ? tf.layers.conv1d(conf) : tf.layers.conv2d(conf);
75 | }
76 |
77 | prepareInput(input?: SymbolicTensor[]) {
78 | return input.slice(0, 1);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------
1 | import {Tensor} from '@tensorflow/tfjs';
2 | import {onnx} from 'onnx-proto';
3 |
4 | import {parseTensor} from './onnx_util';
5 |
6 | export function normalizeArrayToObj(
7 | array: T[], indexKey: keyof T): {[key: string]: T} {
8 | const normalizedObject: any = {};
9 | for (let i = 0; i < array.length; i++) {
10 | const key = array[i][indexKey];
11 | normalizedObject[key] = array[i];
12 | }
13 | return normalizedObject as {
14 | [key: string]: T
15 | }
16 | }
17 |
18 | export function joinArraysToObj(
19 | keys: string[], values: T[]): {[key: string]: T} {
20 | const normalizedObject: any = {};
21 | for (let i = 0; i < keys.length; i++) {
22 | const key = keys[i];
23 | normalizedObject[key] = values[i];
24 | }
25 | return normalizedObject as {
26 | [key: string]: T
27 | }
28 | }
29 |
30 | // tslint:disable-next-line:no-any
31 | export function isNotNull(val: any): boolean {
32 | return val !== undefined && val !== null;
33 | }
34 |
35 | export function fetchText(uri: string): Promise {
36 | return fetch(new Request(uri))
37 | .then(handleFetchErrors)
38 | .then((res) => res.text());
39 | }
40 |
41 | export function fetchArrayBuffer(uri: string): Promise {
42 | return fetch(new Request(uri))
43 | .then(handleFetchErrors)
44 | .then((res) => res.arrayBuffer());
45 | }
46 |
47 | function handleFetchErrors(response: Response) {
48 | if (!response.ok) {
49 | throw Error(response.statusText);
50 | }
51 | return response;
52 | }
53 |
54 | export function parseOnnxModel(data: ArrayBuffer) {
55 | return onnx.ModelProto.decode(new Uint8Array(data));
56 | }
57 |
58 | export function getNamedAttrs(attrs?: any[]): T {
59 | return normalizeArrayToObj(attrs, 'name') as T;
60 | }
61 |
62 | export async function loadOnnxModel(modelUrl: string):
63 | Promise {
64 | const buffer = await fetchArrayBuffer(modelUrl);
65 | return await parseOnnxModel(buffer);
66 | }
67 |
68 | export function getBlobValues(graph: onnx.IGraphProto):
69 | {[name: string]: Tensor} {
70 | const blobs = graph.initializer;
71 | const weights = blobs.map((d: onnx.TensorProto) => parseTensor(d));
72 | const names = blobs.map(getLayerName);
73 | return joinArraysToObj(names, weights);
74 | }
75 |
76 | export function getValueInfo(valueInfo: onnx.IValueInfoProto[]):
77 | {[name: string]: number[]} {
78 | const getDimValues = (shape: onnx.ITensorShapeProto) =>
79 | shape.dim.map(d => d.dimValue) as number[];
80 |
81 | const shapes = valueInfo.map(d => d.type.tensorType.shape).map(getDimValues);
82 | const names = valueInfo.map(getLayerName);
83 | return joinArraysToObj(names, shapes);
84 | }
85 |
86 | export function getBlobShapes(graph: onnx.IGraphProto) {
87 | // Parse input/output tensor shapes
88 | const inputShapes = getValueInfo(graph.input);
89 | const outputShapes = getValueInfo(graph.output);
90 | return Object.assign({}, inputShapes, outputShapes);
91 | }
92 |
93 | export function getLayerName(node: onnx.INodeProto) {
94 | return node.name ? node.name : node.output[0];
95 | }
96 |
97 | export function getNodes(graph: onnx.IGraphProto) {
98 | const nodes = graph.node;
99 | const names = nodes.map(getLayerName);
100 | return joinArraysToObj(names, nodes);
101 | }
102 |
103 | export function loadImageData(url: string): Promise {
104 | const img = new Image();
105 | return new Promise((resolve, reject) => {
106 | img.crossOrigin = 'anonymous';
107 | img.src = url;
108 | img.onload = () => resolve(img);
109 | img.onerror = reject;
110 | });
111 | }
112 |
--------------------------------------------------------------------------------
/src/layers/core.ts:
--------------------------------------------------------------------------------
1 | import * as tf from '@tensorflow/tfjs';
2 | import {SymbolicTensor, Tensor} from '@tensorflow/tfjs';
3 | import {Layer, LayerConfig} from '@tensorflow/tfjs-layers/dist/engine/topology';
4 | import {DenseLayerConfig, DropoutLayerConfig, ReshapeLayerConfig} from '@tensorflow/tfjs-layers/dist/layers/core';
5 | import {onnx} from 'onnx-proto';
6 |
7 | import {ConstantCompat, ConstantLayerConfig, MatMulCompat} from '../compat/core';
8 | import {OnnxNode, WeightInitializer} from '../node';
9 | import {parseAttr, parseAttrOrDefault, parseShape, parseTensor} from '../onnx_util';
10 | import {getNamedAttrs} from '../util';
11 |
12 | export interface ConstantNodeConfig {
13 | value?: onnx.AttributeProto;
14 | }
15 |
16 | export class Constant extends OnnxNode {
17 | static getConstantAttr(node: onnx.INodeProto, transpose = true) {
18 | const conf = getNamedAttrs(node.attribute) as ConstantNodeConfig;
19 | const value = parseAttr(conf.value) as onnx.TensorProto;
20 | return parseTensor(value, transpose) as Tensor;
21 | }
22 |
23 | getTfjsLayerConfig(node: onnx.INodeProto): ConstantLayerConfig {
24 | const value = Constant.getConstantAttr(node);
25 |
26 | return {
27 | value: value.expandDims(0), inputShape: value.shape
28 | }
29 | }
30 |
31 | getTfjsLayer(node: onnx.INodeProto): Layer {
32 | const conf = this.getTfjsConfig(node) as ConstantLayerConfig;
33 | return new ConstantCompat(conf);
34 | }
35 | }
36 |
37 | export interface FCNodeConfig {
38 | axis?: onnx.AttributeProto;
39 | axis_w?: onnx.AttributeProto;
40 | }
41 |
42 | export class Dense extends OnnxNode {
43 | getTfjsLayerConfig(node: onnx.INodeProto): DenseLayerConfig {
44 | const w = node.input[1];
45 | const b = node.input[2];
46 | const weightShape = this.model.blobShapes[w];
47 | const units = weightShape[0];
48 | const kernel = this.model.blobValues[w];
49 | const bias = this.model.blobValues[b];
50 |
51 | return {
52 | units: units, kernelInitializer: new WeightInitializer(kernel),
53 | biasInitializer: new WeightInitializer(bias)
54 | }
55 | }
56 |
57 | getTfjsLayer(node: onnx.INodeProto): Layer {
58 | const conf = this.getTfjsConfig(node) as DenseLayerConfig;
59 | return tf.layers.dense(conf)
60 | }
61 | }
62 |
63 | export interface DropoutNodeConfig {
64 | is_test?: onnx.AttributeProto;
65 | ratio?: onnx.AttributeProto;
66 | }
67 |
68 | export class Dropout extends OnnxNode {
69 | getTfjsLayerConfig(node: onnx.INodeProto): DropoutLayerConfig {
70 | const conf = getNamedAttrs(node.attribute) as DropoutNodeConfig;
71 | const ratio = parseAttrOrDefault(conf.ratio, 0) as number;
72 | return {
73 | rate: ratio
74 | }
75 | }
76 |
77 | getTfjsLayer(node: onnx.INodeProto): Layer {
78 | const conf = this.getTfjsConfig(node) as DropoutLayerConfig;
79 | return tf.layers.dropout(conf);
80 | }
81 | }
82 |
83 | export class Flatten extends OnnxNode {
84 | getTfjsLayer(node: onnx.INodeProto): Layer {
85 | const conf = this.getTfjsConfig(node);
86 | return tf.layers.flatten(conf);
87 | }
88 | }
89 |
90 | export interface ReshapeNodeConfig {
91 | shape?: onnx.AttributeProto;
92 | }
93 |
94 | export class Reshape extends OnnxNode {
95 | isSimplifiable(input?: SymbolicTensor[]) {
96 | if (input.length == 1 && input[0] !== undefined &&
97 | input[0].sourceLayer instanceof ConstantCompat) {
98 | return true;
99 | }
100 | return false;
101 | }
102 |
103 | getConstantLayer(
104 | node: onnx.INodeProto, origConf: LayerConfig, shape: number[]) {
105 | // the layer is reshaped, therefore it contains no batch dimensions
106 | const constValue =
107 | Constant.getConstantAttr(this.model.nodes[node.input[0]], false);
108 | const value = constValue.reshape(shape).expandDims(0);
109 | const conf = {name: origConf.name, value: value, inputShape: shape};
110 | return new ConstantCompat(conf);
111 | }
112 |
113 | getTfjsLayerConfig(node: onnx.INodeProto, input?: SymbolicTensor[]):
114 | ReshapeLayerConfig {
115 | const conf = getNamedAttrs(node.attribute) as ReshapeNodeConfig;
116 | const value = parseAttr(conf.shape);
117 | const shape = parseShape(value);
118 | // Add batch dimension if required
119 | const targetShape =
120 | input[0].shape[0] == null ? shape : [null].concat(shape);
121 | return {targetShape: targetShape};
122 | }
123 |
124 | getTfjsLayer(node: onnx.INodeProto, input?: SymbolicTensor[]): Layer {
125 | const conf = this.getTfjsConfig(node, input) as ReshapeLayerConfig;
126 |
127 | // TODO not only reshape can take constant inputs
128 | // check if this can be generalized to all layers
129 | if (this.isSimplifiable(input)) {
130 | return this.getConstantLayer(node, conf, conf.targetShape);
131 | }
132 |
133 | return tf.layers.reshape(conf);
134 | }
135 | }
136 |
137 | export class MatMul extends OnnxNode {
138 | getTfjsLayer(node: onnx.INodeProto): Layer {
139 | const conf = this.getTfjsConfig(node);
140 | return new MatMulCompat(conf);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/model.ts:
--------------------------------------------------------------------------------
1 | import {Model, ModelPredictConfig, SymbolicTensor, Tensor} from '@tensorflow/tfjs';
2 | import {ContainerConfig, Layer} from '@tensorflow/tfjs-layers/dist/engine/topology';
3 | import {onnx} from 'onnx-proto';
4 |
5 | import {ConstantCompat} from './compat/core';
6 | import * as layer_util from './layer_util';
7 | import {Elu, HardSigmoid, Relu, Sigmoid, Softplus, Softsign, Tanh} from './layers/activations'
8 | import {Softmax} from './layers/advanced_activations';
9 | import {Conv} from './layers/convolution'
10 | import {Constant, Dense, Dropout, Flatten, MatMul, Reshape} from './layers/core';
11 | import {Add, Concat, Div, Mul, Sub} from './layers/merge';
12 | import {AveragePool, GlobalAveragePool, GlobalMaxPool, MaxPool} from './layers/pooling';
13 | import {OnnxNode} from './node';
14 | import * as util from './util';
15 |
16 | type NodeFactory = {
17 | [key: string]: typeof OnnxNode
18 | };
19 |
20 | const nodeFactory: NodeFactory = {
21 | 'Add': Add,
22 | 'AveragePool': AveragePool,
23 | 'Concat': Concat,
24 | 'Constant': Constant,
25 | 'Conv': Conv,
26 | 'Div': Div,
27 | 'Dropout': Dropout,
28 | 'Elu': Elu,
29 | 'FC': Dense,
30 | 'Flatten': Flatten,
31 | 'GlobalAveragePool': GlobalAveragePool,
32 | 'GlobalMaxPool': GlobalMaxPool,
33 | 'HardSigmoid': HardSigmoid,
34 | 'MaxPool': MaxPool,
35 | 'MatMul': MatMul,
36 | 'Mul': Mul,
37 | 'Relu': Relu,
38 | 'Reshape': Reshape,
39 | 'Sigmoid': Sigmoid,
40 | 'Softmax': Softmax,
41 | 'Softplus': Softplus,
42 | 'Softsign': Softsign,
43 | 'Sub': Sub,
44 | 'Tanh': Tanh,
45 | };
46 |
47 | export type Blob = Tensor|SymbolicTensor;
48 |
49 | export async function loadModel(modelUrl: string): Promise {
50 | const model = new OnnxModel(modelUrl);
51 | await model.load();
52 | return model.getModel();
53 | }
54 |
55 | export async function loadModelFromBuffer(modelBuffer: ArrayBuffer): Promise {
56 | const model = new OnnxModel('');
57 | await model.loadFromBuffer(modelBuffer);
58 | return model.getModel();
59 | }
60 |
61 | export class ModelCompat extends Model {
62 | constructor(config: ContainerConfig, public onnx: OnnxModel) {
63 | super(config);
64 | }
65 |
66 | predict(x: Tensor|Tensor[], config: ModelPredictConfig = {}): Tensor
67 | |Tensor[] {
68 | return super.predict(this.onnx.getAllInputs(x), config);
69 | }
70 | }
71 |
72 | export class OnnxModel {
73 | onnx: onnx.IModelProto;
74 | graph: onnx.IGraphProto;
75 | blobShapes: {[name: string]: number[]};
76 | blobValues: {[name: string]: Tensor};
77 | nodes: {[name: string]: onnx.INodeProto};
78 | layers: {[name: string]: Layer} = {};
79 | blobs: {[name: string]: Blob} = {};
80 |
81 | constructor(public modelUrl: string) {}
82 |
83 | async load() {
84 | this.onnx = await util.loadOnnxModel(this.modelUrl);
85 | this.graph = this.onnx.graph;
86 | this.nodes = util.getNodes(this.graph);
87 | this.blobShapes = util.getBlobShapes(this.graph);
88 | this.blobValues = util.getBlobValues(this.graph);
89 | }
90 |
91 | async loadFromBuffer(model: ArrayBuffer) {
92 | this.onnx = await util.parseOnnxModel(model);
93 | this.graph = this.onnx.graph;
94 | this.nodes = util.getNodes(this.graph);
95 | this.blobShapes = util.getBlobShapes(this.graph);
96 | this.blobValues = util.getBlobValues(this.graph);
97 | }
98 |
99 | getAllInputs(input: Tensor | Tensor[]): Tensor[] {
100 | return [].concat(input, this.getConstantInputs());
101 | }
102 |
103 | getModel() {
104 | const input = this.getInputLayer();
105 |
106 | this.blobs[input.name] = input;
107 |
108 | for (let i = 0; i < this.graph.node.length; ++i) {
109 | let currNode = this.graph.node[i];
110 |
111 | let inputBlobs: SymbolicTensor[] = [];
112 | for (let j = 0; j < currNode.input.length; ++j) {
113 | let inputNodeName = currNode.input[j];
114 | if (this.blobs.hasOwnProperty(inputNodeName)) {
115 | inputBlobs.push(this.blobs[inputNodeName] as SymbolicTensor);
116 | }
117 | }
118 |
119 | const [layer, output] = this.setupTfjsLayer(currNode, inputBlobs);
120 | this.layers[layer.name] = layer;
121 |
122 | currNode.output.forEach((d) => {
123 | this.blobs[d] = output;
124 | });
125 | }
126 |
127 | // Select the input blobs
128 | const inputs = [input].concat(this.getSymbolicConstantInputs());
129 |
130 | // Select the output blobs
131 | const outputs = this.getSymbolicOutputs();
132 |
133 | // Create the container config
134 | const config = {inputs: inputs, outputs: outputs, name: this.graph.name} as
135 | ContainerConfig;
136 |
137 | // Create the model
138 | return new ModelCompat(config, this);
139 | }
140 |
141 | setupTfjsLayer(node: onnx.INodeProto, input?: SymbolicTensor[]):
142 | [Layer, SymbolicTensor] {
143 | if (nodeFactory.hasOwnProperty(node.opType)) {
144 | const onnxNode = (nodeFactory[node.opType]).from(this);
145 | return onnxNode.setup(node, input) as [Layer, SymbolicTensor];
146 | }
147 | throw new Error(`'${node.opType}' is not implemented in tfjs-onnx.`);
148 | }
149 |
150 | private getInputLayer(): SymbolicTensor {
151 | const name = layer_util.getInputName(this.graph);
152 | const shape = layer_util.getInputShape(this.blobShapes[name]);
153 | return layer_util.input(name, shape);
154 | }
155 |
156 | private getConstantInputNames(): string[] {
157 | return Object.keys(this.layers)
158 | .filter(d => layer_util.isConstantLayer(this.layers[d]));
159 | }
160 |
161 | private getConstantInputs(): Tensor[] {
162 | return this.getConstantInputNames()
163 | .map(d => this.layers[d] as ConstantCompat)
164 | .map(d => d.value) as Tensor[];
165 | }
166 |
167 | private getSymbolicConstantInputs() {
168 | return this.getConstantInputNames()
169 | .map(d => this.nodes[d].output[0])
170 | .map(d => this.blobs[d]) as SymbolicTensor[];
171 | }
172 |
173 | private getSymbolicOutputs() {
174 | return this.graph.output.map(d => d.name).map(d => this.blobs[d]);
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/onnx_util.ts:
--------------------------------------------------------------------------------
1 | import * as tf from '@tensorflow/tfjs';
2 | import {Tensor} from '@tensorflow/tfjs';
3 | import {DType, TypedArray} from '@tensorflow/tfjs-core/dist/types';
4 | import {onnx} from 'onnx-proto';
5 |
6 | export function parseAxis(axis: number, shape: number[]): number {
7 | // convert to channelsLast
8 | // -----------------------------------------------------
9 | // onnx shape: batchSize, inChannels, inHeight, inWidth
10 | // tfjs shape: batchSize, inHeight, inWidth, inChannels
11 | switch (shape.length) {
12 | case 4:
13 | return axis == 1 ? 3 : axis == 3 || axis == -1 ? 1 : axis;
14 | case 3:
15 | return axis == 0 ? 2 : axis == 2 || axis == -1 ? 0 : axis;
16 | default:
17 | return axis;
18 | }
19 | }
20 |
21 | export function parseShape(shape: number[]): number[] {
22 | // convert to channelsLast
23 | // -----------------------------------------------------
24 | // onnx shape: batchSize, inChannels, inHeight, inWidth
25 | // tfjs shape: batchSize, inHeight, inWidth, inChannels
26 | switch (shape.length) {
27 | case 4:
28 | return [shape[0], shape[2], shape[3], shape[1]];
29 | case 3:
30 | return [shape[1], shape[2], shape[0]];
31 | default:
32 | return shape;
33 | }
34 | }
35 |
36 | export function parseTensor(
37 | tensor: onnx.TensorProto, transpose = true): Tensor {
38 | const shape = tensor.dims as number[];
39 | const dtype = parseTensorDtype(tensor);
40 | const typedArray = parseTensorData(tensor);
41 | const data = tf.tensor(typedArray, shape, dtype);
42 |
43 | // convert to channelsLast
44 | // -----------------------------------------------------
45 | // onnx shape: batchSize, inChannels, inHeight, inWidth
46 | // tfjs shape: batchSize, inHeight, inWidth, inChannels
47 | if (transpose) {
48 | switch (shape.length) {
49 | case 4:
50 | return data.transpose([0, 2, 3, 1]);
51 | case 3:
52 | return data.transpose([1, 2, 0]);
53 | default:
54 | return data;
55 | }
56 | }
57 | return data;
58 | }
59 |
60 | export function parseAttrOrDefault(attr: onnx.AttributeProto, def?: any): any {
61 | return attr === undefined ? def : parseAttr(attr);
62 | }
63 |
64 | export function parseAttr(attr: onnx.AttributeProto): any {
65 | switch (attr.type) {
66 | case onnx.AttributeProto.AttributeType.FLOAT:
67 | return attr.f;
68 | case onnx.AttributeProto.AttributeType.INT:
69 | return attr.i;
70 | case onnx.AttributeProto.AttributeType.STRING:
71 | return attr.s;
72 | case onnx.AttributeProto.AttributeType.TENSOR:
73 | return attr.t;
74 | case onnx.AttributeProto.AttributeType.GRAPH:
75 | return attr.g;
76 | case onnx.AttributeProto.AttributeType.FLOATS:
77 | return attr.floats;
78 | case onnx.AttributeProto.AttributeType.INTS:
79 | return attr.ints;
80 | case onnx.AttributeProto.AttributeType.STRINGS:
81 | return attr.strings;
82 | case onnx.AttributeProto.AttributeType.TENSORS:
83 | return attr.tensors;
84 | case onnx.AttributeProto.AttributeType.GRAPHS:
85 | return attr.graphs;
86 | case onnx.AttributeProto.AttributeType.UNDEFINED:
87 | default:
88 | throw new Error(`Cannot parse attr '${attr.name}'`);
89 | }
90 | }
91 |
92 | export function parseTensorDtype(tensor: onnx.TensorProto): DType {
93 | switch (tensor.dataType) {
94 | case onnx.TensorProto.DataType.INT8:
95 | console.warn(`'Int8Array' type is not supported in tfjs. Converting to ${
96 | DType.int32}`);
97 | return DType.int32;
98 | case onnx.TensorProto.DataType.INT16:
99 | console.warn(`'Int16Array' type is not supported in tfjs. Converting to ${
100 | DType.int32}`);
101 | return DType.int32;
102 | case onnx.TensorProto.DataType.INT32:
103 | return DType.int32;
104 | case onnx.TensorProto.DataType.INT64:
105 | console.warn(
106 | `'Int64Array' type is not supported in JavaScript. Trying to convert to ${
107 | DType.int32}`);
108 | return DType.int32;
109 | case onnx.TensorProto.DataType.UINT8:
110 | throw new Error(`Cannot use 'uint8' tensor in tfjs`);
111 | case onnx.TensorProto.DataType.UINT16:
112 | throw new Error(`Cannot use 'uint16' tensor in tfjs`);
113 | case onnx.TensorProto.DataType.UINT32:
114 | throw new Error(`Cannot use 'uint32' tensor in tfjs`);
115 | case onnx.TensorProto.DataType.UINT16:
116 | throw new Error(`Cannot use 'uint64' tensor in tfjs`);
117 | case onnx.TensorProto.DataType.FLOAT:
118 | return DType.float32;
119 | case onnx.TensorProto.DataType.DOUBLE:
120 | console.warn(`'double' type is not supported in tfjs. Converting to ${
121 | DType.float32}`)
122 | return DType.float32;
123 | case onnx.TensorProto.DataType.UNDEFINED:
124 | default:
125 | throw new Error(`Cannot parse tensor '${tensor.dataType}'`);
126 | }
127 | }
128 |
129 | function getArrayBuffer(b: Uint8Array) {
130 | const data = new Uint8Array(b);
131 | return data.buffer;
132 | }
133 |
134 | export function parseTensorData(tensor: onnx.TensorProto): TypedArray {
135 | switch (tensor.dataType) {
136 | case onnx.TensorProto.DataType.INT8:
137 | return new Int32Array(new Int8Array(getArrayBuffer(tensor.rawData)));
138 | case onnx.TensorProto.DataType.INT16:
139 | return new Int32Array(new Int16Array(getArrayBuffer(tensor.rawData)));
140 | case onnx.TensorProto.DataType.INT32:
141 | return tensor.int32Data.length > 0 ?
142 | new Int32Array(tensor.int32Data) :
143 | new Int32Array(getArrayBuffer(tensor.rawData));
144 | case onnx.TensorProto.DataType.INT64:
145 | if (tensor.int64Data.length) {
146 | return new Int32Array(tensor.int32Data);
147 | }
148 | throw new Error(`'Int64Array' type not suppoert in JavaScript`);
149 | case onnx.TensorProto.DataType.FLOAT:
150 | return tensor.floatData.length > 0 ?
151 | new Float32Array(tensor.floatData) :
152 | new Float32Array(getArrayBuffer(tensor.rawData));
153 | case onnx.TensorProto.DataType.DOUBLE:
154 | return new Float32Array(new Float64Array(getArrayBuffer(tensor.rawData)));
155 | case onnx.TensorProto.DataType.UNDEFINED:
156 | default:
157 | throw new Error(`Cannot parse tensor '${tensor.dataType}'`);
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/demos/ILSVRC2012.js:
--------------------------------------------------------------------------------
1 | const IMAGENET_CLASSES = {
2 | 0: 'tench, Tinca tinca',
3 | 1: 'goldfish, Carassius auratus',
4 | 2: 'great white shark, white shark, man-eater, man-eating shark, ' +
5 | 'Carcharodon carcharias',
6 | 3: 'tiger shark, Galeocerdo cuvieri',
7 | 4: 'hammerhead, hammerhead shark',
8 | 5: 'electric ray, crampfish, numbfish, torpedo',
9 | 6: 'stingray',
10 | 7: 'cock',
11 | 8: 'hen',
12 | 9: 'ostrich, Struthio camelus',
13 | 10: 'brambling, Fringilla montifringilla',
14 | 11: 'goldfinch, Carduelis carduelis',
15 | 12: 'house finch, linnet, Carpodacus mexicanus',
16 | 13: 'junco, snowbird',
17 | 14: 'indigo bunting, indigo finch, indigo bird, Passerina cyanea',
18 | 15: 'robin, American robin, Turdus migratorius',
19 | 16: 'bulbul',
20 | 17: 'jay',
21 | 18: 'magpie',
22 | 19: 'chickadee',
23 | 20: 'water ouzel, dipper',
24 | 21: 'kite',
25 | 22: 'bald eagle, American eagle, Haliaeetus leucocephalus',
26 | 23: 'vulture',
27 | 24: 'great grey owl, great gray owl, Strix nebulosa',
28 | 25: 'European fire salamander, Salamandra salamandra',
29 | 26: 'common newt, Triturus vulgaris',
30 | 27: 'eft',
31 | 28: 'spotted salamander, Ambystoma maculatum',
32 | 29: 'axolotl, mud puppy, Ambystoma mexicanum',
33 | 30: 'bullfrog, Rana catesbeiana',
34 | 31: 'tree frog, tree-frog',
35 | 32: 'tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui',
36 | 33: 'loggerhead, loggerhead turtle, Caretta caretta',
37 | 34: 'leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea',
38 | 35: 'mud turtle',
39 | 36: 'terrapin',
40 | 37: 'box turtle, box tortoise',
41 | 38: 'banded gecko',
42 | 39: 'common iguana, iguana, Iguana iguana',
43 | 40: 'American chameleon, anole, Anolis carolinensis',
44 | 41: 'whiptail, whiptail lizard',
45 | 42: 'agama',
46 | 43: 'frilled lizard, Chlamydosaurus kingi',
47 | 44: 'alligator lizard',
48 | 45: 'Gila monster, Heloderma suspectum',
49 | 46: 'green lizard, Lacerta viridis',
50 | 47: 'African chameleon, Chamaeleo chamaeleon',
51 | 48: 'Komodo dragon, Komodo lizard, dragon lizard, giant lizard, ' +
52 | 'Varanus komodoensis',
53 | 49: 'African crocodile, Nile crocodile, Crocodylus niloticus',
54 | 50: 'American alligator, Alligator mississipiensis',
55 | 51: 'triceratops',
56 | 52: 'thunder snake, worm snake, Carphophis amoenus',
57 | 53: 'ringneck snake, ring-necked snake, ring snake',
58 | 54: 'hognose snake, puff adder, sand viper',
59 | 55: 'green snake, grass snake',
60 | 56: 'king snake, kingsnake',
61 | 57: 'garter snake, grass snake',
62 | 58: 'water snake',
63 | 59: 'vine snake',
64 | 60: 'night snake, Hypsiglena torquata',
65 | 61: 'boa constrictor, Constrictor constrictor',
66 | 62: 'rock python, rock snake, Python sebae',
67 | 63: 'Indian cobra, Naja naja',
68 | 64: 'green mamba',
69 | 65: 'sea snake',
70 | 66: 'horned viper, cerastes, sand viper, horned asp, Cerastes cornutus',
71 | 67: 'diamondback, diamondback rattlesnake, Crotalus adamanteus',
72 | 68: 'sidewinder, horned rattlesnake, Crotalus cerastes',
73 | 69: 'trilobite',
74 | 70: 'harvestman, daddy longlegs, Phalangium opilio',
75 | 71: 'scorpion',
76 | 72: 'black and gold garden spider, Argiope aurantia',
77 | 73: 'barn spider, Araneus cavaticus',
78 | 74: 'garden spider, Aranea diademata',
79 | 75: 'black widow, Latrodectus mactans',
80 | 76: 'tarantula',
81 | 77: 'wolf spider, hunting spider',
82 | 78: 'tick',
83 | 79: 'centipede',
84 | 80: 'black grouse',
85 | 81: 'ptarmigan',
86 | 82: 'ruffed grouse, partridge, Bonasa umbellus',
87 | 83: 'prairie chicken, prairie grouse, prairie fowl',
88 | 84: 'peacock',
89 | 85: 'quail',
90 | 86: 'partridge',
91 | 87: 'African grey, African gray, Psittacus erithacus',
92 | 88: 'macaw',
93 | 89: 'sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita',
94 | 90: 'lorikeet',
95 | 91: 'coucal',
96 | 92: 'bee eater',
97 | 93: 'hornbill',
98 | 94: 'hummingbird',
99 | 95: 'jacamar',
100 | 96: 'toucan',
101 | 97: 'drake',
102 | 98: 'red-breasted merganser, Mergus serrator',
103 | 99: 'goose',
104 | 100: 'black swan, Cygnus atratus',
105 | 101: 'tusker',
106 | 102: 'echidna, spiny anteater, anteater',
107 | 103: 'platypus, duckbill, duckbilled platypus, duck-billed platypus, ' +
108 | 'Ornithorhynchus anatinus',
109 | 104: 'wallaby, brush kangaroo',
110 | 105: 'koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus',
111 | 106: 'wombat',
112 | 107: 'jelly fish',
113 | 108: 'sea anemone, anemone',
114 | 109: 'brain coral',
115 | 110: 'flatworm, platyhelminth',
116 | 111: 'nematode, nematode worm, roundworm',
117 | 112: 'conch',
118 | 113: 'snail',
119 | 114: 'slug',
120 | 115: 'sea slug, nudibranch',
121 | 116: 'chiton, coat-of-mail shell, sea cradle, polyplacophore',
122 | 117: 'chambered nautilus, pearly nautilus, nautilus',
123 | 118: 'Dungeness crab, Cancer magister',
124 | 119: 'rock crab, Cancer irroratus',
125 | 120: 'fiddler crab',
126 | 121: 'king crab, Alaska crab, Alaskan king crab, Alaska king crab, ' +
127 | 'Paralithodes camtschatica',
128 | 122: 'American lobster, Northern lobster, Maine lobster, Homarus americanus',
129 | 123: 'spiny lobster, langouste, rock lobster, crawfish, crayfish, sea ' +
130 | 'crawfish',
131 | 124: 'crayfish, crawfish, crawdad, crawdaddy',
132 | 125: 'hermit crab',
133 | 126: 'isopod',
134 | 127: 'white stork, Ciconia ciconia',
135 | 128: 'black stork, Ciconia nigra',
136 | 129: 'spoonbill',
137 | 130: 'flamingo',
138 | 131: 'little blue heron, Egretta caerulea',
139 | 132: 'American egret, great white heron, Egretta albus',
140 | 133: 'bittern',
141 | 134: 'crane',
142 | 135: 'limpkin, Aramus pictus',
143 | 136: 'European gallinule, Porphyrio porphyrio',
144 | 137: 'American coot, marsh hen, mud hen, water hen, Fulica americana',
145 | 138: 'bustard',
146 | 139: 'ruddy turnstone, Arenaria interpres',
147 | 140: 'red-backed sandpiper, dunlin, Erolia alpina',
148 | 141: 'redshank, Tringa totanus',
149 | 142: 'dowitcher',
150 | 143: 'oystercatcher, oyster catcher',
151 | 144: 'pelican',
152 | 145: 'king penguin, Aptenodytes patagonica',
153 | 146: 'albatross, mollymawk',
154 | 147: 'grey whale, gray whale, devilfish, Eschrichtius gibbosus, ' +
155 | 'Eschrichtius robustus',
156 | 148: 'killer whale, killer, orca, grampus, sea wolf, Orcinus orca',
157 | 149: 'dugong, Dugong dugon',
158 | 150: 'sea lion',
159 | 151: 'Chihuahua',
160 | 152: 'Japanese spaniel',
161 | 153: 'Maltese dog, Maltese terrier, Maltese',
162 | 154: 'Pekinese, Pekingese, Peke',
163 | 155: 'Shih-Tzu',
164 | 156: 'Blenheim spaniel',
165 | 157: 'papillon',
166 | 158: 'toy terrier',
167 | 159: 'Rhodesian ridgeback',
168 | 160: 'Afghan hound, Afghan',
169 | 161: 'basset, basset hound',
170 | 162: 'beagle',
171 | 163: 'bloodhound, sleuthhound',
172 | 164: 'bluetick',
173 | 165: 'black-and-tan coonhound',
174 | 166: 'Walker hound, Walker foxhound',
175 | 167: 'English foxhound',
176 | 168: 'redbone',
177 | 169: 'borzoi, Russian wolfhound',
178 | 170: 'Irish wolfhound',
179 | 171: 'Italian greyhound',
180 | 172: 'whippet',
181 | 173: 'Ibizan hound, Ibizan Podenco',
182 | 174: 'Norwegian elkhound, elkhound',
183 | 175: 'otterhound, otter hound',
184 | 176: 'Saluki, gazelle hound',
185 | 177: 'Scottish deerhound, deerhound',
186 | 178: 'Weimaraner',
187 | 179: 'Staffordshire bullterrier, Staffordshire bull terrier',
188 | 180: 'American Staffordshire terrier, Staffordshire terrier, American pit ' +
189 | 'bull terrier, pit bull terrier',
190 | 181: 'Bedlington terrier',
191 | 182: 'Border terrier',
192 | 183: 'Kerry blue terrier',
193 | 184: 'Irish terrier',
194 | 185: 'Norfolk terrier',
195 | 186: 'Norwich terrier',
196 | 187: 'Yorkshire terrier',
197 | 188: 'wire-haired fox terrier',
198 | 189: 'Lakeland terrier',
199 | 190: 'Sealyham terrier, Sealyham',
200 | 191: 'Airedale, Airedale terrier',
201 | 192: 'cairn, cairn terrier',
202 | 193: 'Australian terrier',
203 | 194: 'Dandie Dinmont, Dandie Dinmont terrier',
204 | 195: 'Boston bull, Boston terrier',
205 | 196: 'miniature schnauzer',
206 | 197: 'giant schnauzer',
207 | 198: 'standard schnauzer',
208 | 199: 'Scotch terrier, Scottish terrier, Scottie',
209 | 200: 'Tibetan terrier, chrysanthemum dog',
210 | 201: 'silky terrier, Sydney silky',
211 | 202: 'soft-coated wheaten terrier',
212 | 203: 'West Highland white terrier',
213 | 204: 'Lhasa, Lhasa apso',
214 | 205: 'flat-coated retriever',
215 | 206: 'curly-coated retriever',
216 | 207: 'golden retriever',
217 | 208: 'Labrador retriever',
218 | 209: 'Chesapeake Bay retriever',
219 | 210: 'German short-haired pointer',
220 | 211: 'vizsla, Hungarian pointer',
221 | 212: 'English setter',
222 | 213: 'Irish setter, red setter',
223 | 214: 'Gordon setter',
224 | 215: 'Brittany spaniel',
225 | 216: 'clumber, clumber spaniel',
226 | 217: 'English springer, English springer spaniel',
227 | 218: 'Welsh springer spaniel',
228 | 219: 'cocker spaniel, English cocker spaniel, cocker',
229 | 220: 'Sussex spaniel',
230 | 221: 'Irish water spaniel',
231 | 222: 'kuvasz',
232 | 223: 'schipperke',
233 | 224: 'groenendael',
234 | 225: 'malinois',
235 | 226: 'briard',
236 | 227: 'kelpie',
237 | 228: 'komondor',
238 | 229: 'Old English sheepdog, bobtail',
239 | 230: 'Shetland sheepdog, Shetland sheep dog, Shetland',
240 | 231: 'collie',
241 | 232: 'Border collie',
242 | 233: 'Bouvier des Flandres, Bouviers des Flandres',
243 | 234: 'Rottweiler',
244 | 235: 'German shepherd, German shepherd dog, German police dog, alsatian',
245 | 236: 'Doberman, Doberman pinscher',
246 | 237: 'miniature pinscher',
247 | 238: 'Greater Swiss Mountain dog',
248 | 239: 'Bernese mountain dog',
249 | 240: 'Appenzeller',
250 | 241: 'EntleBucher',
251 | 242: 'boxer',
252 | 243: 'bull mastiff',
253 | 244: 'Tibetan mastiff',
254 | 245: 'French bulldog',
255 | 246: 'Great Dane',
256 | 247: 'Saint Bernard, St Bernard',
257 | 248: 'Eskimo dog, husky',
258 | 249: 'malamute, malemute, Alaskan malamute',
259 | 250: 'Siberian husky',
260 | 251: 'dalmatian, coach dog, carriage dog',
261 | 252: 'affenpinscher, monkey pinscher, monkey dog',
262 | 253: 'basenji',
263 | 254: 'pug, pug-dog',
264 | 255: 'Leonberg',
265 | 256: 'Newfoundland, Newfoundland dog',
266 | 257: 'Great Pyrenees',
267 | 258: 'Samoyed, Samoyede',
268 | 259: 'Pomeranian',
269 | 260: 'chow, chow chow',
270 | 261: 'keeshond',
271 | 262: 'Brabancon griffon',
272 | 263: 'Pembroke, Pembroke Welsh corgi',
273 | 264: 'Cardigan, Cardigan Welsh corgi',
274 | 265: 'toy poodle',
275 | 266: 'miniature poodle',
276 | 267: 'standard poodle',
277 | 268: 'Mexican hairless',
278 | 269: 'timber wolf, grey wolf, gray wolf, Canis lupus',
279 | 270: 'white wolf, Arctic wolf, Canis lupus tundrarum',
280 | 271: 'red wolf, maned wolf, Canis rufus, Canis niger',
281 | 272: 'coyote, prairie wolf, brush wolf, Canis latrans',
282 | 273: 'dingo, warrigal, warragal, Canis dingo',
283 | 274: 'dhole, Cuon alpinus',
284 | 275: 'African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus',
285 | 276: 'hyena, hyaena',
286 | 277: 'red fox, Vulpes vulpes',
287 | 278: 'kit fox, Vulpes macrotis',
288 | 279: 'Arctic fox, white fox, Alopex lagopus',
289 | 280: 'grey fox, gray fox, Urocyon cinereoargenteus',
290 | 281: 'tabby, tabby cat',
291 | 282: 'tiger cat',
292 | 283: 'Persian cat',
293 | 284: 'Siamese cat, Siamese',
294 | 285: 'Egyptian cat',
295 | 286: 'cougar, puma, catamount, mountain lion, painter, panther, ' +
296 | 'Felis concolor',
297 | 287: 'lynx, catamount',
298 | 288: 'leopard, Panthera pardus',
299 | 289: 'snow leopard, ounce, Panthera uncia',
300 | 290: 'jaguar, panther, Panthera onca, Felis onca',
301 | 291: 'lion, king of beasts, Panthera leo',
302 | 292: 'tiger, Panthera tigris',
303 | 293: 'cheetah, chetah, Acinonyx jubatus',
304 | 294: 'brown bear, bruin, Ursus arctos',
305 | 295: 'American black bear, black bear, Ursus americanus, Euarctos ' +
306 | 'americanus',
307 | 296: 'ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus',
308 | 297: 'sloth bear, Melursus ursinus, Ursus ursinus',
309 | 298: 'mongoose',
310 | 299: 'meerkat, mierkat',
311 | 300: 'tiger beetle',
312 | 301: 'ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle',
313 | 302: 'ground beetle, carabid beetle',
314 | 303: 'long-horned beetle, longicorn, longicorn beetle',
315 | 304: 'leaf beetle, chrysomelid',
316 | 305: 'dung beetle',
317 | 306: 'rhinoceros beetle',
318 | 307: 'weevil',
319 | 308: 'fly',
320 | 309: 'bee',
321 | 310: 'ant, emmet, pismire',
322 | 311: 'grasshopper, hopper',
323 | 312: 'cricket',
324 | 313: 'walking stick, walkingstick, stick insect',
325 | 314: 'cockroach, roach',
326 | 315: 'mantis, mantid',
327 | 316: 'cicada, cicala',
328 | 317: 'leafhopper',
329 | 318: 'lacewing, lacewing fly',
330 | 319: 'dragonfly, darning needle, devil\'s darning needle, sewing needle, ' +
331 | 'snake feeder, snake doctor, mosquito hawk, skeeter hawk',
332 | 320: 'damselfly',
333 | 321: 'admiral',
334 | 322: 'ringlet, ringlet butterfly',
335 | 323: 'monarch, monarch butterfly, milkweed butterfly, Danaus plexippus',
336 | 324: 'cabbage butterfly',
337 | 325: 'sulphur butterfly, sulfur butterfly',
338 | 326: 'lycaenid, lycaenid butterfly',
339 | 327: 'starfish, sea star',
340 | 328: 'sea urchin',
341 | 329: 'sea cucumber, holothurian',
342 | 330: 'wood rabbit, cottontail, cottontail rabbit',
343 | 331: 'hare',
344 | 332: 'Angora, Angora rabbit',
345 | 333: 'hamster',
346 | 334: 'porcupine, hedgehog',
347 | 335: 'fox squirrel, eastern fox squirrel, Sciurus niger',
348 | 336: 'marmot',
349 | 337: 'beaver',
350 | 338: 'guinea pig, Cavia cobaya',
351 | 339: 'sorrel',
352 | 340: 'zebra',
353 | 341: 'hog, pig, grunter, squealer, Sus scrofa',
354 | 342: 'wild boar, boar, Sus scrofa',
355 | 343: 'warthog',
356 | 344: 'hippopotamus, hippo, river horse, Hippopotamus amphibius',
357 | 345: 'ox',
358 | 346: 'water buffalo, water ox, Asiatic buffalo, Bubalus bubalis',
359 | 347: 'bison',
360 | 348: 'ram, tup',
361 | 349: 'bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky ' +
362 | 'Mountain sheep, Ovis canadensis',
363 | 350: 'ibex, Capra ibex',
364 | 351: 'hartebeest',
365 | 352: 'impala, Aepyceros melampus',
366 | 353: 'gazelle',
367 | 354: 'Arabian camel, dromedary, Camelus dromedarius',
368 | 355: 'llama',
369 | 356: 'weasel',
370 | 357: 'mink',
371 | 358: 'polecat, fitch, foulmart, foumart, Mustela putorius',
372 | 359: 'black-footed ferret, ferret, Mustela nigripes',
373 | 360: 'otter',
374 | 361: 'skunk, polecat, wood pussy',
375 | 362: 'badger',
376 | 363: 'armadillo',
377 | 364: 'three-toed sloth, ai, Bradypus tridactylus',
378 | 365: 'orangutan, orang, orangutang, Pongo pygmaeus',
379 | 366: 'gorilla, Gorilla gorilla',
380 | 367: 'chimpanzee, chimp, Pan troglodytes',
381 | 368: 'gibbon, Hylobates lar',
382 | 369: 'siamang, Hylobates syndactylus, Symphalangus syndactylus',
383 | 370: 'guenon, guenon monkey',
384 | 371: 'patas, hussar monkey, Erythrocebus patas',
385 | 372: 'baboon',
386 | 373: 'macaque',
387 | 374: 'langur',
388 | 375: 'colobus, colobus monkey',
389 | 376: 'proboscis monkey, Nasalis larvatus',
390 | 377: 'marmoset',
391 | 378: 'capuchin, ringtail, Cebus capucinus',
392 | 379: 'howler monkey, howler',
393 | 380: 'titi, titi monkey',
394 | 381: 'spider monkey, Ateles geoffroyi',
395 | 382: 'squirrel monkey, Saimiri sciureus',
396 | 383: 'Madagascar cat, ring-tailed lemur, Lemur catta',
397 | 384: 'indri, indris, Indri indri, Indri brevicaudatus',
398 | 385: 'Indian elephant, Elephas maximus',
399 | 386: 'African elephant, Loxodonta africana',
400 | 387: 'lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens',
401 | 388: 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca',
402 | 389: 'barracouta, snoek',
403 | 390: 'eel',
404 | 391: 'coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus ' +
405 | 'kisutch',
406 | 392: 'rock beauty, Holocanthus tricolor',
407 | 393: 'anemone fish',
408 | 394: 'sturgeon',
409 | 395: 'gar, garfish, garpike, billfish, Lepisosteus osseus',
410 | 396: 'lionfish',
411 | 397: 'puffer, pufferfish, blowfish, globefish',
412 | 398: 'abacus',
413 | 399: 'abaya',
414 | 400: 'academic gown, academic robe, judge\'s robe',
415 | 401: 'accordion, piano accordion, squeeze box',
416 | 402: 'acoustic guitar',
417 | 403: 'aircraft carrier, carrier, flattop, attack aircraft carrier',
418 | 404: 'airliner',
419 | 405: 'airship, dirigible',
420 | 406: 'altar',
421 | 407: 'ambulance',
422 | 408: 'amphibian, amphibious vehicle',
423 | 409: 'analog clock',
424 | 410: 'apiary, bee house',
425 | 411: 'apron',
426 | 412: 'ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, ' +
427 | 'dustbin, trash barrel, trash bin',
428 | 413: 'assault rifle, assault gun',
429 | 414: 'backpack, back pack, knapsack, packsack, rucksack, haversack',
430 | 415: 'bakery, bakeshop, bakehouse',
431 | 416: 'balance beam, beam',
432 | 417: 'balloon',
433 | 418: 'ballpoint, ballpoint pen, ballpen, Biro',
434 | 419: 'Band Aid',
435 | 420: 'banjo',
436 | 421: 'bannister, banister, balustrade, balusters, handrail',
437 | 422: 'barbell',
438 | 423: 'barber chair',
439 | 424: 'barbershop',
440 | 425: 'barn',
441 | 426: 'barometer',
442 | 427: 'barrel, cask',
443 | 428: 'barrow, garden cart, lawn cart, wheelbarrow',
444 | 429: 'baseball',
445 | 430: 'basketball',
446 | 431: 'bassinet',
447 | 432: 'bassoon',
448 | 433: 'bathing cap, swimming cap',
449 | 434: 'bath towel',
450 | 435: 'bathtub, bathing tub, bath, tub',
451 | 436: 'beach wagon, station wagon, wagon, estate car, beach waggon, station ' +
452 | 'waggon, waggon',
453 | 437: 'beacon, lighthouse, beacon light, pharos',
454 | 438: 'beaker',
455 | 439: 'bearskin, busby, shako',
456 | 440: 'beer bottle',
457 | 441: 'beer glass',
458 | 442: 'bell cote, bell cot',
459 | 443: 'bib',
460 | 444: 'bicycle-built-for-two, tandem bicycle, tandem',
461 | 445: 'bikini, two-piece',
462 | 446: 'binder, ring-binder',
463 | 447: 'binoculars, field glasses, opera glasses',
464 | 448: 'birdhouse',
465 | 449: 'boathouse',
466 | 450: 'bobsled, bobsleigh, bob',
467 | 451: 'bolo tie, bolo, bola tie, bola',
468 | 452: 'bonnet, poke bonnet',
469 | 453: 'bookcase',
470 | 454: 'bookshop, bookstore, bookstall',
471 | 455: 'bottlecap',
472 | 456: 'bow',
473 | 457: 'bow tie, bow-tie, bowtie',
474 | 458: 'brass, memorial tablet, plaque',
475 | 459: 'brassiere, bra, bandeau',
476 | 460: 'breakwater, groin, groyne, mole, bulwark, seawall, jetty',
477 | 461: 'breastplate, aegis, egis',
478 | 462: 'broom',
479 | 463: 'bucket, pail',
480 | 464: 'buckle',
481 | 465: 'bulletproof vest',
482 | 466: 'bullet train, bullet',
483 | 467: 'butcher shop, meat market',
484 | 468: 'cab, hack, taxi, taxicab',
485 | 469: 'caldron, cauldron',
486 | 470: 'candle, taper, wax light',
487 | 471: 'cannon',
488 | 472: 'canoe',
489 | 473: 'can opener, tin opener',
490 | 474: 'cardigan',
491 | 475: 'car mirror',
492 | 476: 'carousel, carrousel, merry-go-round, roundabout, whirligig',
493 | 477: 'carpenter\'s kit, tool kit',
494 | 478: 'carton',
495 | 479: 'car wheel',
496 | 480: 'cash machine, cash dispenser, automated teller machine, automatic ' +
497 | 'teller machine, automated teller, automatic teller, ATM',
498 | 481: 'cassette',
499 | 482: 'cassette player',
500 | 483: 'castle',
501 | 484: 'catamaran',
502 | 485: 'CD player',
503 | 486: 'cello, violoncello',
504 | 487: 'cellular telephone, cellular phone, cellphone, cell, mobile phone',
505 | 488: 'chain',
506 | 489: 'chainlink fence',
507 | 490: 'chain mail, ring mail, mail, chain armor, chain armour, ring armor, ' +
508 | 'ring armour',
509 | 491: 'chain saw, chainsaw',
510 | 492: 'chest',
511 | 493: 'chiffonier, commode',
512 | 494: 'chime, bell, gong',
513 | 495: 'china cabinet, china closet',
514 | 496: 'Christmas stocking',
515 | 497: 'church, church building',
516 | 498: 'cinema, movie theater, movie theatre, movie house, picture palace',
517 | 499: 'cleaver, meat cleaver, chopper',
518 | 500: 'cliff dwelling',
519 | 501: 'cloak',
520 | 502: 'clog, geta, patten, sabot',
521 | 503: 'cocktail shaker',
522 | 504: 'coffee mug',
523 | 505: 'coffeepot',
524 | 506: 'coil, spiral, volute, whorl, helix',
525 | 507: 'combination lock',
526 | 508: 'computer keyboard, keypad',
527 | 509: 'confectionery, confectionary, candy store',
528 | 510: 'container ship, containership, container vessel',
529 | 511: 'convertible',
530 | 512: 'corkscrew, bottle screw',
531 | 513: 'cornet, horn, trumpet, trump',
532 | 514: 'cowboy boot',
533 | 515: 'cowboy hat, ten-gallon hat',
534 | 516: 'cradle',
535 | 517: 'crane',
536 | 518: 'crash helmet',
537 | 519: 'crate',
538 | 520: 'crib, cot',
539 | 521: 'Crock Pot',
540 | 522: 'croquet ball',
541 | 523: 'crutch',
542 | 524: 'cuirass',
543 | 525: 'dam, dike, dyke',
544 | 526: 'desk',
545 | 527: 'desktop computer',
546 | 528: 'dial telephone, dial phone',
547 | 529: 'diaper, nappy, napkin',
548 | 530: 'digital clock',
549 | 531: 'digital watch',
550 | 532: 'dining table, board',
551 | 533: 'dishrag, dishcloth',
552 | 534: 'dishwasher, dish washer, dishwashing machine',
553 | 535: 'disk brake, disc brake',
554 | 536: 'dock, dockage, docking facility',
555 | 537: 'dogsled, dog sled, dog sleigh',
556 | 538: 'dome',
557 | 539: 'doormat, welcome mat',
558 | 540: 'drilling platform, offshore rig',
559 | 541: 'drum, membranophone, tympan',
560 | 542: 'drumstick',
561 | 543: 'dumbbell',
562 | 544: 'Dutch oven',
563 | 545: 'electric fan, blower',
564 | 546: 'electric guitar',
565 | 547: 'electric locomotive',
566 | 548: 'entertainment center',
567 | 549: 'envelope',
568 | 550: 'espresso maker',
569 | 551: 'face powder',
570 | 552: 'feather boa, boa',
571 | 553: 'file, file cabinet, filing cabinet',
572 | 554: 'fireboat',
573 | 555: 'fire engine, fire truck',
574 | 556: 'fire screen, fireguard',
575 | 557: 'flagpole, flagstaff',
576 | 558: 'flute, transverse flute',
577 | 559: 'folding chair',
578 | 560: 'football helmet',
579 | 561: 'forklift',
580 | 562: 'fountain',
581 | 563: 'fountain pen',
582 | 564: 'four-poster',
583 | 565: 'freight car',
584 | 566: 'French horn, horn',
585 | 567: 'frying pan, frypan, skillet',
586 | 568: 'fur coat',
587 | 569: 'garbage truck, dustcart',
588 | 570: 'gasmask, respirator, gas helmet',
589 | 571: 'gas pump, gasoline pump, petrol pump, island dispenser',
590 | 572: 'goblet',
591 | 573: 'go-kart',
592 | 574: 'golf ball',
593 | 575: 'golfcart, golf cart',
594 | 576: 'gondola',
595 | 577: 'gong, tam-tam',
596 | 578: 'gown',
597 | 579: 'grand piano, grand',
598 | 580: 'greenhouse, nursery, glasshouse',
599 | 581: 'grille, radiator grille',
600 | 582: 'grocery store, grocery, food market, market',
601 | 583: 'guillotine',
602 | 584: 'hair slide',
603 | 585: 'hair spray',
604 | 586: 'half track',
605 | 587: 'hammer',
606 | 588: 'hamper',
607 | 589: 'hand blower, blow dryer, blow drier, hair dryer, hair drier',
608 | 590: 'hand-held computer, hand-held microcomputer',
609 | 591: 'handkerchief, hankie, hanky, hankey',
610 | 592: 'hard disc, hard disk, fixed disk',
611 | 593: 'harmonica, mouth organ, harp, mouth harp',
612 | 594: 'harp',
613 | 595: 'harvester, reaper',
614 | 596: 'hatchet',
615 | 597: 'holster',
616 | 598: 'home theater, home theatre',
617 | 599: 'honeycomb',
618 | 600: 'hook, claw',
619 | 601: 'hoopskirt, crinoline',
620 | 602: 'horizontal bar, high bar',
621 | 603: 'horse cart, horse-cart',
622 | 604: 'hourglass',
623 | 605: 'iPod',
624 | 606: 'iron, smoothing iron',
625 | 607: 'jack-o\'-lantern',
626 | 608: 'jean, blue jean, denim',
627 | 609: 'jeep, landrover',
628 | 610: 'jersey, T-shirt, tee shirt',
629 | 611: 'jigsaw puzzle',
630 | 612: 'jinrikisha, ricksha, rickshaw',
631 | 613: 'joystick',
632 | 614: 'kimono',
633 | 615: 'knee pad',
634 | 616: 'knot',
635 | 617: 'lab coat, laboratory coat',
636 | 618: 'ladle',
637 | 619: 'lampshade, lamp shade',
638 | 620: 'laptop, laptop computer',
639 | 621: 'lawn mower, mower',
640 | 622: 'lens cap, lens cover',
641 | 623: 'letter opener, paper knife, paperknife',
642 | 624: 'library',
643 | 625: 'lifeboat',
644 | 626: 'lighter, light, igniter, ignitor',
645 | 627: 'limousine, limo',
646 | 628: 'liner, ocean liner',
647 | 629: 'lipstick, lip rouge',
648 | 630: 'Loafer',
649 | 631: 'lotion',
650 | 632: 'loudspeaker, speaker, speaker unit, loudspeaker system, speaker ' +
651 | 'system',
652 | 633: 'loupe, jeweler\'s loupe',
653 | 634: 'lumbermill, sawmill',
654 | 635: 'magnetic compass',
655 | 636: 'mailbag, postbag',
656 | 637: 'mailbox, letter box',
657 | 638: 'maillot',
658 | 639: 'maillot, tank suit',
659 | 640: 'manhole cover',
660 | 641: 'maraca',
661 | 642: 'marimba, xylophone',
662 | 643: 'mask',
663 | 644: 'matchstick',
664 | 645: 'maypole',
665 | 646: 'maze, labyrinth',
666 | 647: 'measuring cup',
667 | 648: 'medicine chest, medicine cabinet',
668 | 649: 'megalith, megalithic structure',
669 | 650: 'microphone, mike',
670 | 651: 'microwave, microwave oven',
671 | 652: 'military uniform',
672 | 653: 'milk can',
673 | 654: 'minibus',
674 | 655: 'miniskirt, mini',
675 | 656: 'minivan',
676 | 657: 'missile',
677 | 658: 'mitten',
678 | 659: 'mixing bowl',
679 | 660: 'mobile home, manufactured home',
680 | 661: 'Model T',
681 | 662: 'modem',
682 | 663: 'monastery',
683 | 664: 'monitor',
684 | 665: 'moped',
685 | 666: 'mortar',
686 | 667: 'mortarboard',
687 | 668: 'mosque',
688 | 669: 'mosquito net',
689 | 670: 'motor scooter, scooter',
690 | 671: 'mountain bike, all-terrain bike, off-roader',
691 | 672: 'mountain tent',
692 | 673: 'mouse, computer mouse',
693 | 674: 'mousetrap',
694 | 675: 'moving van',
695 | 676: 'muzzle',
696 | 677: 'nail',
697 | 678: 'neck brace',
698 | 679: 'necklace',
699 | 680: 'nipple',
700 | 681: 'notebook, notebook computer',
701 | 682: 'obelisk',
702 | 683: 'oboe, hautboy, hautbois',
703 | 684: 'ocarina, sweet potato',
704 | 685: 'odometer, hodometer, mileometer, milometer',
705 | 686: 'oil filter',
706 | 687: 'organ, pipe organ',
707 | 688: 'oscilloscope, scope, cathode-ray oscilloscope, CRO',
708 | 689: 'overskirt',
709 | 690: 'oxcart',
710 | 691: 'oxygen mask',
711 | 692: 'packet',
712 | 693: 'paddle, boat paddle',
713 | 694: 'paddlewheel, paddle wheel',
714 | 695: 'padlock',
715 | 696: 'paintbrush',
716 | 697: 'pajama, pyjama, pj\'s, jammies',
717 | 698: 'palace',
718 | 699: 'panpipe, pandean pipe, syrinx',
719 | 700: 'paper towel',
720 | 701: 'parachute, chute',
721 | 702: 'parallel bars, bars',
722 | 703: 'park bench',
723 | 704: 'parking meter',
724 | 705: 'passenger car, coach, carriage',
725 | 706: 'patio, terrace',
726 | 707: 'pay-phone, pay-station',
727 | 708: 'pedestal, plinth, footstall',
728 | 709: 'pencil box, pencil case',
729 | 710: 'pencil sharpener',
730 | 711: 'perfume, essence',
731 | 712: 'Petri dish',
732 | 713: 'photocopier',
733 | 714: 'pick, plectrum, plectron',
734 | 715: 'pickelhaube',
735 | 716: 'picket fence, paling',
736 | 717: 'pickup, pickup truck',
737 | 718: 'pier',
738 | 719: 'piggy bank, penny bank',
739 | 720: 'pill bottle',
740 | 721: 'pillow',
741 | 722: 'ping-pong ball',
742 | 723: 'pinwheel',
743 | 724: 'pirate, pirate ship',
744 | 725: 'pitcher, ewer',
745 | 726: 'plane, carpenter\'s plane, woodworking plane',
746 | 727: 'planetarium',
747 | 728: 'plastic bag',
748 | 729: 'plate rack',
749 | 730: 'plow, plough',
750 | 731: 'plunger, plumber\'s helper',
751 | 732: 'Polaroid camera, Polaroid Land camera',
752 | 733: 'pole',
753 | 734: 'police van, police wagon, paddy wagon, patrol wagon, wagon, black ' +
754 | 'Maria',
755 | 735: 'poncho',
756 | 736: 'pool table, billiard table, snooker table',
757 | 737: 'pop bottle, soda bottle',
758 | 738: 'pot, flowerpot',
759 | 739: 'potter\'s wheel',
760 | 740: 'power drill',
761 | 741: 'prayer rug, prayer mat',
762 | 742: 'printer',
763 | 743: 'prison, prison house',
764 | 744: 'projectile, missile',
765 | 745: 'projector',
766 | 746: 'puck, hockey puck',
767 | 747: 'punching bag, punch bag, punching ball, punchball',
768 | 748: 'purse',
769 | 749: 'quill, quill pen',
770 | 750: 'quilt, comforter, comfort, puff',
771 | 751: 'racer, race car, racing car',
772 | 752: 'racket, racquet',
773 | 753: 'radiator',
774 | 754: 'radio, wireless',
775 | 755: 'radio telescope, radio reflector',
776 | 756: 'rain barrel',
777 | 757: 'recreational vehicle, RV, R.V.',
778 | 758: 'reel',
779 | 759: 'reflex camera',
780 | 760: 'refrigerator, icebox',
781 | 761: 'remote control, remote',
782 | 762: 'restaurant, eating house, eating place, eatery',
783 | 763: 'revolver, six-gun, six-shooter',
784 | 764: 'rifle',
785 | 765: 'rocking chair, rocker',
786 | 766: 'rotisserie',
787 | 767: 'rubber eraser, rubber, pencil eraser',
788 | 768: 'rugby ball',
789 | 769: 'rule, ruler',
790 | 770: 'running shoe',
791 | 771: 'safe',
792 | 772: 'safety pin',
793 | 773: 'saltshaker, salt shaker',
794 | 774: 'sandal',
795 | 775: 'sarong',
796 | 776: 'sax, saxophone',
797 | 777: 'scabbard',
798 | 778: 'scale, weighing machine',
799 | 779: 'school bus',
800 | 780: 'schooner',
801 | 781: 'scoreboard',
802 | 782: 'screen, CRT screen',
803 | 783: 'screw',
804 | 784: 'screwdriver',
805 | 785: 'seat belt, seatbelt',
806 | 786: 'sewing machine',
807 | 787: 'shield, buckler',
808 | 788: 'shoe shop, shoe-shop, shoe store',
809 | 789: 'shoji',
810 | 790: 'shopping basket',
811 | 791: 'shopping cart',
812 | 792: 'shovel',
813 | 793: 'shower cap',
814 | 794: 'shower curtain',
815 | 795: 'ski',
816 | 796: 'ski mask',
817 | 797: 'sleeping bag',
818 | 798: 'slide rule, slipstick',
819 | 799: 'sliding door',
820 | 800: 'slot, one-armed bandit',
821 | 801: 'snorkel',
822 | 802: 'snowmobile',
823 | 803: 'snowplow, snowplough',
824 | 804: 'soap dispenser',
825 | 805: 'soccer ball',
826 | 806: 'sock',
827 | 807: 'solar dish, solar collector, solar furnace',
828 | 808: 'sombrero',
829 | 809: 'soup bowl',
830 | 810: 'space bar',
831 | 811: 'space heater',
832 | 812: 'space shuttle',
833 | 813: 'spatula',
834 | 814: 'speedboat',
835 | 815: 'spider web, spider\'s web',
836 | 816: 'spindle',
837 | 817: 'sports car, sport car',
838 | 818: 'spotlight, spot',
839 | 819: 'stage',
840 | 820: 'steam locomotive',
841 | 821: 'steel arch bridge',
842 | 822: 'steel drum',
843 | 823: 'stethoscope',
844 | 824: 'stole',
845 | 825: 'stone wall',
846 | 826: 'stopwatch, stop watch',
847 | 827: 'stove',
848 | 828: 'strainer',
849 | 829: 'streetcar, tram, tramcar, trolley, trolley car',
850 | 830: 'stretcher',
851 | 831: 'studio couch, day bed',
852 | 832: 'stupa, tope',
853 | 833: 'submarine, pigboat, sub, U-boat',
854 | 834: 'suit, suit of clothes',
855 | 835: 'sundial',
856 | 836: 'sunglass',
857 | 837: 'sunglasses, dark glasses, shades',
858 | 838: 'sunscreen, sunblock, sun blocker',
859 | 839: 'suspension bridge',
860 | 840: 'swab, swob, mop',
861 | 841: 'sweatshirt',
862 | 842: 'swimming trunks, bathing trunks',
863 | 843: 'swing',
864 | 844: 'switch, electric switch, electrical switch',
865 | 845: 'syringe',
866 | 846: 'table lamp',
867 | 847: 'tank, army tank, armored combat vehicle, armoured combat vehicle',
868 | 848: 'tape player',
869 | 849: 'teapot',
870 | 850: 'teddy, teddy bear',
871 | 851: 'television, television system',
872 | 852: 'tennis ball',
873 | 853: 'thatch, thatched roof',
874 | 854: 'theater curtain, theatre curtain',
875 | 855: 'thimble',
876 | 856: 'thresher, thrasher, threshing machine',
877 | 857: 'throne',
878 | 858: 'tile roof',
879 | 859: 'toaster',
880 | 860: 'tobacco shop, tobacconist shop, tobacconist',
881 | 861: 'toilet seat',
882 | 862: 'torch',
883 | 863: 'totem pole',
884 | 864: 'tow truck, tow car, wrecker',
885 | 865: 'toyshop',
886 | 866: 'tractor',
887 | 867: 'trailer truck, tractor trailer, trucking rig, rig, articulated ' +
888 | 'lorry, semi',
889 | 868: 'tray',
890 | 869: 'trench coat',
891 | 870: 'tricycle, trike, velocipede',
892 | 871: 'trimaran',
893 | 872: 'tripod',
894 | 873: 'triumphal arch',
895 | 874: 'trolleybus, trolley coach, trackless trolley',
896 | 875: 'trombone',
897 | 876: 'tub, vat',
898 | 877: 'turnstile',
899 | 878: 'typewriter keyboard',
900 | 879: 'umbrella',
901 | 880: 'unicycle, monocycle',
902 | 881: 'upright, upright piano',
903 | 882: 'vacuum, vacuum cleaner',
904 | 883: 'vase',
905 | 884: 'vault',
906 | 885: 'velvet',
907 | 886: 'vending machine',
908 | 887: 'vestment',
909 | 888: 'viaduct',
910 | 889: 'violin, fiddle',
911 | 890: 'volleyball',
912 | 891: 'waffle iron',
913 | 892: 'wall clock',
914 | 893: 'wallet, billfold, notecase, pocketbook',
915 | 894: 'wardrobe, closet, press',
916 | 895: 'warplane, military plane',
917 | 896: 'washbasin, handbasin, washbowl, lavabo, wash-hand basin',
918 | 897: 'washer, automatic washer, washing machine',
919 | 898: 'water bottle',
920 | 899: 'water jug',
921 | 900: 'water tower',
922 | 901: 'whiskey jug',
923 | 902: 'whistle',
924 | 903: 'wig',
925 | 904: 'window screen',
926 | 905: 'window shade',
927 | 906: 'Windsor tie',
928 | 907: 'wine bottle',
929 | 908: 'wing',
930 | 909: 'wok',
931 | 910: 'wooden spoon',
932 | 911: 'wool, woolen, woollen',
933 | 912: 'worm fence, snake fence, snake-rail fence, Virginia fence',
934 | 913: 'wreck',
935 | 914: 'yawl',
936 | 915: 'yurt',
937 | 916: 'web site, website, internet site, site',
938 | 917: 'comic book',
939 | 918: 'crossword puzzle, crossword',
940 | 919: 'street sign',
941 | 920: 'traffic light, traffic signal, stoplight',
942 | 921: 'book jacket, dust cover, dust jacket, dust wrapper',
943 | 922: 'menu',
944 | 923: 'plate',
945 | 924: 'guacamole',
946 | 925: 'consomme',
947 | 926: 'hot pot, hotpot',
948 | 927: 'trifle',
949 | 928: 'ice cream, icecream',
950 | 929: 'ice lolly, lolly, lollipop, popsicle',
951 | 930: 'French loaf',
952 | 931: 'bagel, beigel',
953 | 932: 'pretzel',
954 | 933: 'cheeseburger',
955 | 934: 'hotdog, hot dog, red hot',
956 | 935: 'mashed potato',
957 | 936: 'head cabbage',
958 | 937: 'broccoli',
959 | 938: 'cauliflower',
960 | 939: 'zucchini, courgette',
961 | 940: 'spaghetti squash',
962 | 941: 'acorn squash',
963 | 942: 'butternut squash',
964 | 943: 'cucumber, cuke',
965 | 944: 'artichoke, globe artichoke',
966 | 945: 'bell pepper',
967 | 946: 'cardoon',
968 | 947: 'mushroom',
969 | 948: 'Granny Smith',
970 | 949: 'strawberry',
971 | 950: 'orange',
972 | 951: 'lemon',
973 | 952: 'fig',
974 | 953: 'pineapple, ananas',
975 | 954: 'banana',
976 | 955: 'jackfruit, jak, jack',
977 | 956: 'custard apple',
978 | 957: 'pomegranate',
979 | 958: 'hay',
980 | 959: 'carbonara',
981 | 960: 'chocolate sauce, chocolate syrup',
982 | 961: 'dough',
983 | 962: 'meat loaf, meatloaf',
984 | 963: 'pizza, pizza pie',
985 | 964: 'potpie',
986 | 965: 'burrito',
987 | 966: 'red wine',
988 | 967: 'espresso',
989 | 968: 'cup',
990 | 969: 'eggnog',
991 | 970: 'alp',
992 | 971: 'bubble',
993 | 972: 'cliff, drop, drop-off',
994 | 973: 'coral reef',
995 | 974: 'geyser',
996 | 975: 'lakeside, lakeshore',
997 | 976: 'promontory, headland, head, foreland',
998 | 977: 'sandbar, sand bar',
999 | 978: 'seashore, coast, seacoast, sea-coast',
1000 | 979: 'valley, vale',
1001 | 980: 'volcano',
1002 | 981: 'ballplayer, baseball player',
1003 | 982: 'groom, bridegroom',
1004 | 983: 'scuba diver',
1005 | 984: 'rapeseed',
1006 | 985: 'daisy',
1007 | 986: 'yellow lady\'s slipper, yellow lady-slipper, Cypripedium calceolus, ' +
1008 | 'Cypripedium parviflorum',
1009 | 987: 'corn',
1010 | 988: 'acorn',
1011 | 989: 'hip, rose hip, rosehip',
1012 | 990: 'buckeye, horse chestnut, conker',
1013 | 991: 'coral fungus',
1014 | 992: 'agaric',
1015 | 993: 'gyromitra',
1016 | 994: 'stinkhorn, carrion fungus',
1017 | 995: 'earthstar',
1018 | 996: 'hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola ' +
1019 | 'frondosa',
1020 | 997: 'bolete',
1021 | 998: 'ear, spike, capitulum',
1022 | 999: 'toilet tissue, toilet paper, bathroom tissue'
1023 | };
1024 |
--------------------------------------------------------------------------------