├── .all-contributorsrc ├── .circleci └── config.yml ├── .editorconfig ├── .github └── dependabot.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── __tests__ ├── __snapshots__ │ └── regressionCheck.ts.snap ├── index.ts └── regressionCheck.ts ├── _art ├── nsfw_demo.gif ├── nsfwjs-mobile.jpg ├── nsfwjs_logo.jpg └── red_mockup_top.jpg ├── examples ├── manual_testing │ ├── README.md │ ├── data │ │ ├── animations │ │ │ └── smile.webp │ │ ├── images │ │ │ ├── cat.svg │ │ │ ├── glasses.png │ │ │ ├── house.webp │ │ │ └── smile.jpg │ │ └── index.html │ ├── default.conf │ └── docker-compose.yml ├── minimal_demo │ ├── README.md │ └── index.html ├── node_demo │ └── server.js └── nsfw_demo │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── config-overrides.js │ ├── netlify │ └── edge-functions │ │ └── country-block.tsx │ ├── package.json │ ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-256x256.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── site.webmanifest │ ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── components │ │ ├── Footer.js │ │ ├── Header.js │ │ ├── Loading.js │ │ ├── Results.js │ │ └── Underdrop.js │ ├── index.css │ ├── index.js │ ├── ir.svg │ ├── logo.svg │ ├── serviceWorker.js │ └── tflogo.jpg │ └── yarn.lock ├── jest.config.js ├── models ├── inception_v3 │ ├── group1-shard1of6 │ ├── group1-shard2of6 │ ├── group1-shard3of6 │ ├── group1-shard4of6 │ ├── group1-shard5of6 │ ├── group1-shard6of6 │ └── model.json ├── mobilenet_v2 │ ├── group1-shard1of1 │ └── model.json └── mobilenet_v2_mid │ ├── group1-shard1of2 │ ├── group1-shard2of2 │ └── model.json ├── netlify.toml ├── package.json ├── scripts ├── add-extensions.mjs ├── add-nested-package-json.mjs ├── bundle-model.mjs ├── post-test.mjs └── pre-test.mjs ├── src ├── index.ts ├── model_imports │ ├── inception_v3.ts │ ├── mobilenet_v2.ts │ └── mobilenet_v2_mid.ts ├── nsfw_classes.ts └── nsfwjs.ts ├── tsconfig.cjs.json ├── tsconfig.esm.json ├── tsconfig.json └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "nsfwjs", 3 | "projectOwner": "infinitered", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "contributors": [ 12 | { 13 | "login": "GantMan", 14 | "name": "Gant Laborde", 15 | "avatar_url": "https://avatars0.githubusercontent.com/u/997157?v=4", 16 | "profile": "http://gantlaborde.com/", 17 | "contributions": [ 18 | "question", 19 | "blog", 20 | "code", 21 | "example", 22 | "ideas", 23 | "infra", 24 | "review", 25 | "test" 26 | ] 27 | }, 28 | { 29 | "login": "jamonholmgren", 30 | "name": "Jamon Holmgren", 31 | "avatar_url": "https://avatars3.githubusercontent.com/u/1479215?v=4", 32 | "profile": "https://jamonholmgren.com", 33 | "contributions": [ 34 | "doc", 35 | "ideas", 36 | "code", 37 | "content" 38 | ] 39 | }, 40 | { 41 | "login": "mazenchami", 42 | "name": "Mazen Chami", 43 | "avatar_url": "https://avatars.githubusercontent.com/u/9324607?v=4", 44 | "profile": "https://github.com/mazenchami", 45 | "contributions": [ 46 | "doc", 47 | "code", 48 | "review", 49 | "test" 50 | ] 51 | }, 52 | { 53 | "login": "jstudenski", 54 | "name": "Jeff Studenski", 55 | "avatar_url": "https://avatars0.githubusercontent.com/u/7350279?v=4", 56 | "profile": "https://github.com/jstudenski", 57 | "contributions": [ 58 | "design" 59 | ] 60 | }, 61 | { 62 | "login": "fvonhoven", 63 | "name": "Frank von Hoven III", 64 | "avatar_url": "https://avatars2.githubusercontent.com/u/10098988?v=4", 65 | "profile": "https://github.com/fvonhoven", 66 | "contributions": [ 67 | "doc", 68 | "ideas" 69 | ] 70 | }, 71 | { 72 | "login": "sandeshsoni", 73 | "name": "Sandesh Soni", 74 | "avatar_url": "https://avatars3.githubusercontent.com/u/3761745?v=4", 75 | "profile": "https://github.com/sandeshsoni", 76 | "contributions": [ 77 | "code" 78 | ] 79 | }, 80 | { 81 | "login": "seannam1218", 82 | "name": "Sean Nam", 83 | "avatar_url": "https://avatars1.githubusercontent.com/u/24437898?v=4", 84 | "profile": "https://github.com/seannam1218", 85 | "contributions": [ 86 | "doc" 87 | ] 88 | }, 89 | { 90 | "login": "emer7", 91 | "name": "Gilbert Emerson", 92 | "avatar_url": "https://avatars1.githubusercontent.com/u/21377166?v=4", 93 | "profile": "https://github.com/emer7", 94 | "contributions": [ 95 | "code" 96 | ] 97 | }, 98 | { 99 | "login": "xilaraux", 100 | "name": "Oleksandr Kozlov", 101 | "avatar_url": "https://avatars2.githubusercontent.com/u/17703730?v=4", 102 | "profile": "https://github.com/xilaraux", 103 | "contributions": [ 104 | "infra", 105 | "test", 106 | "code" 107 | ] 108 | }, 109 | { 110 | "login": "mlaco", 111 | "name": "Morgan", 112 | "avatar_url": "https://avatars2.githubusercontent.com/u/4466642?v=4", 113 | "profile": "http://morganlaco.com", 114 | "contributions": [ 115 | "code", 116 | "ideas" 117 | ] 118 | }, 119 | { 120 | "login": "mycaule", 121 | "name": "Michel Hua", 122 | "avatar_url": "https://avatars2.githubusercontent.com/u/6161385?v=4", 123 | "profile": "http://mycaule.github.io/", 124 | "contributions": [ 125 | "code", 126 | "doc" 127 | ] 128 | }, 129 | { 130 | "login": "kevinvangelder", 131 | "name": "Kevin VanGelder", 132 | "avatar_url": "https://avatars2.githubusercontent.com/u/1771152?v=4", 133 | "profile": "https://www.infinite.red", 134 | "contributions": [ 135 | "code", 136 | "doc" 137 | ] 138 | }, 139 | { 140 | "login": "TechnikEmpire", 141 | "name": "Jesse Nicholson", 142 | "avatar_url": "https://avatars2.githubusercontent.com/u/11234763?v=4", 143 | "profile": "http://technikempire.com", 144 | "contributions": [ 145 | "data", 146 | "ideas" 147 | ] 148 | }, 149 | { 150 | "login": "camhart", 151 | "name": "camhart", 152 | "avatar_url": "https://avatars0.githubusercontent.com/u/3038809?v=4", 153 | "profile": "https://github.com/camhart", 154 | "contributions": [ 155 | "doc" 156 | ] 157 | }, 158 | { 159 | "login": "Cameron-Burkholder", 160 | "name": "Cameron Burkholder", 161 | "avatar_url": "https://avatars2.githubusercontent.com/u/13265710?v=4", 162 | "profile": "https://github.com/Cameron-Burkholder", 163 | "contributions": [ 164 | "design" 165 | ] 166 | }, 167 | { 168 | "login": "qwertyforce", 169 | "name": "qwertyforce", 170 | "avatar_url": "https://avatars0.githubusercontent.com/u/44163887?v=4", 171 | "profile": "https://qwertyforce.ru", 172 | "contributions": [ 173 | "doc" 174 | ] 175 | }, 176 | { 177 | "login": "YegorZaremba", 178 | "name": "Yegor <3", 179 | "avatar_url": "https://avatars3.githubusercontent.com/u/31797554?v=4", 180 | "profile": "https://github.com/YegorZaremba", 181 | "contributions": [ 182 | "code", 183 | "test" 184 | ] 185 | }, 186 | { 187 | "login": "navendu-pottekkat", 188 | "name": "Navendu Pottekkat", 189 | "avatar_url": "https://avatars1.githubusercontent.com/u/49474499?v=4", 190 | "profile": "http://navendu.me", 191 | "contributions": [ 192 | "doc" 193 | ] 194 | }, 195 | { 196 | "login": "VladStepanov", 197 | "name": "Vladislav", 198 | "avatar_url": "https://avatars0.githubusercontent.com/u/49880862?v=4", 199 | "profile": "https://github.com/VladStepanov", 200 | "contributions": [ 201 | "code", 202 | "doc" 203 | ] 204 | }, 205 | { 206 | "login": "nacht42", 207 | "name": "Nacht", 208 | "avatar_url": "https://avatars1.githubusercontent.com/u/37903575?v=4", 209 | "profile": "https://github.com/nacht42", 210 | "contributions": [ 211 | "code" 212 | ] 213 | }, 214 | { 215 | "login": "kateinkim", 216 | "name": "kateinkim", 217 | "avatar_url": "https://avatars.githubusercontent.com/u/53795920?v=4", 218 | "profile": "https://github.com/kateinkim", 219 | "contributions": [ 220 | "code", 221 | "doc" 222 | ] 223 | }, 224 | { 225 | "login": "JanPoonthong", 226 | "name": "jan", 227 | "avatar_url": "https://avatars.githubusercontent.com/u/56725335?v=4", 228 | "profile": "https://janpoonthong.github.io/portfolio/", 229 | "contributions": [ 230 | "doc" 231 | ] 232 | }, 233 | { 234 | "login": "roerohan", 235 | "name": "Rohan Mukherjee", 236 | "avatar_url": "https://avatars.githubusercontent.com/u/42958812?v=4", 237 | "profile": "https://github.com/roerohan", 238 | "contributions": [ 239 | "question", 240 | "infra", 241 | "maintenance", 242 | "code" 243 | ] 244 | }, 245 | { 246 | "login": "haZya", 247 | "name": "Hasitha Wickramasinghe", 248 | "avatar_url": "https://avatars.githubusercontent.com/u/63403456?v=4", 249 | "profile": "https://hazya.dev", 250 | "contributions": [ 251 | "code", 252 | "doc", 253 | "example", 254 | "ideas", 255 | "infra", 256 | "test" 257 | ] 258 | } 259 | ], 260 | "contributorsPerLine": 7, 261 | "commitConvention": "none" 262 | } 263 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | lint: 5 | docker: 6 | - image: cimg/node:20.9.0 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | name: Restore node modules 11 | keys: 12 | - v1-dependencies-{{ checksum "package.json" }}-{{ arch }} 13 | # fallback to using the latest cache if no exact match is found 14 | - v1-dependencies- 15 | - run: 16 | name: Install dependencies 17 | command: yarn install 18 | - save_cache: 19 | name: Save node modules 20 | paths: 21 | - node_modules 22 | key: v1-dependencies-{{ checksum "package.json" }}-{{ arch }} 23 | - run: 24 | name: Lint 25 | command: yarn lint 26 | test: 27 | docker: 28 | - image: cimg/node:20.9.0 29 | steps: 30 | - checkout 31 | - restore_cache: 32 | name: Restore node modules 33 | keys: 34 | - v1-dependencies-{{ checksum "package.json" }}-{{ arch }} 35 | # fallback to using the latest cache if no exact match is found 36 | - v1-dependencies- 37 | - run: 38 | name: Install dependencies 39 | command: yarn install 40 | - save_cache: 41 | name: Save node modules 42 | paths: 43 | - node_modules 44 | key: v1-dependencies-{{ checksum "package.json" }}-{{ arch }} 45 | - run: 46 | name: Test 47 | command: yarn test 48 | 49 | workflows: 50 | nsfw-ci: 51 | jobs: 52 | - lint 53 | - test 54 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # all files 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | max_line_length = 80 13 | quote_type = double 14 | 15 | [*.{js,ts}] 16 | quote_type = double 17 | curly_bracket_next_line = false 18 | spaces_around_brackets = inside 19 | indent_brace_style = BSD KNF -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: np 11 | versions: 12 | - 7.3.0 13 | - 7.4.0 14 | - 7.5.0 15 | - dependency-name: "@types/jest" 16 | versions: 17 | - 26.0.23 18 | - dependency-name: "@tensorflow/tfjs" 19 | versions: 20 | - 3.0.0 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | yarn-error.log 4 | .vscode 5 | .DS_Store 6 | 7 | examples/manual-testing/data/* 8 | !examples/manual-testing/data/index.html 9 | !examples/manual-testing/data/animations/ 10 | !examples/manual-testing/data/images/ 11 | !examples/manual-testing/data/videos/ 12 | 13 | /models/**/*.js 14 | /models/**/*.min.js 15 | 16 | # Local Netlify folder 17 | .netlify 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .rpt2_cache/ 3 | demos/ 4 | scripts/ 5 | src/ 6 | coverage/ 7 | node_modules/ 8 | karma.conf.js 9 | *.tgz 10 | dist/**/*.js.map 11 | .travis.yml 12 | .npmignore 13 | tslint.json 14 | yarn.lock 15 | examples/ 16 | _art/ 17 | yarn-error.log 18 | bundle.js 19 | .DS_Store 20 | .all-contributorsrc 21 | .editorconfig 22 | .gitignore 23 | jest.config.js 24 | tsconfig.cjs.json 25 | tsconfig.esm.json 26 | tsconfig.json 27 | .circleci/ 28 | .github/ 29 | __tests__/ 30 | 31 | # Models 32 | /models/ 33 | 34 | # Ignore intermediate browserify files 35 | # Mountpoint 36 | dist/cjs/nsfwjs.d.ts 37 | dist/cjs/nsfwjs.js 38 | dist/esm/nsfwjs.d.ts 39 | dist/esm/nsfwjs.js 40 | # Unminified bundle 41 | dist/browser/bundle.js 42 | 43 | netlify.toml 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Infinite Red, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | NSFWJS Logo 3 |

Client-side indecent content checking

4 |

5 | 6 | [![All Contributors](https://img.shields.io/badge/all_contributors-15-green.svg?style=flat-square)](#contributors) 7 | [![CircleCI](https://dl.circleci.com/status-badge/img/gh/infinitered/nsfwjs/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/infinitered/nsfwjs/tree/master) 8 | [![Netlify Status](https://api.netlify.com/api/v1/badges/72d19dc0-d316-4f75-9904-a33d833ff628/deploy-status)](https://app.netlify.com/sites/nsfwjs/deploys) 9 | 10 | A simple JavaScript library to help you quickly identify unseemly images; all in the client's browser. NSFWJS isn't perfect, but it's pretty accurate (~90% with small and ~93% with midsized model)... and it's getting more accurate all the time. 11 | 12 | Why would this be useful? [Check out the announcement blog post](https://shift.infinite.red/avoid-nightmares-nsfw-js-ab7b176978b1). 13 | 14 |

15 | demo example 16 |

17 | 18 | ## NOTE 19 | 20 | If you're trying to access the Cloudfront hosted model and are running into an error, it's likely due to the fact that the model has been moved to a new location. Please take a look at our [Host your own model](#host-your-own-model) section. We will be returning the model after some hotlinkers have been dealt with. 21 | 22 | ## **Table of Contents** 23 | 24 | 25 | 26 | 27 | - [QUICK: How to use the module](#quick-how-to-use-the-module) 28 | - [Library API](#library-api) 29 | - [`load` the model](#load-the-model) 30 | - [Caching](#caching) 31 | - [`classify` an image](#classify-an-image) 32 | - [Production](#production) 33 | - [Install](#install) 34 | - [Host your own model](#host-your-own-model) 35 | - [Run the Examples](#run-the-examples) 36 | - [Tensorflow.js in the browser](#tensorflowjs-in-the-browser) 37 | - [Browserify](#browserify) 38 | - [React Native](#react-native) 39 | - [Node JS App](#node-js-app) 40 | - [NSFW Filter](#nsfw-filter) 41 | - [Learn TensorFlow.js](#learn-tensorflowjs) 42 | - [More!](#more) 43 | - [Open Source](#open-source) 44 | - [Need the experts? Hire Infinite Red for your next project](#need-the-experts-hire-infinite-red-for-your-next-project) 45 | - [Contributors](#contributors) 46 | 47 | 48 | 49 | The library categorizes image probabilities in the following 5 classes: 50 | 51 | - `Drawing` - safe for work drawings (including anime) 52 | - `Hentai` - hentai and pornographic drawings 53 | - `Neutral` - safe for work neutral images 54 | - `Porn` - pornographic images, sexual acts 55 | - `Sexy` - sexually explicit images, not pornography 56 | 57 | > _The demo is a continuous deployment source - Give it a go: http://nsfwjs.com_ 58 | 59 | ## QUICK: How to use the module 60 | 61 | With `async/await` support: 62 | 63 | ```js 64 | import * as nsfwjs from "nsfwjs"; 65 | 66 | const img = document.getElementById("img"); 67 | 68 | // If you want to host models on your own or use different model from the ones available, see the section "Host your own model". 69 | const model = await nsfwjs.load(); 70 | 71 | // Classify the image 72 | const predictions = await model.classify(img); 73 | console.log("Predictions: ", predictions); 74 | ``` 75 | 76 | Without `async/await` support: 77 | 78 | ```js 79 | import * as nsfwjs from "nsfwjs"; 80 | 81 | const img = document.getElementById("img"); 82 | 83 | // If you want to host models on your own or use different model from the ones available, see the section "Host your own model". 84 | nsfwjs 85 | .load() 86 | .then(function (model) { 87 | // Classify the image 88 | return model.classify(img); 89 | }) 90 | .then(function (predictions) { 91 | console.log("Predictions: ", predictions); 92 | }); 93 | ``` 94 | 95 | ## Library API 96 | 97 | ### `load` the model 98 | 99 | Before you can classify any image, you'll need to load the model. 100 | 101 | ```js 102 | const model = nsfwjs.load(); // Default: "MobileNetV2" 103 | ``` 104 | 105 | You can use the optional first parameter to specify which model you want to use from the three that are bundled together. Defaults to: `"MobileNetV2"`. This supports tree-shaking on supported bundlers like Webpack, so you will only be loading the model you are using. 106 | 107 | ```js 108 | const model = nsfwjs.load("MobileNetV2Mid"); // "MobileNetV2" | "MobileNetV2Mid" | "InceptionV3" 109 | ``` 110 | 111 | You can also use same parameter and load the model from your website/server, as explained in the [Host your own model](#host-your-own-model) section. Doing so could reduce the bundle size for loading the model by approximately 1.33 times (33%) since you can directly use the binary files instead of the base64 that are bundled with the package. i.e. The `"MobileNetV2"` model bundled into the package is 3.5MB instead of 2.6MB for hosted binary files. This would only make a difference if you are loading the model every time (without [Caching](#caching)) on the client-side browser since on the server-side, you'd only be loading the model once at the server start. 112 | 113 | Model MobileNetV2 - [224x224](https://github.com/infinitered/nsfwjs/blob/master/models/mobilenet_v2/) 114 | 115 | ```js 116 | const model = nsfwjs.load("/path/to/mobilenet_v2/"); 117 | ``` 118 | 119 | If you're using a model that needs an image of dimension other than 224x224, you can pass the size in the options parameter. 120 | 121 | Model MobileNetV2Mid - [Graph](https://github.com/infinitered/nsfwjs/tree/master/models/mobilenet_v2_mid) 122 | 123 | ```js 124 | /* You may need to load this model with graph type */ 125 | const model = nsfwjs.load("/path/to/mobilenet_v2_mid/", { type: 'graph' }); 126 | ``` 127 | 128 | If you're using a graph model, you cannot use the infer method, and you'll need to tell model load that you're dealing with a graph model in options. 129 | 130 | Model InceptionV3 - [299x299](https://github.com/infinitered/nsfwjs/tree/master/models/inception_v3) 131 | 132 | ```js 133 | const model = nsfwjs.load("/path/to/inception_v3/", { size: 299 }); 134 | ``` 135 | 136 | ### Caching 137 | 138 | If you're using in the browser and you'd like to subsequently load from indexed db or local storage (NOTE: model size may be too large for local storage!) you can save the underlying model using the appropriate scheme and load from there. 139 | 140 | ```js 141 | const initialLoad = await nsfwjs.load( 142 | "/path/to/different/model/" /*, { ...options }*/ 143 | ); 144 | await initialLoad.model.save("indexeddb://exampleModel"); 145 | const model = await nsfwjs.load("indexeddb://exampleModel" /*, { ...options }*/); 146 | ``` 147 | 148 | **Parameters** 149 | 150 | Initial Load: 151 | 1. URL or path to folder containing `model.json`. 152 | 2. Optional object with size or type property that your model expects. 153 | 154 | Subsequent Load: 155 | 1. IndexedDB path. 156 | 2. Optional object with size or type property that your model expects. 157 | 158 | 159 | **Returns** 160 | 161 | - Ready to use NSFWJS model object 162 | 163 | 164 | **Troubleshooting** 165 | 166 | - On the tab where the model is being loaded, inspect element and navigate to the the "Application" tab. On the left pane under the "Storage" section, there is a subsection named "IndexedDB". Here you can view if the model is being saved. 167 | 168 | 169 | ### `classify` an image 170 | 171 | This function can take any browser-based image elements (``, `