├── .gitignore ├── README.md ├── image_ops.js ├── lib.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TFJS 2 | 3 | ## About 4 | 5 | TFJS is a library that wraps and it easier to interact with the compiled version of TensorFlow.js (https://github.com/tomasreimers/tensorflowjs). 6 | 7 | ## Installing 8 | 9 | Before you can use TFJS, you must have a copy of the compiled version of TensorFlow.js (found [here](https://github.com/tomasreimers/tensorflowjs)). Unfortunately the size of TensorFlow.js means that it can't be uglified or babelified (as doing so will exhaust memory in those gulp plugins, at least in our experience), but must be included separately. 10 | 11 | We recommend doing `git clone https://github.com/tomasreimers/tensorflowjs` in the root of your website or node script. 12 | 13 | ## Usage 14 | 15 | ### Creating a TFJS Instance 16 | 17 | Because TFJS wraps TensorFlow.js, it must first be made aware of where the emscripten module (exported by TensorFlow.js) is. 18 | 19 | ``` 20 | const TFJS = require('tfjs'); 21 | 22 | let tfjs = TFJS(window.TensorFlowJS); // or similar 23 | ``` 24 | 25 | We recognize that actually creating the emscripten module can be a bother, and so we expose two additional methods to create a TFJS instance: 26 | 27 | ``` 28 | // for node 29 | const TFJS = require('tfjs'); 30 | 31 | let tfjs = TFJS.for_node("./path/to/tensorflowjs/directory/"); 32 | ``` 33 | 34 | ``` 35 | // for the browser 36 | const TFJS = require('tfjs'); 37 | 38 | let tfjs_promise = TFJS.for_browser("/url/to/tensorflowjs/directory/"); 39 | 40 | // NOTE: This returns a promise, so to get tfjs, use then: 41 | tfjs_promise.then(function (tfjs) { 42 | /* 43 | * You can call then multiple times, so no need to worry about saving it to 44 | * another variable, and you can also use this promise to see when the 45 | * library is done loading. 46 | */ 47 | }); 48 | ``` 49 | 50 | ### Interacting with a TFJS Instance 51 | 52 | Before you can interact with a TFJS instance, you need to be able to create two things: 53 | - Graph Protobufs 54 | - Tensor Protobufs 55 | 56 | #### Creating Graph Protobufs 57 | 58 | ``` 59 | // for node 60 | 61 | const graph_buf = fs.readFileSync('../graphs/example_graph.pb'); 62 | const graph_bufview = new Uint8Array(graph_buf); 63 | let graph = ""; 64 | for (let ii = 0; ii < graph_bufview.length; ii++) { 65 | graph += String.fromCharCode(graph_bufview[ii]); 66 | } 67 | ``` 68 | 69 | ``` 70 | // for the browser 71 | 72 | function loadGraph(graph) { 73 | return new Promise(function (resolve, reject) { 74 | var oReq = new XMLHttpRequest(); 75 | oReq.open("GET", graph, true); 76 | oReq.responseType = "arraybuffer"; 77 | 78 | oReq.onload = function (oEvent) { 79 | var arrayBuffer = oReq.response; // Note: not oReq.responseText 80 | if (arrayBuffer) { 81 | var byteArray = new Uint8Array(arrayBuffer); 82 | 83 | // convert to string 84 | let graph = ""; 85 | for (let ii = 0; ii < byteArray.length; ii++) { 86 | graph += String.fromCharCode(byteArray[ii]); 87 | } 88 | 89 | resolve(graph); 90 | } 91 | }; 92 | 93 | oReq.send(null); 94 | }); 95 | } 96 | 97 | let graph_promise = loadGraph("/graphs/example_graph.pb"); 98 | graph_promise.then(function (graph) { 99 | // ... 100 | }); 101 | ``` 102 | 103 | #### Creating Tensor Protobufs 104 | 105 | To create tensor protobufs, we use [tensorjs](https://www.npmjs.com/package/tensorjs). See that readme for a much more detailed explanation. Although the tl;dr is: 106 | 107 | ``` 108 | const tensorjs = require('tensorjs'); 109 | 110 | tensorjs.intTensor([[1, 2], [3, 4]]); // returns a string for the protobuf of the tensor 111 | ``` 112 | 113 | #### Calling the TFJS Session & Run 114 | 115 | To actually call the graph, you have to construct a session, and then you can perform runs against it (passing a feed dictionary of tensors and an array detailing which tensors to fetch). 116 | 117 | We tried to make this API mirror the python API for TensorFlow as much as possible. 118 | 119 | ``` 120 | new tfjs.Session(graph_pb) 121 | ``` 122 | 123 | - `graph_pb`: The protobuf representing the graph for this session. 124 | - `RETURNS`: A session object. 125 | 126 | ``` 127 | session.run(feed_dict, fetches) 128 | ``` 129 | 130 | - `feed_dict`: A dictionary mapping tensor names to tensors. 131 | - `fetches`: An array of tensor names to fetch. 132 | - `RETURNS`: An array of the same length of fetches with the respective values for all the tensors requested. 133 | 134 | Example: 135 | 136 | ``` 137 | /* 138 | * simple_addition_graph is basically just: 139 | * 140 | * tf.add( 141 | * tf.placeholder(name="x"), 142 | * tf.placeholder(name="y"), 143 | * name="output" 144 | * ); 145 | */ 146 | 147 | const sess = new lib.Session(simple_addition_graph); 148 | const results = sess.run( 149 | { 150 | "x": tensorjs.intTensor(40), 151 | "y": tensorjs.intTensor(2) 152 | }, 153 | ["output"] 154 | ); 155 | console.log(results[0]); // 42 156 | ``` 157 | 158 | #### Calling TFJS Convenience Methods 159 | 160 | We understand that certain methods can be tedious to constantly rewrite, and are common; for that reason, we provide them on the tfjs instance object. 161 | 162 | ``` 163 | tfjs.image_ops.get_array(canvas_image_data, grayscale, mean, std); 164 | ``` 165 | 166 | - `canvas_image_data`: An image_data object 167 | - `grayscale`: Whether to have 1 color channel (instead of 3) 168 | - `mean`: All data will be transformed with: `(value - mean) / std` 169 | - `std`: See above 170 | - `RETURNS`: A rank-4 tensor encoding the image. Out of convention it is stored as [batch, height, width, channel] for tensorflow graphs. 171 | 172 | 173 | Example: 174 | 175 | ``` 176 | // ...ctx is a canvas context 177 | const img_data = ctx.getImageData(0, 0, WIDTH, HEIGHT); 178 | 179 | tfjs.image_ops.get_array(img_data, true, 0, 1); 180 | ``` 181 | 182 | ## License & Author 183 | 184 | Copyright 2017 Tomas Reimers 185 | 186 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 187 | 188 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 189 | 190 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 191 | -------------------------------------------------------------------------------- /image_ops.js: -------------------------------------------------------------------------------- 1 | // From tensorflow/example/label_image/main.cc: 2 | // The convention for image ops in TensorFlow is that all images are expected 3 | // to be in batches, so that they're four-dimensional arrays with indices of 4 | // [batch, height, width, channel]. Because we only have a single image, we 5 | // have to add a batch dimension of 1 to the start. 6 | 7 | module.exports = { 8 | get_array: function (image_data, grayscale = false, mean = 0, std = 1) { 9 | const result = new Array(1); 10 | result[0] = new Array(image_data.height); 11 | for (let ii = 0; ii < image_data.height; ii++) { 12 | result[0][ii] = new Array(image_data.width); 13 | for (let jj = 0; jj < image_data.width; jj++) { 14 | let index = (ii * image_data.width + jj) * 4; 15 | let r = image_data.data[index]; 16 | let g = image_data.data[index + 1]; 17 | let b = image_data.data[index + 2]; 18 | // let alpha = image_data.data[index + 3] 19 | 20 | if (grayscale) { 21 | // one channel 22 | result[0][ii][jj] = new Array(1); 23 | let gray = (r + g + b) / 3; 24 | result[0][ii][jj][0] = (gray - mean) / std; 25 | } else { 26 | // three channel 27 | result[0][ii][jj] = new Array(3); 28 | result[0][ii][jj][0] = (r - mean) / std; 29 | result[0][ii][jj][1] = (g - mean) / std; 30 | result[0][ii][jj][2] = (b - mean) / std; 31 | } 32 | } 33 | } 34 | 35 | return result; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /lib.js: -------------------------------------------------------------------------------- 1 | const tensorjs = require('tensorjs'); 2 | const image_ops = require('./image_ops.js'); 3 | 4 | // utility function 5 | function copy_array_to_vector(arr, vector) { 6 | for (var ii = 0; ii < arr.length; ii++) { 7 | vector.push_back(arr[ii]); 8 | } 9 | } 10 | 11 | function TFJS(graph_runner) { 12 | this.Session = function (graph_pb) { 13 | var self = this; 14 | self._session = new graph_runner.JSSession(graph_pb); 15 | self.run = function (inputs, outputs) { 16 | // because emscripten requires us to explicitly delete classes, we keep a list 17 | const trash_pile = []; 18 | 19 | // encode the inputs and outputs 20 | const input_pairs = []; 21 | const input_keys = Object.keys(inputs); 22 | for (let ii = 0; ii < Object.keys(inputs).length; ii++) { 23 | let tensor = graph_runner.parseTensor( 24 | inputs[input_keys[ii]] 25 | ); 26 | let stpair = graph_runner.makeStringTensorPair( 27 | input_keys[ii], 28 | tensor 29 | ) 30 | input_pairs.push(stpair); 31 | 32 | trash_pile.push(tensor); 33 | trash_pile.push(stpair); 34 | } 35 | 36 | const inputs_vector = new graph_runner.VectorStringTensorPair(); 37 | const outputs_vector = new graph_runner.VectorString(); 38 | 39 | trash_pile.push(inputs_vector); 40 | trash_pile.push(outputs_vector); 41 | 42 | copy_array_to_vector(input_pairs, inputs_vector); 43 | copy_array_to_vector(outputs, outputs_vector); 44 | 45 | // run 46 | const results_tensor_vector = self._session.run(inputs_vector, outputs_vector); 47 | const results_vector = graph_runner.tensorVectorToStringVector( 48 | results_tensor_vector 49 | ); 50 | 51 | trash_pile.push(results_tensor_vector); 52 | trash_pile.push(results_vector); 53 | 54 | // decode the results 55 | const results = []; 56 | for (let ii = 0; ii < results_vector.size(); ii++) { 57 | results.push( 58 | tensorjs.make_array(results_vector.get(ii)) 59 | ); 60 | } 61 | 62 | // schedule cleanup 63 | setTimeout(() => { 64 | for (var ii = 0; ii < trash_pile.length; ii++) { 65 | trash_pile[ii].delete(); 66 | } 67 | }, 0); 68 | 69 | // return results 70 | return results; 71 | }; 72 | 73 | self.cleanup = function () { 74 | self._session.delete(); 75 | }; 76 | }; 77 | 78 | this.image_ops = image_ops; 79 | }; 80 | 81 | // constructor functions 82 | TFJS.for_browser = function (url_for_dir) { 83 | url_for_dir = url_for_dir || ""; 84 | 85 | if (url_for_dir != "" && url_for_dir[url_for_dir.length - 1] != '/') { 86 | throw "Path must end in a trailing slash." 87 | } 88 | 89 | let loading_promise = new Promise(function (resolve, reject) { 90 | window.Module = { 91 | memoryInitializerPrefixURL: url_for_dir, 92 | onRuntimeInitialized: function () { 93 | console.log("Emscripten initialized!"); 94 | resolve(new TFJS(window.Module)); 95 | } 96 | }; 97 | }); 98 | 99 | var s = document.createElement("script"); 100 | s.type = "text/javascript"; 101 | s.src = url_for_dir + "graph_runner.js"; 102 | document.body.appendChild(s); 103 | 104 | return loading_promise; 105 | }; 106 | 107 | TFJS.for_web_worker = function (url_for_dir) { 108 | url_for_dir = url_for_dir || ""; 109 | 110 | if (url_for_dir != "" && url_for_dir[url_for_dir.length - 1] != '/') { 111 | throw "Path must end in a trailing slash." 112 | } 113 | 114 | let loading_promise = new Promise(function (resolve, reject) { 115 | self.Module = { 116 | // overriding this so that workers can be compiled with webpack, per https://github.com/webpack-contrib/worker-loader 117 | ENVIRONMENT: 'WORKER', 118 | memoryInitializerPrefixURL: url_for_dir, 119 | onRuntimeInitialized: function () { 120 | console.log("Emscripten initialized!"); 121 | resolve(new TFJS(self.Module)); 122 | } 123 | }; 124 | }); 125 | 126 | importScripts(url_for_dir + "graph_runner.js"); 127 | 128 | return loading_promise; 129 | }; 130 | 131 | TFJS.for_node = function (path_for_dir) { 132 | path_for_dir = path_for_dir || "./"; 133 | 134 | if (path_for_dir[path_for_dir.length - 1] != '/') { 135 | throw "Path must end in a trailing slash." 136 | } 137 | 138 | // nasty hack to get around memory initializer non-sense 139 | let cwd = process.cwd(); 140 | process.chdir(__dirname + '/' + path_for_dir); 141 | const graph_runner = require(path_for_dir + 'graph_runner.js'); 142 | process.chdir(cwd); 143 | 144 | return new TFJS(graph_runner); 145 | }; 146 | 147 | // export 148 | module.exports = TFJS; 149 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tfjs", 3 | "version": "0.6.0", 4 | "description": "A wrapper for interacting with tensorflow.js.", 5 | "main": "lib.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/tomasreimers/tfjs.git" 12 | }, 13 | "keywords": [ 14 | "tensorflow", 15 | "machine-learning" 16 | ], 17 | "author": "Tomas Reimers", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/tomasreimers/tfjs/issues" 21 | }, 22 | "homepage": "https://github.com/tomasreimers/tfjs#readme", 23 | "dependencies": { 24 | "tensorjs": "^0.2.0" 25 | } 26 | } 27 | --------------------------------------------------------------------------------