├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── example ├── package.json ├── public │ ├── example-300.gif │ ├── example-600.gif │ ├── example.gif │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ └── mobilenet_v1_0.25_224.json ├── src │ ├── containers │ │ ├── App │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ └── Search │ │ │ ├── Topic.tsx │ │ │ ├── getImages.ts │ │ │ ├── index.tsx │ │ │ ├── loading.gif │ │ │ └── styles.scss │ ├── index.tsx │ └── mlClassifierUI.ts ├── tsconfig.json ├── tsconfig.prod.json ├── tsconfig.test.json ├── tslint.json └── yarn.lock ├── issue_template.md ├── package.json ├── rollup.config.js ├── rollup.config.js.umd ├── src ├── App │ ├── index.tsx │ └── styles.scss ├── Dropzone │ ├── getFile.ts │ ├── index.tsx │ ├── styles.scss │ ├── transformFiles.ts │ └── traverseFileTree.ts ├── Loading │ ├── index.tsx │ ├── loading.gif │ └── loading.ts ├── Model │ ├── Evaluator │ │ ├── Predictions │ │ │ ├── Prediction.tsx │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ └── index.tsx │ ├── Metrics │ │ ├── Info.tsx │ │ ├── Logs │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── index.tsx │ │ └── styles.scss │ ├── index.tsx │ └── styles.scss ├── Preview │ ├── index.tsx │ └── styles.scss ├── global.d.ts ├── index.ts ├── types.ts └── utils │ ├── __tests__ │ └── classNames.test.ts │ ├── classNames.ts │ ├── getFilesAsImages.ts │ └── nextFrame.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ], 10 | "@babel/typescript" 11 | ], 12 | "plugins": [ 13 | "transform-class-properties" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Don't include generated files 4 | *.css 5 | *.js 6 | *.d.ts 7 | *.tgz 8 | dist 9 | example/build 10 | 11 | # Various caches, error logs and such 12 | yarn-error.log 13 | .rpt2_cache 14 | package-lock.json 15 | 16 | # Don't include stylesheet typings 17 | src/*.css.d.ts 18 | src/**/*.css.d.ts 19 | 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | node_modules 3 | yarn-error.log 4 | .rpt2_cache 5 | tsconfig.json 6 | tslint.json 7 | yarn.lock 8 | rollup.config.js 9 | scripts 10 | package-lock.json 11 | example 12 | *.tgz 13 | .storybook 14 | stories 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kevin Scott 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ML Classifier UI 2 | 3 | ML Classifier is a React front end for a machine learning engine for quickly training image classification models in your browser. Models can be saved with a single command, and the resulting models reused to make image classification predictions. 4 | 5 | This package is the UI front end for [`ml-classifier`](https://github.com/thekevinscott/ml-classifier). 6 | 7 | ## Walkthrough 8 | 9 | A walkthrough of the code can be found in the article [Image Classification in the Browser with Javascript](https://thekevinscott.com/image-classification-with-javascript/). 10 | 11 | ## Demo 12 | 13 | An interactive [demo can be found here](https://thekevinscott.github.io/ml-classifier-ui/). 14 | 15 | ![Demo](https://github.com/thekevinscott/ml-classifier-ui/raw/master/example/public/example.gif) 16 | *Screenshot of demo* 17 | 18 | ## Getting Started 19 | 20 | ### Installation 21 | 22 | `ml-classifier-ui` can be installed via `yarn` or `npm`: 23 | 24 | ``` 25 | yarn add ml-classifier-ui 26 | ``` 27 | 28 | or 29 | 30 | ``` 31 | npm install ml-classifier-ui 32 | ``` 33 | 34 | ### Quick Start (Code Sandbox) 35 | 36 | You can fork a live running version at [codesandbox.io](https://codesandbox.io/s/218po5mzxn). 37 | 38 | ### Quick Start (Running locally) 39 | 40 | Start by instantiating a new MLClassifierUI. 41 | 42 | ``` 43 | import React from 'react'; 44 | import ReactDOM from 'react-dom'; 45 | import MLClassifierUI from 'ml-classifier-ui'; 46 | 47 | ReactDOM.render(, document.getElementById('root')); 48 | ``` 49 | 50 | ## API Documentation 51 | 52 | `MLClassifierUI` accepts a number of parameters: 53 | 54 | * **getMLClassifier** (`Function`) *Optional* - A callback that returns an instance of the underlying `ml-classifier` object. Call this if you want to programmatically call methods like `addData`, `train`, and `predict`. For more information on `ml-classifier`'s API methods [refer to it's documentation](https://github.com/thekevinscott/ml-classifier#api-documentation). 55 | * **methodParams** (`Object`) *Optional* - A set of parameters that will be passed in calls to `ml-classifier`'s methods. See below for more information. 56 | * **uploadFormat** (`string`) *Optional* - A string denoting what type of upload format to accept. Formats can be `flat` or `nested`. See below note for more information on that. If omitted, all formats are accepted. 57 | * **imageFormats** (`string[]`) *Optional* - An array of file extensions to accept. By default, all valid images are accepted. Images are transformed via the native `Image` tag in the browser, so if the browser can display the image, it'll be processed. 58 | * **showDownload** (`boolean`) *Optional* - A flag denoting whether to show a download button or not. Defaults to true. 59 | 60 | `MLClassifierUI` also accepts a number of callbacks that are called on the beginnings and ends of `ml-classifier` functions. [You can view a list of those here](https://github.com/thekevinscott/ml-classifier#parameters). 61 | 62 | ### `getMLClassifier` 63 | 64 | `getMLClassifier` returns an instance of `ml-classifier` for programmatic access to the underlying methods. 65 | 66 | #### Example 67 | 68 | ``` 69 | { 71 | mlClassifier.addData(...); 72 | }} 73 | /> 74 | ``` 75 | 76 | ### `methodParams` 77 | 78 | `methodParams` can be used to pass method-specific parameters to `ml-classifier`. The key will be used to determine which method to pass parameters to. 79 | 80 | Accepted keys are `train`, `evaluate`, and `save`. Other keys will be ignored. 81 | 82 | #### Example 83 | 84 | ``` 85 | 97 | ``` 98 | 99 | ### `uploadFormat` 100 | 101 | `uploadFormat` corresponds to how uploaded images should be organized. There are two options: 102 | 103 | #### `nested` 104 | Expects images to be organized in folders matching the label. Only the immediate parent folder's name will be used as the label. For example: 105 | 106 | ``` 107 | - containing-folder/ 108 | - dogs/ 109 | - IMG-1.jpg 110 | - IMG-2.jpg 111 | - IMG-3.jpg 112 | - cats/ 113 | - IMG-1.jpg 114 | - IMG-2.jpg 115 | - IMG-3.jpg 116 | ``` 117 | 118 | Will product an array of three `dogs` labels and three `cats` labels. 119 | 120 | Nested folders will be searched recursively, but only immediate parent folders' names will be used. If an invalidly nested structure is found an error will be thrown. 121 | 122 | #### `flat` (*currently in development*) 123 | Expects files' names to be the label. Nested folders will be searched recursively (if the browser supports it) to build a flat array of files. 124 | 125 | ``` 126 | - folder/ 127 | - dog-1.jpg 128 | - dog-2.jpg 129 | - dog-3.jpg 130 | - cat-1.jpg 131 | - cat-2.jpg 132 | - cat-3.jpg 133 | ``` 134 | 135 | #### Example 136 | 137 | ``` 138 | 141 | ``` 142 | 143 | ### `imageFormats` (*currently in development*) 144 | 145 | `imageFormats` denotes the list of acceptable image formats for upload. Any images not matching the list of acceptable formats will be ignored. 146 | 147 | #### Example 148 | 149 | ``` 150 | 156 | ``` 157 | 158 | ## Contributing 159 | 160 | Contributions are welcome! 161 | 162 | You can run the local example with: 163 | 164 | ``` 165 | yarn watch 166 | ``` 167 | 168 | `ml-classifier-ui` is written in Typescript and React. 169 | 170 | ### Tests 171 | 172 | Tests are a work in progress. Currently, the test suite only consists of unit tests. Pull requests for additional tests are welcome! 173 | 174 | Run tests with: 175 | 176 | ``` 177 | yarn test 178 | ``` 179 | 180 | ## Author 181 | 182 | * [Kevin Scott](https://thekevinscott.com) 183 | 184 | ## License 185 | 186 | This project is licensed under the MIT License - see the LICENSE file for details 187 | 188 | ![](https://ga-beacon.appspot.com/UA-112845439-4/ml-classifier-ui/readme) 189 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ml-classifier-ui-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://thekevinscott.github.io/ml-classifier-ui/", 6 | "dependencies": { 7 | "autoprefixer": "7.1.6", 8 | "babel-jest": "^22.1.0", 9 | "babel-loader": "^7.1.2", 10 | "babel-plugin-named-asset-import": "^0.3.6", 11 | "babel-preset-env": "^1.7.0", 12 | "babel-preset-react": "^6.24.1", 13 | "babel-preset-react-app": "^9.1.2", 14 | "case-sensitive-paths-webpack-plugin": "2.1.1", 15 | "chalk": "1.1.3", 16 | "classnames": "^2.2.6", 17 | "css-loader": "0.28.7", 18 | "dotenv": "4.0.0", 19 | "dotenv-expand": "4.2.0", 20 | "eslint": "^7.4.0", 21 | "eslint-loader": "^4.0.2", 22 | "extract-text-webpack-plugin": "3.0.2", 23 | "file-loader": "0.11.2", 24 | "fork-ts-checker-webpack-plugin": "^0.2.8", 25 | "free-google-image-search": "^1.0.0", 26 | "fs-extra": "3.0.1", 27 | "html-webpack-plugin": "^4.3.0", 28 | "jest": "22.4.2", 29 | "mini-css-extract-plugin": "^0.9.0", 30 | "ml-classifier-ui": "file:../", 31 | "node-sass": "^4.9.0", 32 | "object-assign": "4.1.1", 33 | "optimize-css-assets-webpack-plugin": "^5.0.3", 34 | "pnp-webpack-plugin": "^1.6.4", 35 | "postcss-flexbugs-fixes": "3.2.0", 36 | "postcss-loader": "2.0.8", 37 | "postcss-normalize": "^9.0.0", 38 | "postcss-safe-parser": "^4.0.2", 39 | "promise": "8.0.1", 40 | "raf": "3.4.0", 41 | "react": "^16.4.1", 42 | "react-dev-utils": "^10.2.1", 43 | "react-dom": "^16.4.2", 44 | "react-dropzone": "^4.2.11", 45 | "react-progressbar.js": "^0.2.0", 46 | "resolve": "1.6.0", 47 | "resolve-url-loader": "^3.1.1", 48 | "sass-loader": "^7.0.3", 49 | "source-map-loader": "^0.2.1", 50 | "style-loader": "0.19.0", 51 | "sw-precache-webpack-plugin": "0.11.4", 52 | "terser-webpack-plugin": "^3.0.6", 53 | "ts-jest": "22.0.1", 54 | "ts-loader": "^2.3.7", 55 | "tsconfig-paths-webpack-plugin": "^2.0.0", 56 | "tslint": "^5.7.0", 57 | "tslint-config-prettier": "^1.10.0", 58 | "tslint-react": "^3.2.0", 59 | "uglifyjs-webpack-plugin": "^1.1.8", 60 | "url-loader": "0.6.2", 61 | "webpack": "4.43.0", 62 | "webpack-dev-server": ">=3.1.11", 63 | "webpack-manifest-plugin": "1.3.2", 64 | "whatwg-fetch": "2.0.3", 65 | "workbox-webpack-plugin": "^5.1.3", 66 | "yargs": "^12.0.1" 67 | }, 68 | "scripts": { 69 | "start": "node scripts/start.js", 70 | "build": "node scripts/build.js", 71 | "test": "node scripts/test.js --env=jsdom" 72 | }, 73 | "devDependencies": { 74 | "@types/classnames": "^2.2.4", 75 | "@types/jest": "^23.1.1", 76 | "@types/node": "^10.3.4", 77 | "@types/react": "^16.4.6", 78 | "@types/react-dom": "^16.0.6", 79 | "@types/react-dropzone": "^4.2.0", 80 | "typescript": "^2.9.2" 81 | }, 82 | "babel": { 83 | "presets": [ 84 | "react-app" 85 | ] 86 | }, 87 | "eslintConfig": { 88 | "extends": "react-app" 89 | }, 90 | "browserslist": { 91 | "production": [ 92 | ">0.2%", 93 | "not dead", 94 | "not op_mini all" 95 | ], 96 | "development": [ 97 | "last 1 chrome version", 98 | "last 1 firefox version", 99 | "last 1 safari version" 100 | ] 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /example/public/example-300.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thekevinscott/ml-classifier-ui/f3c81d5ee6c3680bd1da0ee923307fc1f6eaf121/example/public/example-300.gif -------------------------------------------------------------------------------- /example/public/example-600.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thekevinscott/ml-classifier-ui/f3c81d5ee6c3680bd1da0ee923307fc1f6eaf121/example/public/example-600.gif -------------------------------------------------------------------------------- /example/public/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thekevinscott/ml-classifier-ui/f3c81d5ee6c3680bd1da0ee923307fc1f6eaf121/example/public/example.gif -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thekevinscott/ml-classifier-ui/f3c81d5ee6c3680bd1da0ee923307fc1f6eaf121/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Demo of ml-classifier-ui 23 | 24 | 25 | 26 | 33 | 34 | 35 | 36 | 37 | 40 |
41 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /example/public/mobilenet_v1_0.25_224.json: -------------------------------------------------------------------------------- 1 | {"modelTopology": {"keras_version": "2.1.4", "model_config": {"class_name": "Model", "config": {"layers": [{"class_name": "InputLayer", "inbound_nodes": [], "config": {"dtype": "float32", "batch_input_shape": [null, 224, 224, 3], "name": "input_1", "sparse": false}, "name": "input_1"}, {"class_name": "Conv2D", "inbound_nodes": [[["input_1", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv1", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [2, 2], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 8, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv1"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv1", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv1_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv1_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv1_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv1_relu"}, "name": "conv1_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv1_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_1", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [1, 1], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_1"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_1", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_1_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_1_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_1_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_1_relu"}, "name": "conv_dw_1_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_1_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_1", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 16, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_1"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_1", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_1_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_1_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_1_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_1_relu"}, "name": "conv_pw_1_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_1_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_2", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [2, 2], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_2"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_2", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_2_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_2_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_2_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_2_relu"}, "name": "conv_dw_2_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_2_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_2", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 32, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_2"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_2", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_2_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_2_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_2_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_2_relu"}, "name": "conv_pw_2_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_2_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_3", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [1, 1], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_3"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_3", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_3_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_3_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_3_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_3_relu"}, "name": "conv_dw_3_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_3_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_3", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 32, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_3"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_3", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_3_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_3_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_3_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_3_relu"}, "name": "conv_pw_3_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_3_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_4", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [2, 2], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_4"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_4", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_4_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_4_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_4_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_4_relu"}, "name": "conv_dw_4_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_4_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_4", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 64, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_4"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_4", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_4_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_4_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_4_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_4_relu"}, "name": "conv_pw_4_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_4_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_5", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [1, 1], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_5"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_5", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_5_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_5_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_5_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_5_relu"}, "name": "conv_dw_5_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_5_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_5", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 64, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_5"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_5", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_5_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_5_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_5_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_5_relu"}, "name": "conv_pw_5_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_5_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_6", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [2, 2], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_6"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_6", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_6_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_6_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_6_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_6_relu"}, "name": "conv_dw_6_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_6_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_6", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 128, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_6"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_6", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_6_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_6_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_6_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_6_relu"}, "name": "conv_pw_6_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_6_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_7", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [1, 1], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_7"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_7", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_7_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_7_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_7_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_7_relu"}, "name": "conv_dw_7_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_7_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_7", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 128, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_7"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_7", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_7_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_7_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_7_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_7_relu"}, "name": "conv_pw_7_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_7_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_8", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [1, 1], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_8"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_8", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_8_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_8_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_8_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_8_relu"}, "name": "conv_dw_8_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_8_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_8", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 128, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_8"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_8", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_8_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_8_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_8_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_8_relu"}, "name": "conv_pw_8_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_8_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_9", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [1, 1], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_9"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_9", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_9_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_9_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_9_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_9_relu"}, "name": "conv_dw_9_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_9_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_9", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 128, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_9"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_9", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_9_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_9_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_9_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_9_relu"}, "name": "conv_pw_9_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_9_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_10", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [1, 1], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_10"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_10", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_10_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_10_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_10_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_10_relu"}, "name": "conv_dw_10_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_10_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_10", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 128, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_10"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_10", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_10_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_10_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_10_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_10_relu"}, "name": "conv_pw_10_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_10_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_11", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [1, 1], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_11"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_11", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_11_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_11_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_11_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_11_relu"}, "name": "conv_dw_11_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_11_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_11", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 128, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_11"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_11", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_11_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_11_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_11_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_11_relu"}, "name": "conv_pw_11_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_11_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_12", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [2, 2], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_12"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_12", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_12_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_12_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_12_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_12_relu"}, "name": "conv_dw_12_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_12_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_12", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 256, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_12"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_12", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_12_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_12_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_12_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_12_relu"}, "name": "conv_pw_12_relu"}, {"class_name": "DepthwiseConv2D", "inbound_nodes": [[["conv_pw_12_relu", 0, 0, {}]]], "config": {"padding": "same", "depth_multiplier": 1, "name": "conv_dw_13", "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "depthwise_constraint": null, "strides": [1, 1], "dilation_rate": [1, 1], "depthwise_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "depthwise_regularizer": null, "use_bias": false, "activity_regularizer": null, "kernel_size": [3, 3]}, "name": "conv_dw_13"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_dw_13", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_dw_13_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_dw_13_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_dw_13_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_dw_13_relu"}, "name": "conv_dw_13_relu"}, {"class_name": "Conv2D", "inbound_nodes": [[["conv_dw_13_relu", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_pw_13", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 256, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": false, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_pw_13"}, {"class_name": "BatchNormalization", "inbound_nodes": [[["conv_pw_13", 0, 0, {}]]], "config": {"gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "name": "conv_pw_13_bn", "epsilon": 0.001, "trainable": true, "center": true, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_initializer": {"class_name": "Zeros", "config": {}}, "scale": true, "gamma_regularizer": null, "gamma_constraint": null, "beta_constraint": null, "beta_regularizer": null, "momentum": 0.99, "axis": -1}, "name": "conv_pw_13_bn"}, {"class_name": "Activation", "inbound_nodes": [[["conv_pw_13_bn", 0, 0, {}]]], "config": {"activation": "relu6", "trainable": true, "name": "conv_pw_13_relu"}, "name": "conv_pw_13_relu"}, {"class_name": "GlobalAveragePooling2D", "inbound_nodes": [[["conv_pw_13_relu", 0, 0, {}]]], "config": {"trainable": true, "name": "global_average_pooling2d_1", "data_format": "channels_last"}, "name": "global_average_pooling2d_1"}, {"class_name": "Reshape", "inbound_nodes": [[["global_average_pooling2d_1", 0, 0, {}]]], "config": {"target_shape": [1, 1, 256], "trainable": true, "name": "reshape_1"}, "name": "reshape_1"}, {"class_name": "Dropout", "inbound_nodes": [[["reshape_1", 0, 0, {}]]], "config": {"rate": 0.001, "noise_shape": null, "trainable": true, "seed": null, "name": "dropout"}, "name": "dropout"}, {"class_name": "Conv2D", "inbound_nodes": [[["dropout", 0, 0, {}]]], "config": {"kernel_initializer": {"class_name": "VarianceScaling", "config": {"distribution": "uniform", "scale": 1.0, "seed": null, "mode": "fan_avg"}}, "name": "conv_preds", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "activation": "linear", "trainable": true, "data_format": "channels_last", "padding": "same", "strides": [1, 1], "dilation_rate": [1, 1], "kernel_regularizer": null, "filters": 1000, "bias_initializer": {"class_name": "Zeros", "config": {}}, "use_bias": true, "activity_regularizer": null, "kernel_size": [1, 1]}, "name": "conv_preds"}, {"class_name": "Activation", "inbound_nodes": [[["conv_preds", 0, 0, {}]]], "config": {"activation": "softmax", "trainable": true, "name": "act_softmax"}, "name": "act_softmax"}, {"class_name": "Reshape", "inbound_nodes": [[["act_softmax", 0, 0, {}]]], "config": {"target_shape": [1000], "trainable": true, "name": "reshape_2"}, "name": "reshape_2"}], "input_layers": [["input_1", 0, 0]], "name": "mobilenet_0.25_224", "output_layers": [["reshape_2", 0, 0]]}}, "backend": "tensorflow"}, "weightsManifest": [{"paths": ["group1-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 3, 8], "name": "conv1/kernel"}]}, {"paths": ["group2-shard1of1"], "weights": [{"dtype": "float32", "shape": [8], "name": "conv1_bn/gamma"}, {"dtype": "float32", "shape": [8], "name": "conv1_bn/beta"}, {"dtype": "float32", "shape": [8], "name": "conv1_bn/moving_mean"}, {"dtype": "float32", "shape": [8], "name": "conv1_bn/moving_variance"}]}, {"paths": ["group3-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 8, 1], "name": "conv_dw_1/depthwise_kernel"}]}, {"paths": ["group4-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 128, 1], "name": "conv_dw_10/depthwise_kernel"}]}, {"paths": ["group5-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_dw_10_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_10_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_10_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_10_bn/moving_variance"}]}, {"paths": ["group6-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 128, 1], "name": "conv_dw_11/depthwise_kernel"}]}, {"paths": ["group7-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_dw_11_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_11_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_11_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_11_bn/moving_variance"}]}, {"paths": ["group8-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 128, 1], "name": "conv_dw_12/depthwise_kernel"}]}, {"paths": ["group9-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_dw_12_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_12_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_12_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_12_bn/moving_variance"}]}, {"paths": ["group10-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 256, 1], "name": "conv_dw_13/depthwise_kernel"}]}, {"paths": ["group11-shard1of1"], "weights": [{"dtype": "float32", "shape": [256], "name": "conv_dw_13_bn/gamma"}, {"dtype": "float32", "shape": [256], "name": "conv_dw_13_bn/beta"}, {"dtype": "float32", "shape": [256], "name": "conv_dw_13_bn/moving_mean"}, {"dtype": "float32", "shape": [256], "name": "conv_dw_13_bn/moving_variance"}]}, {"paths": ["group12-shard1of1"], "weights": [{"dtype": "float32", "shape": [8], "name": "conv_dw_1_bn/gamma"}, {"dtype": "float32", "shape": [8], "name": "conv_dw_1_bn/beta"}, {"dtype": "float32", "shape": [8], "name": "conv_dw_1_bn/moving_mean"}, {"dtype": "float32", "shape": [8], "name": "conv_dw_1_bn/moving_variance"}]}, {"paths": ["group13-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 16, 1], "name": "conv_dw_2/depthwise_kernel"}]}, {"paths": ["group14-shard1of1"], "weights": [{"dtype": "float32", "shape": [16], "name": "conv_dw_2_bn/gamma"}, {"dtype": "float32", "shape": [16], "name": "conv_dw_2_bn/beta"}, {"dtype": "float32", "shape": [16], "name": "conv_dw_2_bn/moving_mean"}, {"dtype": "float32", "shape": [16], "name": "conv_dw_2_bn/moving_variance"}]}, {"paths": ["group15-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 32, 1], "name": "conv_dw_3/depthwise_kernel"}]}, {"paths": ["group16-shard1of1"], "weights": [{"dtype": "float32", "shape": [32], "name": "conv_dw_3_bn/gamma"}, {"dtype": "float32", "shape": [32], "name": "conv_dw_3_bn/beta"}, {"dtype": "float32", "shape": [32], "name": "conv_dw_3_bn/moving_mean"}, {"dtype": "float32", "shape": [32], "name": "conv_dw_3_bn/moving_variance"}]}, {"paths": ["group17-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 32, 1], "name": "conv_dw_4/depthwise_kernel"}]}, {"paths": ["group18-shard1of1"], "weights": [{"dtype": "float32", "shape": [32], "name": "conv_dw_4_bn/gamma"}, {"dtype": "float32", "shape": [32], "name": "conv_dw_4_bn/beta"}, {"dtype": "float32", "shape": [32], "name": "conv_dw_4_bn/moving_mean"}, {"dtype": "float32", "shape": [32], "name": "conv_dw_4_bn/moving_variance"}]}, {"paths": ["group19-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 64, 1], "name": "conv_dw_5/depthwise_kernel"}]}, {"paths": ["group20-shard1of1"], "weights": [{"dtype": "float32", "shape": [64], "name": "conv_dw_5_bn/gamma"}, {"dtype": "float32", "shape": [64], "name": "conv_dw_5_bn/beta"}, {"dtype": "float32", "shape": [64], "name": "conv_dw_5_bn/moving_mean"}, {"dtype": "float32", "shape": [64], "name": "conv_dw_5_bn/moving_variance"}]}, {"paths": ["group21-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 64, 1], "name": "conv_dw_6/depthwise_kernel"}]}, {"paths": ["group22-shard1of1"], "weights": [{"dtype": "float32", "shape": [64], "name": "conv_dw_6_bn/gamma"}, {"dtype": "float32", "shape": [64], "name": "conv_dw_6_bn/beta"}, {"dtype": "float32", "shape": [64], "name": "conv_dw_6_bn/moving_mean"}, {"dtype": "float32", "shape": [64], "name": "conv_dw_6_bn/moving_variance"}]}, {"paths": ["group23-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 128, 1], "name": "conv_dw_7/depthwise_kernel"}]}, {"paths": ["group24-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_dw_7_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_7_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_7_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_7_bn/moving_variance"}]}, {"paths": ["group25-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 128, 1], "name": "conv_dw_8/depthwise_kernel"}]}, {"paths": ["group26-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_dw_8_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_8_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_8_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_8_bn/moving_variance"}]}, {"paths": ["group27-shard1of1"], "weights": [{"dtype": "float32", "shape": [3, 3, 128, 1], "name": "conv_dw_9/depthwise_kernel"}]}, {"paths": ["group28-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_dw_9_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_9_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_9_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_dw_9_bn/moving_variance"}]}, {"paths": ["group29-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 256, 1000], "name": "conv_preds/kernel"}, {"dtype": "float32", "shape": [1000], "name": "conv_preds/bias"}]}, {"paths": ["group30-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 8, 16], "name": "conv_pw_1/kernel"}]}, {"paths": ["group31-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 128, 128], "name": "conv_pw_10/kernel"}]}, {"paths": ["group32-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_pw_10_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_10_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_10_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_10_bn/moving_variance"}]}, {"paths": ["group33-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 128, 128], "name": "conv_pw_11/kernel"}]}, {"paths": ["group34-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_pw_11_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_11_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_11_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_11_bn/moving_variance"}]}, {"paths": ["group35-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 128, 256], "name": "conv_pw_12/kernel"}]}, {"paths": ["group36-shard1of1"], "weights": [{"dtype": "float32", "shape": [256], "name": "conv_pw_12_bn/gamma"}, {"dtype": "float32", "shape": [256], "name": "conv_pw_12_bn/beta"}, {"dtype": "float32", "shape": [256], "name": "conv_pw_12_bn/moving_mean"}, {"dtype": "float32", "shape": [256], "name": "conv_pw_12_bn/moving_variance"}]}, {"paths": ["group37-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 256, 256], "name": "conv_pw_13/kernel"}]}, {"paths": ["group38-shard1of1"], "weights": [{"dtype": "float32", "shape": [256], "name": "conv_pw_13_bn/gamma"}, {"dtype": "float32", "shape": [256], "name": "conv_pw_13_bn/beta"}, {"dtype": "float32", "shape": [256], "name": "conv_pw_13_bn/moving_mean"}, {"dtype": "float32", "shape": [256], "name": "conv_pw_13_bn/moving_variance"}]}, {"paths": ["group39-shard1of1"], "weights": [{"dtype": "float32", "shape": [16], "name": "conv_pw_1_bn/gamma"}, {"dtype": "float32", "shape": [16], "name": "conv_pw_1_bn/beta"}, {"dtype": "float32", "shape": [16], "name": "conv_pw_1_bn/moving_mean"}, {"dtype": "float32", "shape": [16], "name": "conv_pw_1_bn/moving_variance"}]}, {"paths": ["group40-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 16, 32], "name": "conv_pw_2/kernel"}]}, {"paths": ["group41-shard1of1"], "weights": [{"dtype": "float32", "shape": [32], "name": "conv_pw_2_bn/gamma"}, {"dtype": "float32", "shape": [32], "name": "conv_pw_2_bn/beta"}, {"dtype": "float32", "shape": [32], "name": "conv_pw_2_bn/moving_mean"}, {"dtype": "float32", "shape": [32], "name": "conv_pw_2_bn/moving_variance"}]}, {"paths": ["group42-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 32, 32], "name": "conv_pw_3/kernel"}]}, {"paths": ["group43-shard1of1"], "weights": [{"dtype": "float32", "shape": [32], "name": "conv_pw_3_bn/gamma"}, {"dtype": "float32", "shape": [32], "name": "conv_pw_3_bn/beta"}, {"dtype": "float32", "shape": [32], "name": "conv_pw_3_bn/moving_mean"}, {"dtype": "float32", "shape": [32], "name": "conv_pw_3_bn/moving_variance"}]}, {"paths": ["group44-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 32, 64], "name": "conv_pw_4/kernel"}]}, {"paths": ["group45-shard1of1"], "weights": [{"dtype": "float32", "shape": [64], "name": "conv_pw_4_bn/gamma"}, {"dtype": "float32", "shape": [64], "name": "conv_pw_4_bn/beta"}, {"dtype": "float32", "shape": [64], "name": "conv_pw_4_bn/moving_mean"}, {"dtype": "float32", "shape": [64], "name": "conv_pw_4_bn/moving_variance"}]}, {"paths": ["group46-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 64, 64], "name": "conv_pw_5/kernel"}]}, {"paths": ["group47-shard1of1"], "weights": [{"dtype": "float32", "shape": [64], "name": "conv_pw_5_bn/gamma"}, {"dtype": "float32", "shape": [64], "name": "conv_pw_5_bn/beta"}, {"dtype": "float32", "shape": [64], "name": "conv_pw_5_bn/moving_mean"}, {"dtype": "float32", "shape": [64], "name": "conv_pw_5_bn/moving_variance"}]}, {"paths": ["group48-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 64, 128], "name": "conv_pw_6/kernel"}]}, {"paths": ["group49-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_pw_6_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_6_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_6_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_6_bn/moving_variance"}]}, {"paths": ["group50-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 128, 128], "name": "conv_pw_7/kernel"}]}, {"paths": ["group51-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_pw_7_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_7_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_7_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_7_bn/moving_variance"}]}, {"paths": ["group52-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 128, 128], "name": "conv_pw_8/kernel"}]}, {"paths": ["group53-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_pw_8_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_8_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_8_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_8_bn/moving_variance"}]}, {"paths": ["group54-shard1of1"], "weights": [{"dtype": "float32", "shape": [1, 1, 128, 128], "name": "conv_pw_9/kernel"}]}, {"paths": ["group55-shard1of1"], "weights": [{"dtype": "float32", "shape": [128], "name": "conv_pw_9_bn/gamma"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_9_bn/beta"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_9_bn/moving_mean"}, {"dtype": "float32", "shape": [128], "name": "conv_pw_9_bn/moving_variance"}]}]} -------------------------------------------------------------------------------- /example/src/containers/App/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './styles.scss'; 3 | import MLClassifierUI from 'mlClassifierUI'; 4 | import Search, { 5 | IImage, 6 | } from '../Search'; 7 | 8 | const CORS_BYPASS = 'https://fast-cove-30289.herokuapp.com/'; 9 | 10 | const qs: { 11 | SHOW_HELP?: string; 12 | SHOW_DOWNLOAD?: string; 13 | } = (window.location.search.split('?').pop() || '').split('&').filter(p => p).map(p => p.split('=')).reduce((obj, [key, val]) => ({ 14 | ...obj, 15 | [key]: (val === "1" || val === "true") ? true : false, 16 | }), {}); 17 | 18 | const SHOW_HELP = qs.SHOW_HELP !== undefined ? qs.SHOW_HELP : true; 19 | const SHOW_DOWNLOAD = qs.SHOW_DOWNLOAD !== undefined ? qs.SHOW_DOWNLOAD : true; 20 | 21 | if (SHOW_HELP === true) { 22 | document.body.innerHTML += ``; 23 | } 24 | 25 | const splitImagesFromLabels = async (images: IImage[]) => { 26 | const origData: { 27 | images: string[]; 28 | labels: string[]; 29 | } = { 30 | images: [], 31 | labels: [], 32 | }; 33 | 34 | return images.reduce((data, image: IImage) => ({ 35 | images: data.images.concat(`${CORS_BYPASS}${image.src}`), 36 | // images: data.images.concat(`${image.src}`), 37 | labels: data.labels.concat(image.label), 38 | }), origData); 39 | } 40 | 41 | interface IState { 42 | training: boolean; 43 | evalImages?: IImage[]; 44 | } 45 | 46 | class App extends React.Component { 47 | public state: IState = { 48 | training: false, 49 | evalImages: undefined, 50 | }; 51 | 52 | private classifier:any; 53 | 54 | public getMLClassifier = (classifier: any) => { 55 | this.classifier = classifier; 56 | } 57 | 58 | public onBeginTraining = () => { 59 | this.setState({ 60 | training: true, 61 | }); 62 | } 63 | 64 | public train = async (trainImages: IImage[], evalImages?: IImage[]) => { 65 | this.onBeginTraining(); 66 | const { 67 | images, 68 | labels, 69 | } = await splitImagesFromLabels(trainImages); 70 | 71 | this.setState({ 72 | evalImages, 73 | }); 74 | 75 | await this.classifier.addData(images, labels, 'train'); 76 | } 77 | 78 | public onTrainComplete = async () => { 79 | if (this.state.evalImages && this.state.evalImages.length) { 80 | const { 81 | images, 82 | labels, 83 | } = await splitImagesFromLabels(this.state.evalImages); 84 | 85 | for (let i = 0; i < images.length; i++) { 86 | const src = images[i]; 87 | const label = labels[i]; 88 | 89 | this.classifier.predict(src, label); 90 | 91 | // const prediction = await this.predictSingleImage(src, label); 92 | // callback({ 93 | // src, 94 | // label, 95 | // prediction, 96 | // }); 97 | } 98 | 99 | // return await this.classifier.addData(images, labels, 'eval'); 100 | } 101 | } 102 | 103 | public render() { 104 | return ( 105 | 106 |
107 |
108 | 114 |
115 | {SHOW_HELP && this.state.training === false && ( 116 |
117 |

Instructions

118 |

119 | Drag and drop some labeled images below to begin training your classifier. You can download image datasets here. 120 |

121 |

Organize your images into folders, where the folders' names are the desired labels.

122 |
123 | 124 | 125 | 126 |
127 |
128 | )} 129 |
130 | {SHOW_HELP && this.state.training === false && ( 131 | 132 |
133 |
134 |

Don't have any images handy? Search below for some images. Select up to 10 that match your query.

135 |

Note This can be a little buggy at the moment due to CORS issues. Working on making it better!

136 |
137 | 140 |
141 | )} 142 |
143 | ); 144 | } 145 | } 146 | 147 | export default App; 148 | -------------------------------------------------------------------------------- /example/src/containers/App/styles.scss: -------------------------------------------------------------------------------- 1 | .app { 2 | display: flex; 3 | flex-direction: row; 4 | width: 300px; 5 | display: flex; 6 | margin-right: 20px; 7 | 8 | &.centeredApp { 9 | margin-right: 0; 10 | } 11 | } 12 | 13 | .classifierContainer { 14 | display: flex; 15 | flex-direction: row; 16 | 17 | .imgContainer { 18 | max-height: 400px; 19 | border-radius: 5px; 20 | overflow: hidden; 21 | box-shadow: 0 2px 4px rgba(0,0,0,0.2); 22 | } 23 | 24 | img { 25 | height: 100%; 26 | max-height: 600px; 27 | } 28 | 29 | &.center { 30 | justify-content: center; 31 | } 32 | } 33 | 34 | .info { 35 | margin: 0 20px 0 0; 36 | 37 | h2 { 38 | margin-top: 0; 39 | } 40 | 41 | a { 42 | color: #100382bd; 43 | text-decoration: underline; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /example/src/containers/Search/Topic.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './styles.scss'; 3 | import loading from './loading.gif'; 4 | import { 5 | IImgurImage, 6 | } from './getImages'; 7 | 8 | const Topic = ({ 9 | topic, 10 | images, 11 | picked, 12 | handlePicked, 13 | handleRemove, 14 | }: { 15 | topic: string; 16 | images: IImgurImage[]; 17 | picked: string[]; 18 | handlePicked: (topic: string) => (id: string) => () => void; 19 | handleRemove: (topic: string) => (id: string) => () => void; 20 | }) => ( 21 |
22 |
23 | {images.length ? ( 24 | 31 | ) : ( 32 | 33 | )} 34 |
35 |
36 | ); 37 | 38 | const splitImages = (images: IImgurImage[], picked: string[]) => { 39 | const startingData: { 40 | pickedImages: IImgurImage[]; 41 | unpickedImages: IImgurImage[]; 42 | } = { 43 | pickedImages: [], 44 | unpickedImages: [], 45 | }; 46 | return images.reduce(({ 47 | pickedImages, 48 | unpickedImages, 49 | }, image: IImgurImage) => { 50 | if (picked.includes(image.id)) { 51 | return { 52 | unpickedImages, 53 | pickedImages: [ 54 | ...pickedImages, 55 | image, 56 | ], 57 | }; 58 | } 59 | 60 | return { 61 | pickedImages, 62 | unpickedImages: [ 63 | ...unpickedImages, 64 | image, 65 | ], 66 | }; 67 | }, startingData); 68 | }; 69 | 70 | const Images = ({ 71 | title, 72 | images, 73 | picked, 74 | handlePicked, 75 | handleRemove, 76 | }: { 77 | title: string; 78 | images: IImgurImage[]; 79 | picked: string[]; 80 | handlePicked: (id: string) => () => void; 81 | handleRemove: (id: string) => () => void; 82 | }) => { 83 | const { 84 | pickedImages, 85 | unpickedImages, 86 | } = splitImages(images, picked); 87 | 88 | const training = pickedImages.slice(0, 5); 89 | const validation = pickedImages.slice(5); 90 | 91 | return ( 92 | 93 |
94 | 95 | {training.length > 0 ? ( 96 | 101 | ): null} 102 | {validation.length > 0 ? ( 103 | 108 | ): null} 109 |
110 | {pickedImages.length < 10 ? ( 111 |
112 | {unpickedImages.map((image: IImgurImage) => ( 113 | 118 | ))} 119 |
120 | ) :

All done with these images!

} 121 |
122 | ); 123 | }; 124 | 125 | interface IImageSetProps { 126 | title: string; 127 | images: IImgurImage[]; 128 | handleRemove: (id: string) => () => void; 129 | } 130 | 131 | interface IState { 132 | mounted: boolean; 133 | } 134 | 135 | class ImageSet extends React.Component { 136 | public state: IState = { 137 | mounted: false, 138 | }; 139 | 140 | public componentDidMount() { 141 | setTimeout(() => { 142 | this.setState({ 143 | mounted: true, 144 | }); 145 | }, 10); 146 | } 147 | 148 | public render() { 149 | const { 150 | images, 151 | title, 152 | handleRemove 153 | } = this.props; 154 | 155 | const className = `${styles.set} ${this.state.mounted ? styles.show : ''}`; 156 | 157 | return ( 158 |
159 | 160 | {images.map((image: IImgurImage) => ( 161 | 165 | 166 | 167 | ))} 168 |
169 | ); 170 | } 171 | } 172 | 173 | const SearchImage = ({ 174 | src, 175 | handleClick, 176 | children, 177 | }: { 178 | src: string; 179 | handleClick?: () => void; 180 | children?: any; 181 | }) => ( 182 |
183 | {children} 184 | 185 |
186 | ); 187 | 188 | export default Topic; 189 | -------------------------------------------------------------------------------- /example/src/containers/Search/getImages.ts: -------------------------------------------------------------------------------- 1 | const clientID = '21211812267bd50'; 2 | 3 | interface IData { 4 | data: IImgurDatum[]; 5 | } 6 | 7 | interface IImgurDatum { 8 | id: string; 9 | images: IImgurImage[]; 10 | } 11 | 12 | export interface IImgurImage { 13 | id: string; 14 | link: string; 15 | size: number; 16 | width: number; 17 | height: number; 18 | gifv?: string; 19 | type: string; 20 | } 21 | 22 | const getImages = (q: string) => fetch(`https://api.imgur.com/3/gallery/search?q=${q}`, { 23 | headers: { 24 | Authorization: `Client-ID ${clientID}`, 25 | }, 26 | }).then(resp => resp.json()).then(({ data }: IData) => { 27 | const startingData: IImgurImage[] = []; 28 | const c = data.reduce((collectedImages, { images }) => { 29 | if (!images) { 30 | return collectedImages; 31 | } 32 | 33 | return collectedImages.concat(images); 34 | }, startingData); 35 | 36 | return c.filter((image: IImgurImage) => { 37 | return image && !image.gifv && [ 38 | 'image/jpeg', 39 | 'image/png', 40 | 'image/gif', 41 | ].includes(image.type); 42 | }); 43 | // .map((image: IImgurImage) => image.link); 44 | }); 45 | 46 | export default getImages; 47 | -------------------------------------------------------------------------------- /example/src/containers/Search/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './styles.scss'; 3 | import getImages, { 4 | IImgurImage, 5 | } from './getImages'; 6 | import Topic from './Topic'; 7 | 8 | interface IProps { 9 | train: (images: IImage[], evalImages: IImage[]) => {}; 10 | } 11 | 12 | export interface IImage { 13 | src: string; 14 | label: string; 15 | } 16 | 17 | interface IState { 18 | value: string; 19 | topics: string[]; 20 | searches: { 21 | [index: string]: IImgurImage[]; 22 | } 23 | picked: { 24 | [index: string]: string[]; 25 | } 26 | } 27 | 28 | class Search extends React.Component { 29 | public state: IState = { 30 | value: '', 31 | topics: [], 32 | searches: { }, 33 | picked: { }, 34 | }; 35 | 36 | public handleKeyDown = (e:React.KeyboardEvent) => { 37 | if (e.key === 'Enter') { 38 | e.preventDefault(); 39 | const value = this.state.value; 40 | this.setState({ 41 | topics: [ 42 | ...this.state.topics, 43 | value, 44 | ], 45 | searches: { 46 | [value]: [], 47 | ...this.state.searches, 48 | }, 49 | picked: { 50 | [value]: [], 51 | ...this.state.picked, 52 | }, 53 | value: '', 54 | }); 55 | getImages(this.state.value).then(images => { 56 | this.setState({ 57 | searches: { 58 | ...this.state.searches, 59 | [value]: images, 60 | }, 61 | }); 62 | }); 63 | } else if (e.key === 'Backspace' && this.state.value === '' && this.state.topics.length > 0) { 64 | this.setState({ 65 | topics: this.state.topics.slice(0, -1), 66 | }); 67 | } 68 | } 69 | 70 | public handleChange = (e:React.ChangeEvent) => { 71 | this.setState({ 72 | value: e.target.value, 73 | }); 74 | }; 75 | 76 | public train = (e:React.MouseEvent) => { 77 | 78 | const images = transformImages(this.state.picked, this.state.searches, 0, 5); 79 | const evalImages = transformImages(this.state.picked, this.state.searches, 5, 10); 80 | 81 | this.props.train(images, evalImages); 82 | } 83 | 84 | // public componentDidMount() { 85 | // this.props.train([{ 86 | // label: 'cat', 87 | // src: 'https://i.imgur.com/40QdFBf.jpg', 88 | // }, { 89 | // label: 'cat', 90 | // src: 'https://imgur.com/cnAtiVO.jpg', 91 | // }, { 92 | // label: 'cat', 93 | // src: 'https://i.imgur.com/6Z4v0Xy.jpg', 94 | // }, { 95 | // label: 'cat', 96 | // src: 'https://i.imgur.com/5HvlY3r.jpg', 97 | // }, { 98 | // label: 'cat', 99 | // src: 'https://i.imgur.com/aHOLAm4.jpg', 100 | // }, { 101 | // label: 'dog', 102 | // src: 'https://i.imgur.com/ALmkDwN.jpg', 103 | // }, { 104 | // label: 'dog', 105 | // src: 'https://i.imgur.com/JpGAInT.jpg', 106 | // }, { 107 | // label: 'dog', 108 | // src: 'https://i.imgur.com/vdrzl74.jpg', 109 | // }, { 110 | // label: 'dog', 111 | // src: 'https://i.imgur.com/xFczRzG.jpg', 112 | // }, { 113 | // label: 'dog', 114 | // src: 'https://i.imgur.com/nBpidmQ.jpg', 115 | // }], [ 116 | 117 | 118 | 119 | // { 120 | // label: 'dog', 121 | // src: 'https://i.imgur.com/nZg4Nn8.jpg', 122 | // }, { 123 | // label: 'cat', 124 | // src: 'https://i.imgur.com/LyH5RK2.jpg', 125 | // }, { 126 | // label: 'cat', 127 | // src: 'https://i.imgur.com/MNHiUjf.jpg', 128 | // }]); 129 | // } 130 | 131 | public handleRemoveTopic = (idx: number) => (e:React.MouseEvent) => { 132 | e.preventDefault(); 133 | 134 | this.setState({ 135 | topics: this.state.topics.reduce((topics: string[], topic, topicId) => { 136 | if (topicId === idx) { 137 | return topics; 138 | } 139 | 140 | return topics.concat(topic); 141 | }, []), 142 | }); 143 | } 144 | 145 | public handleSelectTopicImage = (topic: string) => (id: string) => () => { 146 | if (this.state.picked[topic].length < 10) { 147 | this.setState({ 148 | picked: { 149 | ...this.state.picked, 150 | [topic]: (this.state.picked[topic] || []).concat(id), 151 | }, 152 | }); 153 | } 154 | } 155 | 156 | public handleRemoveTopicImage = (topic: string) => (id: string) => () => { 157 | this.setState({ 158 | picked: { 159 | ...this.state.picked, 160 | [topic]: (this.state.picked[topic] || []).reduce((pickeds: string[], picked) => { 161 | if (picked === id) { 162 | return pickeds; 163 | } 164 | 165 | return pickeds.concat(picked); 166 | }, []), 167 | }, 168 | }); 169 | } 170 | 171 | public render() { 172 | const disabled = this.state.topics.length < 2; 173 | return ( 174 |
175 |
176 | {this.state.topics.map((topic, idx) => ( 177 |
178 | 179 | {topic} 180 |
181 | ))} 182 | 189 | Search provided by Imgur 190 |
191 | {this.state.topics.map((topic, key) => ( 192 | 200 | ))} 201 | 208 |
209 | ); 210 | } 211 | } 212 | 213 | const transformImages = ( 214 | picked: { [index: string]: string[] }, 215 | searches: { [index: string]: IImgurImage[]}, 216 | start: number, 217 | end?: number 218 | ) => { 219 | const originalData: Array<{ 220 | src: string; 221 | label: string; 222 | }> = []; 223 | 224 | return Object.entries(picked).reduce((collected, [label, pickedImages]) => { 225 | return collected.concat(pickedImages.slice(start, end).map((imageId: string) => { 226 | const image = searches[label].filter(({ id }: IImgurImage) => id === imageId)[0]; 227 | 228 | return { 229 | src: image.link, 230 | label, 231 | }; 232 | })); 233 | }, originalData); 234 | } 235 | 236 | export default Search; 237 | -------------------------------------------------------------------------------- /example/src/containers/Search/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thekevinscott/ml-classifier-ui/f3c81d5ee6c3680bd1da0ee923307fc1f6eaf121/example/src/containers/Search/loading.gif -------------------------------------------------------------------------------- /example/src/containers/Search/styles.scss: -------------------------------------------------------------------------------- 1 | .search { 2 | flex: 1; 3 | 4 | .input { 5 | position: relative; 6 | border: 1px solid rgba(0,0,0,0.2); 7 | border-radius: 4px; 8 | display: flex; 9 | margin-bottom: 20px; 10 | 11 | small { 12 | color: rgba(0,0,0,0.6); 13 | position: absolute; 14 | top: 45px; 15 | bottom: 0px; 16 | font-size: 10px; 17 | right: 0px; 18 | } 19 | } 20 | 21 | input { 22 | flex: 1; 23 | display: block; 24 | background: transparent; 25 | border: none; 26 | font-size: 16px; 27 | padding: 10px 10px; 28 | box-sizing: border-box; 29 | outline: none; 30 | } 31 | 32 | button { 33 | padding: 10px 20px; 34 | background: rgba(90,90,255,0.15); 35 | border: 1px solid rgba(0,0,0,0.1); 36 | border-radius: 5px; 37 | font-size: 16px; 38 | outline: none; 39 | 40 | &.add { 41 | $size: 16px; 42 | font-size: 14px; 43 | border: none; 44 | display: flex; 45 | height: $size; 46 | padding: 0; 47 | top: 2px; 48 | left: 2px; 49 | width: $size; 50 | justify-content: center; 51 | align-items: center; 52 | position: absolute; 53 | background: white; 54 | border-radius: $size; 55 | } 56 | } 57 | } 58 | 59 | .topic { 60 | background: rgba(50,50,255,0.04); 61 | border: 1px solid rgba(50,50,255,0.2); 62 | border-radius: 4px; 63 | margin: 5px; 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | padding: 0 10px 0 0; 68 | 69 | button { 70 | background: none; 71 | border: none; 72 | margin: 0; 73 | padding: 0 5px; 74 | font-size: 20px; 75 | color: rgba(0,0,0,0.6); 76 | margin-top: -2px; 77 | font-size: 16px; 78 | outline: none; 79 | cursor: pointer; 80 | 81 | } 82 | } 83 | 84 | .images { 85 | border: 1px solid rgba(0,0,0,0.1); 86 | padding: 10px; 87 | border-radius: 5px; 88 | margin: 10px 0; 89 | max-height: 460px; 90 | overflow-y: scroll; 91 | 92 | label { 93 | display: block; 94 | font-weight: bold; 95 | margin: 0; 96 | // margin-bottom: 5px; 97 | // margin-top: 20px; 98 | } 99 | } 100 | 101 | .loading { 102 | width: 16px; 103 | height: 16px; 104 | } 105 | 106 | .carousel { 107 | .set { 108 | margin-left: 10px; 109 | display: flex; 110 | flex-direction: row; 111 | border-radius: 5px; 112 | transition-duration: 0.4s; 113 | width: 100%; 114 | max-height: 0; 115 | overflow-x: scroll; 116 | margin-bottom: 20px; 117 | 118 | label { 119 | position: absolute; 120 | z-index: 1; 121 | background: rgba(255,255,255,0.8); 122 | font-size: 12px; 123 | display: block; 124 | color: rgba(0,0,0,0.8); 125 | margin-right: 5px; 126 | } 127 | 128 | &.show { 129 | max-height: 200px; 130 | } 131 | } 132 | 133 | img { 134 | max-width: 100px; 135 | max-height: 100px; 136 | display: block; 137 | } 138 | } 139 | 140 | .trainModel { 141 | margin-top: 20px; 142 | } 143 | 144 | .image { 145 | display: inline-block; 146 | position: relative; 147 | cursor: pointer; 148 | margin-right: 5px; 149 | 150 | img { 151 | display: block; 152 | } 153 | 154 | button { 155 | position: absolute; 156 | height: 100%; 157 | width: 100%; 158 | opacity: 0; 159 | transition-duration: 0.2s; 160 | background: rgba(0,0,0,0.6); 161 | font-size: 60px; 162 | border-radius: 0; 163 | color: white; 164 | 165 | &:hover { 166 | opacity: 1; 167 | } 168 | } 169 | } 170 | 171 | .top { 172 | display: flex; 173 | min-height: 100px; 174 | } 175 | -------------------------------------------------------------------------------- /example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from 'containers/App'; 4 | import './index.css'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') as HTMLElement, 9 | ); 10 | -------------------------------------------------------------------------------- /example/src/mlClassifierUI.ts: -------------------------------------------------------------------------------- 1 | import '../../dist/index.css'; 2 | export { default } from '../../'; 3 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "build/dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["esnext", "dom"], 8 | 9 | "sourceMap": true, 10 | "allowJs": true, 11 | "jsx": "react", 12 | "moduleResolution": "node", 13 | "rootDir": "src", 14 | "forceConsistentCasingInFileNames": true, 15 | "noImplicitReturns": true, 16 | "noImplicitThis": true, 17 | "noImplicitAny": true, 18 | "strictNullChecks": true, 19 | "suppressImplicitAnyIndexErrors": true, 20 | "paths": { 21 | "*": [ 22 | "src/*" 23 | ] 24 | }, 25 | "noUnusedLocals": true 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "build", 30 | "scripts", 31 | "acceptance-tests", 32 | "webpack", 33 | "jest", 34 | "*.test.ts", 35 | "src/setupTests.ts" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /example/tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /example/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /example/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-react", 5 | "tslint-config-prettier" 6 | ], 7 | "jsEnable": false, 8 | "linterOptions": { 9 | "exclude": [ 10 | "config/**/*.js", 11 | "node_modules/**/*.ts" 12 | ] 13 | }, 14 | "rules": { 15 | "no-var-requires": false, 16 | "prefer-for-of": false, 17 | "object-literal-sort-keys": false, 18 | "ordered-imports": false, 19 | "no-debugger": false, 20 | "no-console": false 21 | }, 22 | "jsRules": { 23 | "no-conditional-assignment": false, 24 | "no-bitwise": false, 25 | "no-duplicate-variable": false, 26 | "no-string-literal": false, 27 | "no-shadowed-variable": false, 28 | "one-variable-per-declaration": false, 29 | "variable-name": false, 30 | "curly": false, 31 | "prefer-for-of": false, 32 | "object-literal-sort-keys": false, 33 | "ordered-imports": false, 34 | "no-debugger": false, 35 | "no-console": false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | Issue tracker is **ONLY** used for reporting bugs. If you are running into issues with Tensorflow.js or Machine Learning in general, please use [stackoverflow](https://stackoverflow.com). 2 | 3 | 4 | 5 | **Describe the bug** 6 | A clear and concise description of what the bug is. 7 | 8 | **To Reproduce** 9 | Steps to reproduce the behavior: 10 | 1. Go to '...' 11 | 2. Click on '....' 12 | 3. Scroll down to '....' 13 | 4. See error 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | 18 | **Codepen or Repo** 19 | Please provide a link to a hosted code snippet (on Codepen or similar site) _or_ a Github repository. 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ml-classifier-ui", 3 | "repository": "https://github.com/thekevinscott/ml-classifier-ui", 4 | "version": "0.5.1", 5 | "homepage": "https://thekevinscott.github.io/ml-classifier-ui/", 6 | "description": "A package for creating image-based machine learning models", 7 | "main": "dist/index.js", 8 | "typings": "dist/index.d.ts", 9 | "scripts": { 10 | "clean": "npm run clean:generated_files && npm run clean:style_types", 11 | "clean:generated_files": "rimraf index.js index.d.ts index.css utils Preview Model Dropzone App dist", 12 | "clean:style_types": "rimraf src/**/*.css.d.ts src/**/*.scss.d.ts", 13 | "watch": "npm run build && rerun-script", 14 | "build": "rollup -c", 15 | "example-start": "node example/scripts/start.js", 16 | "example-build-and-start": "concurrently \"npm run watch\" \"npm run example-start\"", 17 | "example-deploy": "gh-pages -d example/build", 18 | "example-build": "node example/scripts/build.js", 19 | "example-build-and-deploy": "npm run example-build && npm run example-deploy", 20 | "test": "jest", 21 | "storybook": "start-storybook -p 6006", 22 | "build-storybook": "build-storybook" 23 | }, 24 | "watches": { 25 | "build": [ 26 | "src/**/*.scss", 27 | "src/**/*.css", 28 | "src/**/*.ts", 29 | "src/**/*.tsx" 30 | ] 31 | }, 32 | "keywords": [ 33 | "tensorflowjs", 34 | "artificial intelligence", 35 | "machine learning", 36 | "deep learning", 37 | "image classification", 38 | "tensorflow" 39 | ], 40 | "author": "Kevin Scott", 41 | "license": "MIT", 42 | "jest": { 43 | "testMatch": [ 44 | "/src/**/__tests__/**/*.(j|t)s?(x)", 45 | "/src/**/?(*.)(spec|test).(j|t)s?(x)" 46 | ], 47 | "transform": { 48 | "^.+\\.ts?$": "babel-jest" 49 | }, 50 | "transformIgnorePatterns": [ 51 | "[/\\\\]node_modules[/\\\\].+\\.(js|ts)$" 52 | ], 53 | "moduleFileExtensions": [ 54 | "ts", 55 | "js", 56 | "json" 57 | ] 58 | }, 59 | "peerDependencies": { 60 | "react": "^16.4.1" 61 | }, 62 | "devDependencies": { 63 | "@babel/core": "^7.0.0-beta.51", 64 | "@babel/plugin-proposal-decorators": "^7.0.0-beta.51", 65 | "@babel/plugin-transform-classes": "^7.0.0-beta.51", 66 | "@babel/preset-env": "^7.0.0-beta.51", 67 | "@babel/preset-stage-0": "^7.0.0-beta.51", 68 | "@babel/preset-typescript": "^7.0.0-beta.51", 69 | "@storybook/addon-actions": "^3.4.8", 70 | "@storybook/addon-links": "^3.4.8", 71 | "@storybook/react": "^3.4.8", 72 | "@types/jest": "^23.1.1", 73 | "@types/node": "^10.3.4", 74 | "@types/react": "^16.4.6", 75 | "@types/react-dom": "^16.0.6", 76 | "autoprefixer": "^8.6.5", 77 | "babel-core": "^7.0.0-0", 78 | "babel-jest": "^23.2.0", 79 | "babel-plugin-transform-class-properties": "^6.24.1", 80 | "babel-plugin-transform-runtime": "^6.23.0", 81 | "babel-polyfill": "^6.26.0", 82 | "concurrently": "^3.6.0", 83 | "express": "^4.16.3", 84 | "gh-pages": "^1.2.0", 85 | "jest": "^24.9.0", 86 | "node-sass": "^4.9.2", 87 | "npm-run-all": "^4.1.3", 88 | "react": "^16.4.1", 89 | "react-dom": "^16.4.1", 90 | "regenerator-runtime": "^0.12.0", 91 | "rerun-script": "^0.6.0", 92 | "rimraf": "^2.6.2", 93 | "rollup": "^0.62.0", 94 | "rollup-plugin-commonjs": "^9.1.3", 95 | "rollup-plugin-image": "^1.0.2", 96 | "rollup-plugin-node-resolve": "^3.3.0", 97 | "rollup-plugin-postcss-modules": "^1.0.8", 98 | "rollup-plugin-replace": "^2.0.0", 99 | "rollup-plugin-typescript2": "^0.15.1", 100 | "sass-loader": "^7.0.3", 101 | "ts-jest": "^24.0.2", 102 | "ts-loader": "^2.3.7", 103 | "tslint": "^5.7.0", 104 | "tslint-config-prettier": "^1.10.0", 105 | "typescript": "3.0.0-rc", 106 | "uglify-es": "^3.3.9" 107 | }, 108 | "dependencies": { 109 | "ml-classifier": "0.5.1" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /* globals Promise */ 2 | import path from 'path'; 3 | import sass from 'node-sass'; 4 | import typescript from 'rollup-plugin-typescript2'; 5 | import replace from 'rollup-plugin-replace'; 6 | import postcss from 'rollup-plugin-postcss-modules'; 7 | import image from 'rollup-plugin-image'; 8 | import autoprefixer from 'autoprefixer'; 9 | 10 | export default { 11 | input: './src/index.ts', 12 | output: { 13 | moduleName: 'MLClassifierUI', 14 | name: 'MLClassifierUI', 15 | file: './dist/index.js', 16 | format: 'es', 17 | }, 18 | plugins: [ 19 | typescript({ 20 | typescript: require("typescript"), 21 | }), 22 | replace({ 23 | 'process.env.NODE_ENV': JSON.stringify('production'), 24 | }), 25 | postcss({ 26 | preprocessor: (content, id) => new Promise(resolve => { 27 | const result = sass.renderSync({ file: id }); 28 | resolve({ code: result.css.toString() }); 29 | }), 30 | extract: true, 31 | plugins: [autoprefixer()], 32 | // sometimes this writes out blank definitions 33 | writeDefinitions: false, 34 | modules: true, 35 | extensions: [ 36 | '.css', 37 | '.scss', 38 | ], 39 | use: [ 40 | [ 41 | 'sass', 42 | { 43 | includePaths: [ 44 | path.resolve('node_modules'), 45 | ], 46 | }, 47 | ], 48 | ], 49 | }), 50 | image(), 51 | ], 52 | }; 53 | -------------------------------------------------------------------------------- /rollup.config.js.umd: -------------------------------------------------------------------------------- 1 | /* globals Promise */ 2 | import path from 'path'; 3 | import sass from 'node-sass'; 4 | import typescript from 'rollup-plugin-typescript2'; 5 | import replace from 'rollup-plugin-replace'; 6 | import commonjs from 'rollup-plugin-commonjs'; 7 | import postcss from 'rollup-plugin-postcss-modules'; 8 | import image from 'rollup-plugin-image'; 9 | import resolve from 'rollup-plugin-node-resolve'; 10 | import autoprefixer from 'autoprefixer'; 11 | 12 | export default { 13 | input: './src/index.ts', 14 | output: { 15 | moduleName: 'MLClassifierUI', 16 | external: [ 17 | 'react', 18 | 'react-dom', 19 | 'tf', 20 | ], 21 | globals: { 22 | 'react': 'React', 23 | 'react-dom': 'ReactDOM', 24 | }, 25 | name: 'MLClassifierUI', 26 | file: './dist/index.js', 27 | format: 'umd', 28 | }, 29 | plugins: [ 30 | resolve({ 31 | jsnext: true, 32 | main: true, 33 | extensions: [ '.ts', '.tsx', '.js', '.json' ], 34 | only: [ 35 | 'ml-classifier', 36 | ], 37 | }), 38 | typescript({ 39 | typescript: require("typescript"), 40 | }), 41 | replace({ 42 | 'process.env.NODE_ENV': JSON.stringify('production'), 43 | }), 44 | commonjs({ 45 | ignoreGlobal: false, // Default: false 46 | }), 47 | image(), 48 | postcss({ 49 | preprocessor: (content, id) => new Promise(resolve => { 50 | const result = sass.renderSync({ file: id }); 51 | resolve({ code: result.css.toString() }); 52 | }), 53 | extract: true, 54 | plugins: [autoprefixer()], 55 | // sometimes this writes out blank definitions 56 | writeDefinitions: false, 57 | modules: true, 58 | extensions: [ 59 | '.css', 60 | '.scss', 61 | ], 62 | use: [ 63 | [ 64 | 'sass', 65 | { 66 | includePaths: [ 67 | path.resolve('node_modules'), 68 | ], 69 | }, 70 | ], 71 | ], 72 | }), 73 | ], 74 | }; 75 | -------------------------------------------------------------------------------- /src/App/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import Dropzone from '../Dropzone'; 4 | import { 5 | getFilesAsImageArray, 6 | IFileData, 7 | splitImagesFromLabels, 8 | } from 'utils/getFilesAsImages'; 9 | import nextFrame from 'utils/nextFrame'; 10 | 11 | import { 12 | ITrainResult, 13 | } from '../types'; 14 | 15 | import Model, { ImageError } from '../Model'; 16 | import Preview from '../Preview'; 17 | 18 | import MLClassifier from 'ml-classifier'; 19 | 20 | import styles from './styles.scss'; 21 | 22 | export interface IImage { 23 | imageSrc: string; 24 | label: string; 25 | } 26 | interface IParams { 27 | [index: string]: any; 28 | } 29 | 30 | interface IState { 31 | status: string; 32 | images?: string[]; 33 | files: any[]; 34 | labels: string[]; 35 | downloading: boolean; 36 | predictions: { 37 | src: string; 38 | prediction: string; 39 | label: string; 40 | }[]; 41 | logs: { 42 | [index: string]: any; 43 | }; 44 | accuracy: { 45 | training?: number; 46 | evaluation?: number; 47 | }; 48 | errors?: ImageError[]; 49 | } 50 | 51 | interface IProps { 52 | params: { 53 | train?: IParams; 54 | evaluate?: IParams; 55 | save?: IParams; 56 | }; 57 | getMLClassifier?: Function; 58 | uploadFormat: string; 59 | imagesFormats: string[]; 60 | showDownload?: boolean; 61 | onLoadStart?: Function; 62 | onLoadComplete?: Function; 63 | onAddDataStart?: Function; 64 | onAddDataComplete?: Function; 65 | onClearDataStart?: Function; 66 | onClearDataComplete?: Function; 67 | onTrainStart?: Function; 68 | onTrainComplete?: Function; 69 | onPredictComplete?: Function; 70 | onPredictStart?: Function; 71 | onEvaluateStart?: Function; 72 | onEvaluateComplete?: Function; 73 | onSaveStart?: Function; 74 | onSaveComplete?: Function; 75 | } 76 | 77 | class MLClassifierUI extends React.Component { 78 | public static defaultProps: Partial = { 79 | params: { }, 80 | uploadFormat: 'nested', 81 | imagesFormats: undefined, 82 | showDownload: true, 83 | }; 84 | 85 | private classifier: any; 86 | 87 | constructor(props: IProps) { 88 | super(props); 89 | 90 | this.state = { 91 | errors: [], 92 | files: [], 93 | status: 'empty', 94 | images: undefined, 95 | downloading: false, 96 | predictions: [], 97 | logs: {}, 98 | labels: [], 99 | accuracy: { 100 | training: undefined, 101 | evaluation: undefined, 102 | }, 103 | }; 104 | } 105 | 106 | componentDidMount = async () => { 107 | this.classifier = new MLClassifier({ 108 | onLoadStart: this.props.onLoadStart, 109 | onLoadComplete: this.props.onLoadComplete, 110 | onAddDataStart: this.onAddDataStart, 111 | onAddDataComplete: this.onAddDataComplete, 112 | onClearDataStart: this.props.onClearDataStart, 113 | onClearDataComplete: this.props.onClearDataComplete, 114 | onTrainStart: this.props.onTrainStart, 115 | onTrainComplete: this.props.onTrainComplete, 116 | onPredictStart: this.props.onPredictStart, 117 | onPredictComplete: this.onPredictComplete, 118 | onEvaluateStart: this.props.onEvaluateStart, 119 | onEvaluateComplete: this.props.onEvaluateComplete, 120 | onSaveStart: this.props.onSaveStart, 121 | onSaveComplete: this.props.onSaveComplete, 122 | }); 123 | 124 | if (this.props.getMLClassifier) { 125 | this.props.getMLClassifier(this.classifier); 126 | } 127 | } 128 | 129 | private onDrop = (files: FileList) => { 130 | this.setState({ 131 | status: 'uploading', 132 | }); 133 | } 134 | 135 | private onAddDataStart = async (imageSrcs: string[], _labels: any, dataType: string) => { 136 | this.setState({ 137 | status: 'parsing' 138 | }); 139 | 140 | if (this.props.onAddDataStart) { 141 | this.props.onAddDataStart(); 142 | } 143 | 144 | if (dataType === 'train') { 145 | this.setState({ 146 | status: 'training', 147 | images: imageSrcs, 148 | }); 149 | } 150 | } 151 | 152 | private onParseFiles = async (origFiles: FileList) => { 153 | const imageFiles: IFileData[] = await getFilesAsImageArray(origFiles); 154 | 155 | const { 156 | images, 157 | labels, 158 | files, 159 | } = await splitImagesFromLabels(imageFiles); 160 | 161 | this.setState({ 162 | files, 163 | }); 164 | 165 | return this.classifier.addData(images, labels, 'train'); 166 | } 167 | 168 | private onAddDataComplete = async (imageSrcs: string[], labels: string[], dataType: string, errors?: ImageError[]) => { 169 | if (this.props.onAddDataComplete) { 170 | this.props.onAddDataComplete(imageSrcs, labels, dataType, errors); 171 | } 172 | if (dataType === 'train') { 173 | this.setState({ 174 | status: 'training', 175 | images: imageSrcs, 176 | labels, 177 | errors: (errors || []).map((error: ImageError) => { 178 | return { 179 | ...error, 180 | file: this.state.files[error.index], 181 | }; 182 | }), 183 | }); 184 | 185 | const train = this.props.params.train || {}; 186 | const result: ITrainResult = await this.classifier.train({ 187 | ...train, 188 | callbacks: { 189 | onBatchEnd: async (batch: any, logs: any) => { 190 | if (train.callbacks && train.callbacks.onBatchEnd) { 191 | train.callbacks.onBatchEnd(batch, logs); 192 | } 193 | const loss = logs.loss.toFixed(5); 194 | // log(batch, logs); 195 | // log('Loss is: ' + logs.loss.toFixed(5)); 196 | this.setState({ 197 | logs: { 198 | ...this.state.logs, 199 | loss: (this.state.logs.loss || []).concat(loss), 200 | } 201 | }); 202 | 203 | await nextFrame(); 204 | }, 205 | }, 206 | }); 207 | 208 | const { 209 | history: { 210 | acc, 211 | // loss, 212 | }, 213 | } = result; 214 | 215 | const training = acc[acc.length - 1]; 216 | this.setState({ 217 | status: 'trained', 218 | accuracy: { 219 | ...this.state.accuracy, 220 | training, 221 | }, 222 | }); 223 | } 224 | } 225 | 226 | public onPredictComplete = async (src: string, label: string, pred: string | number) => { 227 | if (this.props.onPredictComplete) { 228 | this.props.onPredictComplete(src, label, pred); 229 | } 230 | const prediction = `${pred}`; 231 | 232 | this.setState({ 233 | predictions: this.state.predictions.concat({ 234 | src, 235 | prediction, 236 | label, 237 | }), 238 | }); 239 | } 240 | 241 | public predict = async (imageFiles: IFileData[]) => { 242 | for (let i = 0; i < imageFiles.length; i++) { 243 | const { 244 | src, 245 | label, 246 | } = imageFiles[i]; 247 | 248 | await this.classifier.predict(src, label); 249 | } 250 | }; 251 | 252 | handleDownload = async () => { 253 | this.setState({ 254 | downloading: true, 255 | }); 256 | await this.classifier.save((this.props.params || {}).save); 257 | this.setState({ 258 | downloading: false, 259 | }); 260 | }; 261 | 262 | public render() { 263 | return ( 264 |
265 | {this.state.status === 'empty' && ( 266 | 270 | )} 271 | {['training', 'uploading', 'parsing'].includes(this.state.status) && ( 272 | 275 | )} 276 | {this.state.status === 'trained' && this.state.images && ( 277 | 287 | )} 288 |
289 | ); 290 | } 291 | } 292 | 293 | export default MLClassifierUI; 294 | -------------------------------------------------------------------------------- /src/App/styles.scss: -------------------------------------------------------------------------------- 1 | .classifier { 2 | max-width: 300px; 3 | width: 300px; 4 | background: white; 5 | height: 300px; 6 | 7 | h1,h2,h3,h4,h5,h6 { 8 | font-weight: inherit; 9 | } 10 | 11 | button { 12 | cursor: pointer; 13 | } 14 | 15 | ul, li { 16 | list-style: none; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | * { 22 | box-sizing: border-box; 23 | } 24 | 25 | a, button { 26 | cursor: pointer; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Dropzone/getFile.ts: -------------------------------------------------------------------------------- 1 | const getFile = async (item: any, path: string) => new Promise(resolve => { 2 | item.file((file: any) => { 3 | const reader = new FileReader(); 4 | reader.onload = (e: any) => { 5 | return resolve({ 6 | file, 7 | src: e.target.result, 8 | path: path.split('/').join(''), 9 | }); 10 | }; 11 | reader.readAsDataURL(file); 12 | }); 13 | }); 14 | 15 | export default getFile; 16 | -------------------------------------------------------------------------------- /src/Dropzone/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import classNames from 'utils/classNames'; 3 | import transformFiles from './transformFiles'; 4 | import styles from './styles.scss'; 5 | 6 | interface IProps { 7 | onDrop?: Function; 8 | onParseFiles: Function; 9 | style?: any; 10 | children?: any; 11 | } 12 | 13 | interface IState { 14 | over: boolean; 15 | } 16 | 17 | class Dropzone extends React.Component { 18 | private timeout: number; 19 | constructor(props: IProps) { 20 | super(props); 21 | 22 | this.state = { 23 | over: false, 24 | }; 25 | } 26 | 27 | public handleDrop = async (e: React.DragEvent) => { 28 | if (this.props.onDrop) { 29 | this.props.onDrop(); 30 | } 31 | e.preventDefault(); 32 | e.persist(); 33 | 34 | // console.log('pre transforming files'); 35 | const folders = await transformFiles(e); 36 | // console.log('post transforming files'); 37 | if (e.dataTransfer.items) { 38 | e.dataTransfer.items.clear(); 39 | } else { 40 | e.dataTransfer.clearData(); 41 | } 42 | this.props.onParseFiles(folders); 43 | } 44 | 45 | public handleDrag = (over: boolean) => { 46 | return (e: React.DragEvent) => { 47 | e.preventDefault(); 48 | this.setState({ 49 | over, 50 | }); 51 | } 52 | } 53 | 54 | componentWillUnmount() { 55 | if (this.timeout) { 56 | clearTimeout(this.timeout); 57 | } 58 | } 59 | 60 | public stop = (e: any) => { 61 | if (this.timeout) { 62 | clearTimeout(this.timeout); 63 | } 64 | if (this.state.over === false) { 65 | this.setState({ 66 | over: true, 67 | }); 68 | } 69 | this.timeout = setTimeout(() => { 70 | this.setState({ 71 | over: false, 72 | }); 73 | }, 50); 74 | e.preventDefault(); 75 | } 76 | 77 | public render() { 78 | const className = classNames(styles.container, { 79 | [styles.over]: this.state.over, 80 | }); 81 | return ( 82 |
91 | {this.props.children || (Drop Images To Begin Training)} 92 | 99 |
100 | ); 101 | } 102 | }; 103 | 104 | export default Dropzone; 105 | -------------------------------------------------------------------------------- /src/Dropzone/styles.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | text-align: center; 3 | color: rgba(0,0,0,0.4); 4 | border-radius: 5px; 5 | height: 100%; 6 | width: 100%; 7 | display: flex; 8 | position: relative; 9 | justify-content: center; 10 | align-items: center; 11 | background: rgba(0,0,0,0); 12 | border: 2px dashed rgba(0,0,0,0.2); 13 | transition-duration: 0.2s; 14 | 15 | &.over { 16 | background: rgba(155,77,202,0.2); 17 | border: 2px dashed #9b4dca; 18 | transition-duration: 0.1s; 19 | } 20 | } 21 | 22 | .input { 23 | display: none; 24 | } 25 | -------------------------------------------------------------------------------- /src/Dropzone/transformFiles.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import traverseFileTree from './traverseFileTree'; 3 | 4 | const transformFiles = async (e: React.DragEvent) => { 5 | const d = e.dataTransfer || {}; 6 | const items = d.items || []; 7 | let images: any = []; 8 | for (let i=0; i { 17 | if (image.path) { 18 | return { 19 | ...allImages, 20 | [image.path]: (allImages[image.path] || []).concat(image), 21 | }; 22 | } 23 | 24 | return allImages; 25 | }, {}); 26 | return folders; 27 | }; 28 | 29 | export default transformFiles; 30 | -------------------------------------------------------------------------------- /src/Dropzone/traverseFileTree.ts: -------------------------------------------------------------------------------- 1 | import getFile from './getFile'; 2 | 3 | const readDir = async (dir: any, path: string) => new Promise(resolve => { 4 | const dirReader = dir.createReader(); 5 | let items: any = []; 6 | dirReader.readEntries(async (entries: any) => { 7 | for (let i = 0; i { 23 | return await readDir(item, path); 24 | }; 25 | 26 | export default traverseFileTree; 27 | -------------------------------------------------------------------------------- /src/Loading/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import loading from './loading'; 3 | 4 | const SIZE = 32; 5 | 6 | interface IProps { 7 | size?: number; 8 | } 9 | 10 | const Loading: React.SFC = ({ 11 | size, 12 | }) => ( 13 | Loading... 21 | ); 22 | 23 | export default Loading; 24 | -------------------------------------------------------------------------------- /src/Loading/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thekevinscott/ml-classifier-ui/f3c81d5ee6c3680bd1da0ee923307fc1f6eaf121/src/Loading/loading.gif -------------------------------------------------------------------------------- /src/Loading/loading.ts: -------------------------------------------------------------------------------- 1 | const loadingGif = 'data:image/gif;base64,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t+7667AjFEquJ8USS32QEDMNMSx1w04332a2CC7TFD9NxSkFw87y7DjzLlSsOGN88cuAHBIk5TC/fDrBaBVKMtNPf95JzmivvTi33wRJMOGH77xJoaRjvvnZBB/TIrNI3374p46UzfzzS8f7YMKKZezvgM/7SCgAyMBydK8lodDdAdsXjASCJBT/Y+D8upE+lCwCGBNsXzLsZ5JYmEOD87uGBUGSvxBOzxlIKwjRLvIzgiRDHSjUngBJQgoDutB4/gk7yK6IMUOJfOIa5qjhQCBRjRxqrxww+8gsfmg8YqzwDcrIRjau8QvrOaQRyRiHOcyBLtn5zonLSwZIFqG/EC6DhAUhhRbnWI0ORuQX4BijHl2hEF6cMIfquCJGXhFCZ4xPIdaYoyKZocSFuCIbeoykNhjiDBxqUI0i8WH7gOHFgeBCkaCUVkJcEclSYkshkMAGA81BklC0jxi0O8girgHKOTKDIKwAhy53qUvkCQSSpRzjOJ6GkFOcUXt2DIkEp7EMXzIkGLWco6tyyctdOvMTwdSjMx4SjD/+7l7TMGQnZRhNLf6iINSsJjic+QZnZHOMjUwIJCrJDjiGJFQR/mFGOa/Rr3RWk52NIMc7F/SQQi2FFeXMRjLf4E9esvMNv3inOWpBtURGs1joVGcvD6KNd4Jjar9IqLQaas2DkPKdmAzQLMu5M4OQdKMHQWI2h8kgYuxzhi9dJ0I+IdBsUiNAciznORGS04cOJBkSFSVe9BlNa3ixqAlpRB6zSVDixCKhC8WlRnWakIi+c6i2WUQ1ynlLhUBVIcAMJjiIqZWQljOeWtWoUQly0mymFC9AKifvFnJWhVDjneOA608QGk1+MqSvUe1pMCmaGUswVZFgNetW51oQpJYyG4KFCiksmo0+HXayDpmqOcDBxwDhgpaUzahcHVILc5DjlAwaMN5DELsQYbA1dgOhLW4rotvdTqS3vo0IcINL3OIa97jITa5yl8vc5jr3udCNbtUCAgAh+QQJBABKACwAAAAAeAB4AIcAAAABAQECAgIDAwMEBAQFBQUGBgZNTU2pqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqamqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq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fJHFCUucscEbcxywxx/zW7HI8pJcsr4Ylevyy+e2K7PM3tZs881LtfLmSrncCh4n0owjDUvp7JMOK8hNgsw4TI+Di0rJ7CP1PtgYChUu3TTNNDeRipRJPVNLjU8yWrVijdZaV3sSNmGH/Y7PNXESDdpod5MtR6zg03b+2+cgPZOjWdONNtklnbP33vhUHRMu3Aju+N0ZsXL45PUQzlIrQTtOdzSQa8SK4ZPvnQ7cJk2SjOZ0WwNtSbmAHXrb5VgtUjGBo850N259+O1B0uj9+tSJkyRL47Y3jUzXopwjzVcZqVKOPTsXlMk3v4ddzzEhFVN809Kgac0555iDjK8HZSLNPPbYU05CrBRdvdRDfzRJ7ZpzszpBr4CvPzm5S4QMO+kL4NMScgzXvQ4fstvILlDXDbUphBz6iyA3/OYQXJwjgBhMB0Ow4bvJxS8kxKNbMrpWkGJE8ISaegguMMjCZzAkE+WgHElaQTdpRA8n5jih/rhBEFu844f+QPwhsQRyQRambx4JNIgs3Bc20oEkc+PgxgAbwgwd6o95AvFhEIE4xD+owogB3MZDkmFADZaEE+NoIAkPkjwrngMZBdHiFt/RxT9sA4zpu6FnOLgPCgqvcwThhhvNcRU5brGOmUAfGM+xFewV5RZuPEf/BmLIINbxD8jAoz2CgSsIWvEbB6kkFw+SDjyyQ1bIiGQK4zhHIR5khXicBqkukUMrWgMhonTlQZ4HRiRyShqDxCIrW3lJgahCkUYEpX3y50Y44rKVdEzINDS5yuoI0ork8BM0i2kdAC5yPbuI5CQNkstoJiSTeHQmby7hSR3yUCHl5OZAimhEdiRxKamGdKMfQ7nNhcASjLIUjShqqcMPJiSeDPkGHuehR6hA0oqEZAhCPYNMFnJSoNeMoDrh2U+GTJOF52goXl7RTnI4ZKIM8aY92DHF9RQjh0aSaEcZEgx7zMOFq5rWQ1DKkGfck1s8xRlEgirUk860qDs9KlKXytSmOvWpUI2qVKdK1apa9apPDQgAIfkECQQASwAsAAAAAHgAeACHAAAAAQEBAgICAwMDBAQEBQUFBgYGBwcHCAgICQkJCgoKFRUVqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6urq6urq6urq6urq6urrKysrKysrKysrKysrKysra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2tra2trq6urq6urq6urq6urq6urq6ur6+vr6+vsLCwsbGxsbGxsrKysrKysrKys7Ozs7Ozs7OztLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tbW1tbW1tbW1tbW1tra2t7e3t7e3uLi4uLi4uLi4ubm5ubm5urq6urq6u7u7u7u7u7u7vLy8vLy8vLy8vLy8vb29vb29vr6+vr6+vr6+vr6+v7+/wMDAwsLCwsLCwsLCwsLCw8PDw8PDw8PDxMTExMTExcXFxcXFxsbGyMjIyMjIycnJycnJysrKysrKysrKysrKy8vLzMzMzs7Oz8/P0NDQ0NDQ0dHR0dHR0dHR0dHR0tLS09PT1NTU1tbW19fX19fX2NjY2NjY2dnZ2dnZ29vb3Nzc39/f4ODg4+Pj5OTk5ubm5ubm5ubm5+fn5+fn6Ojo6enp6urq6+vr7e3t7u7u7+/v8fHx8/Pz9PT09vb29/f3+fn5+vr6+vr6CP4AlwgcSLCgwYMIEypcyLChw4cQI0qcSLGixYsYM2rcyLGjx48gQ4ocSbKkyZMoU6pcybKly5cwY8qcSbOmTYWLaOncqXPRzZs5ee70+bNmUKG0iBadeVSo0qUg45R62JTn04VXoTIc5WsYJYdVhzoENKpTVq0GIfUaxrYXWKQ9HV4aRfeSHbQHAd1iy3fYVIZh4zJ8RLfwKEpx8A681Levr8RY4SZt2MmwYUCKlwDq2pgtK8CSzxaEZLlwJ8iKS3XmK3pg4MkK7ZQu/CizwDhrV9+KDLe1QEqzR12yvXg12+EJX/teFHwUZuIC967uhdqgcoVzZ3+FLnCRcb/JQ/4nJBz8LneBrIz7em5d/ME4lWdDOj8wDufOsBBePygpeCf6BKlm3Hzt9XaQbMHVBuBAuXWmy0H7FQTcbMgt+Nt3FbrmHkHMlbdUKb4dJF1njxUU4UDxlbYdRNWNtAg0y7hiXkOAfPeXhgYSRFpwLS5kByAhcgQMNEQmcyNDqXzH3hInxtEcgQ3FAeQiiywZEiVEZgkNMFAqZJ9xu+GI1FMTlvbfWFSmuUiPHiWjpZa6zJjQKN91yRCCswW5hB1qqmmlR6m8+WaMDDXY1y16DmRHmYatmNCPffbJpkZ2LCOooMmciRAkjfWSIUSLZFeYnAZJGWmkf27kyqWsAtMaLP5s+VLKpA89Ep8kCk15aqS0YtSJm6wKSgupAtXISqoTxUHapHzuimqvGrliabBaLpNKXh6xaaqzfhIbkh26UPumMY6atC23aXpLEiVDiptlL4lm1Cy6VCJ7UinAugtNvBbRmyYg0Jpkh7TuPljSvM4Com5Li/RC7TL8XqTrqQvDRIkxrLqCEsJ+BgxTKdNmmUzFH01cpccyDazlkeaqSfJPiwwJDEtA2osXJXaeFAfKFvbs889AT3TJ0EQXbfTRRMOky9JMN+3000xjpM3UVFdt9dVUw/TP1lx37fXXXEuN9dhjaw322WeLTfbaWb+E9tteq8322mbDDbfcc5fttv7dd1+UN9t1852233+THbjgX+NdeNWHI941RkhHLrnSUFdeedCYZ6755ggdxtItYXIHCTXlUMOSOvuo8ylagBhTzuvl5JdSMvvUvk81EbsEyzewv86NzR4tQo/tteOTjFajVNN7776gVA3xxMMTek2QQLP88t/kDNIl+EAP/Tmru7QZ79cvr4xJ6HjvPT64xwQLN+XHr31Hl6hvPz3HtzQK6fFfD838HrlE+uznPXVMzySAUEb/rneNUajkFvUgoPfKkbuK+IJ8C3zdNww2Go+USyDS6J4Ebcc+kqQCfhmEnTFSBYl0SAOAEaFEOezxQSZ5Y4TEo0fzQOKLFMKOGv4ArEY60nGOFfZrGvOwhz3KkZBLoA6HtZMGSACBwf51w4EJOcUQt1gODkZkGOxQohhlhxBfRHCE+KigQ3SxwG/sUCHl2KIcuaGphsDiHGLMYzoYUg0R2k+KIulG/JQBvCX4Qo6IPAVEYJHHRi4DMOWwXz1IMorrUaOGBgHEORC5RW4QhBXwCKUoQ/mZgeCxkUqch55S8UTiHRAk/CtHN8jIkGVwcotdAuUoRVnK36BSjNd4SDLOmDqTQKIcbizkQFp4y3QgoyC63CU8eimQa/xSiZiEUB/3ET6QpAKGB+FGM89hpWjukppMSuIvzxGRS7zxJq5oZjq8iB5pktIgw/64pj148bM43vIbBzHnKNEpkHRckx0+Q4Y8FWkQgfLyIIy85jQspMlmVgMhDr3nQWb4S1UuSBrjnF9Gp4kQSqgTlQCljxab+UyM2pOkCJmGPhnKHXHeshz2GilBXdOOa7KTO7qQJz2h+dKdDiSf1xwGdADhT056UiE6XcgpUckONbpEoc2sY0CLupCI/nKimYHEJm8JSKhydSHfuOY8sgnPcRYyqlg5aSP5GVabyrGlC4HrQmTayHOwdSmnaCoTG6LXhfRUie2gJXd8sUmNEfasDOGFPebxyJ5t5iGFXQjEOGdWexqVsxLJLGgpItrRhhaypk2talfL2ta69rWwjQutbGdL29radnMBAQAh+QQJBAB0ACwAAAAAeAB4AIcAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJYWFhjY2N4eHiNjY2bm5ukpKSoqKipqampqampqampqampqampqampqampqampqampqampqamqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrq6urq6urq6urq6urq6urq6usrKysrKysrKysrKysrKytra2tra2tra2tra2urq6urq6urq6vr6+vr6+wsLCwsLCwsLCxsbGxsbGxsbGxsbGxsbGxsbGysrKysrKysrKysrKysrKzs7Ozs7Ozs7O0tLS0tLS1tbW1tbW2tra3t7e4uLi4uLi4uLi5ubm5ubm5ubm5ubm5ubm5ubm6urq7u7u8vLy9vb2+vr6/v7+/v7/AwMDAwMDAwMDAwMDBwcHDw8PExMTGxsbGxsbGxsbGxsbGxsbHx8fHx8fIyMjJycnKysrMzMzNzc3Nzc3Nzc3Ozs7Ozs7Ozs7Ozs7Pz8/R0dHS0tLT09PU1NTV1dXV1dXW1tbW1tbX19fX19fY2NjZ2dna2trb29vb29vc3Nzd3d3e3t7g4ODi4uLj4+Pl5eXo6Ojp6enq6urq6urr6+vr6+vr6+vs7Ozu7u7v7+/x8fHx8fHz8/P09PT19fX29vb39/f39/cI/gDpCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6ZNhZaA6dyp09LNmzl57vT5s2ZQocCIFp15VKjSpSANvXrYlOfThZgMQYXYqtgyUw6rDnU4idYrTFsZdhq2rO2wsEh7OmxFq24rSGkPQgLWtu+yqQzFylVbtzCtVVrzClzl12+xxDjjJmUo1bDhSYrpQPLauG2uwJKvHjRlufAryHlfde4ruqDgyQohlS7cKbNAQ8RWLwO28HVrgqtm02qFWjFj3asix/0t0JJwWphtD+S7elhxgr4TGqI7O7n0gZZ0/v9NmB1hp+eLvhPMpbsY3oPlDVaeDVb9QEOcO+9CGL9gKuGn2UeQarrVZlB/A8kmnIECDpRbdfCFdhArwrXSYEGmiOcddhK69tx7P7US3UPUdfaYax3e94pwGzpkSHomTVLNM7nA2BAk4gEGXooCifLcdQpN0okoQH5UTDVIOmOhQ7eIById/RnynCgPQTKkKKKgRZIpSHZZTTEMKoSfbrztuBxwABZZkCGYYOmmKDaG5IyXXgITJ0KtiBcmQwrOpmVvb765p0e30EknjQw92BhsDi0SXGmsMCRkoIE+2dEizxhqqDMtGtRJY8R0ShV3hVla0CJXUipoSLlo6mox/iMatEtbxQRYkSGdrEhLKtq1qaqqpm60ypyuGrrLnQLhmEuwExlCWpGT/KpqJ8h6lEumxXr5zC16eQSkldIG2kmsIy0iTLZ0fqUSquEGypxIphyJbpfDkBuSIZa0+2ZWLrVC7LzV2Outvm52wmxKi1w7rzAm5dvuuDVNMky2zwj8Ea7hWqLmSxm6+tlJ0aqKSbU1tYJtl86QHFKqWBqsWMJeLokSJG9aDNUkRxbDUpv82mfKoCYtovKFRBdt9NEdrZLK0kw37fTTSyM90TdUV2311VhXDZMhXHft9ddgd41R1mSXvXXYaKM9dtlsW3122nCLfVHbdH/zdtxwr123/tkv4Y233ntnfbffYQMe+NWDE/614Ydr3bfial+0yuSUV2755ZRLrfnmnHe+OSv1qbTLfvZhgg062LDUzj/t8GobJMegIzs6uqjEzD+4/4PNuzXpQs7ssn9z8EeW3JM77vsssxUr2gAP/FsnYXP88fGQbhMm0TjvPDl/jpTKPtNPr47rMkEyzO/aO++MSeuEH/4+u8ekyzfp1989SKm4r/89yrfEyun101407heSVLRPf+Frh/VOAolmBFB72ohUSnaBDwSGzxy848j5Hgg8cpSpIEC7SOgIEg1+WPB48CPJLejHwdkdI1idaEc1QigRU5xDHyMETzhOeLx7QO8j/sNo4eywQcCBZKMd7VDHMoYnqWrYQx/6OEdCUrE6HuIuGiCBBPoeGA4JIuQVSAwjOnpBkWK8A4po/BhChlHBE/IjgxUBxgPJ8cOEoCOMeOyGqBSSC3Wg8Y/tYAg2TIhALIokHPVzBhPpMAw8OlJHDcnFHyfZjMCcQ3/4IAkrtIeNHCIEEupwZBjtNpBbzOOUqDwltwbix0lC0R42Iwgtqni8BYYEgOgIR+0c4gxRhjFMpkwlKlcpEFO4Eo3ZeAgz2sg6k2ACHXRc5EBi6Mt29K+UwhxmQbJxTCh6kjyD/Af5VFhEhXyjmuqwVDCzSUyBTOKJx1RHRFJRR5vkoprt/iBjQdYpzHYKpBjd1McHiXZHX47jIPxMpT8F0o5uvsNoy8AnJLGZzXkslA6S7GY1iAbKaiYToRW1KEJueExYXqga6AxhQrU5Gni6MhwNAmM1r2mQlaoyIdUIqMy+c05fouNgNhUpQiZxxnjapxf41CdCgnrRgQC0mzqTDiQKKkpSJoSpC2mlK98Ry59EtJp7JAhW+RjQjWamE6H0pVkVMlaFhKOb9vhmUe7py3QypK0JeWc3B5qWTvQUjzS9akibWpCcTlIdct3KK6iKDiYN1iFF1cc71GifYYSSsmx9bEOAoQ97VLJo5nsIXhfSjK52brSeswhqU0uR1bJWIq59F61sZ0vb2tr2trjNrW53y9ve+va3tw0IACH5BAkEAHcALAAAAAB4AHgAhwAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVtbW2pqanl5eYiIiJOTk5ycnKKioqampqioqKmpqampqampqampqampqampqampqampqampqampqampqaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqurq6urq6urq6ysrKysrKysrK2tra2tra2tra6urq6urq6urq6urq6urq+vr6+vr6+vr6+vr7CwsLCwsLCwsLGxsbGxsbKysrOzs7Ozs7S0tLW1tbW1tbW1tba2tra2tra2tra2tra2tra2tra2tre3t7i4uLm5ubq6uru7u7y8vLy8vLy8vLy8vL29vb29vb29vb29vb29vb6+vsDAwMHBwcLCwsPDw8PDw8TExMTExMTExMXFxcXFxcfHx8jIyMnJycnJycrKysrKysrKysvLy8vLy8zMzM7OztDQ0NLS0tLS0tLS0tLS0tPT09PT09PT09TU1NXV1dbW1tfX19jY2NnZ2dnZ2dra2tra2tra2tra2tvb29zc3Nzc3N7e3t/f39/f3+Dg4ODg4OHh4eLi4uTk5Obm5ujo6Onp6ezs7O7u7u7u7u7u7u/v7+/v7+/v7/Dw8PLy8vT09PX19fX19ff39/j4+Pr6+vv7+/z8/Pz8/Aj+AO8IHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnEmzpk2Fl3zp3Knz0s2bOXnu9PmzZlChvogWnXlUqNKlIBGxetiU59OFmwpBhaiqmDJPDqsODUsL1qatDDcJU8ZWWFikPR22okW31SO0Bx/xYstX2VSGYuMy7ES3MC1ViPAOLNW3b7HECwMnZVgIlmHDV6E+8tqYLS7AcCcvLHW5MCzIill15pu5oOTWBB+VLtxJ8UBEa1fzihwa9kBVs2m10mpbIOPVykrh7K1QU3BavrfuXS0MtcHXCQvNna2quGvkfhP+YkdIOPgi7wVxIS929+B4g4gsz1aOniAizp1zIXxfMFVwWMTVN5BqyJ11HXMGyRZcbQIWlFtnwbiHYEGsBNdKgwZ5Ah59BPEnkHPBtffTKpRENF1nj7k2oUCVBdcdRIWcZxIl0zRzi3ULPQLeXwN5CMpzOEYGSikBjjTMNEgys8pDtIAn4h38IfIcKA894kkpWDI4kidIdjnNMJo0dB9yu/W4InCzAShmJ1i2WYqMIjHjpZe8BGmQKuAZ6JCCs+nZnJtugiUSLHPOaSNDD/bFS3QGLYLmZTyKNySgbpYYVTOFFsoMhwdt0pgwnEKkyXaFPdnolZQCSiVIt2Tq6jD+lh6UC1vFsGLnQ4V0Il8q2bGZaqqxelSKnK4WmoudOuJiakWIkHbrJb+m6gmcId2CabFeNnNhgh7ZScmk0bYJCqMcIcILtnN+pdIiqIbbZpgqeXIkul0KE6xIhWzirpud3GrSKsTSO829HxWyb5ueLLsSItbSWyZJ+ro7bk2UCINtMwSDVAi4qWb1k4au3oIStNJSO+K1XTLjL0jtYgmKwj8x7OWSKT3iJrk3UXLkMCyx2UmR3nkC77omY2j00UgnbdLB7sJUyNNQRy311FBj5M3VWGet9dZYw0TJ12CHLfbYYFvN9dlne0322mubjfbbXb/E9txiuw3322rTTbf+3XenLbfee1/UN9x5A9624IOjXbjhY/OdeNaLMx42RkyH6zTVmGOu9Oacd+65RKkIqhIunwmoyTToTMNSO/+0I7piiwyDzuzoiJxSMf/k/s/Ait0yDu2ze1O0SJTco3vu+xSzVSrYAA98hCdVc/zx8JRukybNOO/8OENvuc/006vzOkyLBPO79s4vY5I64IO/D+8w3eIN+vR3z3L7+N+jfEupoE6/9s2wX0g8wT78ga8d1jvJIpbxP+1hg1cpwYXxDDi9cWQMJOZrIPDGoYtOeURLBGEGPyh4vPeRpBXz0yDtijG8TbCjGX6ySCfGoQ8QDoQS2yDh8e7hi5AEQ4X+tJuGAAdiDXawQx3ssQglmmEPfehjHAnxBOt0mDtmgGQR52ugNyCIkFUY8Yvn0M9EhPEOJ5qRFgrxxQQpyI8LZkQXDRwH9BSCji/acRurcggt1GHGPrKDIdUYoQGtKJIUam8ZwytIMOzISJrpsY+QVAZDKDGO/JEkFdqbxvgQ8gh1MPKL2yBIK+ZBylKSclsC4SMknWgPNxKEFVM8XgJD4j90eMN2DWHGJ7/op1GaspSovEMnVmlGazykGGtsh0k0gQ45JrJTuzTi/gbiy1/OI5h3sAYxnWhDhVAikP/YZEhaMUSFbCOa6zBVNX+JTUo0kZjqiIgnelgUWkSTHWL+FKU1T2kQYWxTHx1MWh13CUWDrNOU2BQIO7b5jqQV456O1Oc+E3oHWvyzGUfrZDSNeZCDAhMh5dhmK43mDHTGkJr7vCZ53rlKb2DIi9GcpkFTSlGBNOOfL6rPOXeJDpjdwaP8RAglyghPAeXinvnsKE0V4s9tusU7jzhHNEOpEKCqVCGqXOU7XPmTh0Yzjwixak0HYtFtYtQ2m/DkLp3BELEyxBsi7eZS7LnLdLZ1qQtx5zYDqphN7NSOMk2IWxlyU0iqQ65oWcVA2YEOueCVIUTVxzvQiKFgrIMdlL3rRB2iC33YQ5JIe8QcNWvNsRpEGVz93E8fq9qMDLa1rmUeLWwt8trZ2va2uM2tbnfL29769rfADa5wh0tchgQEACH5BAkEAHYALAAAAAB4AHgAhwAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWmBgYGpqan19fY6Ojpqamqampqmpqampqampqampqampqampqampqampqampqampqaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqurq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6ysrKysrKysrKysrKysrKysrK2tra2tra2tra6urq6urq6urq+vr7CwsLCwsLGxsbGxsbKysrKysrKysrKysrKysrOzs7Ozs7S0tLS0tLW1tbW1tba2tre3t7e3t7e3t7e3t7i4uLm5ubm5ubm5ubm5ubm5ubq6urq6urq6urq6uru7u7u7u7u7u7y8vL6+vr+/v8DAwMHBwcHBwcHBwcLCwsLCwsLCwsPDw8TExMbGxsfHx8jIyMjIyMnJycnJycnJycrKyszMzM3Nzc7Ozs/Pz9DQ0NDQ0NDQ0NHR0dLS0tPT09XV1dbW1tbW1tfX19fX19fX19fX19nZ2dra2tzc3N3d3d3d3d7e3t7e3t/f39/f3+Dg4OHh4eLi4uPj4+Tk5OTk5OXl5ejo6Ozs7O3t7e/v7/Hx8fPz8/T09PT09PT09PT09PX19ff39/n5+fj4+Pj4+Pj4+Aj+AO0IHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnEmzpk2FnZjp3Kmz082bOXnu9PmzZlChzIgWnXlUqNKlICHxetiU59OFpg5BhWgLGrVUDqsODTvMl6mtDEk9o8b2WVikPR32Gka31ya0By8dY8uX2lSGYuMyTEW38LBdlPAOdNW3LzRIgOEmZXjIl2HDV6Fe8tqYLbDIcDMbjHW5sK/Eiu3w6sxXNMHAkxVuKl0YbGo7kNayPrYQtuuBu2gP66X1th3GrKm5win5tx1Swoc5h7qX9TPICH0nPDSX9i7jr5P++02oHSFh4ZfAEwSWHFr6g+UNUrJMO5Z6gpA4dzaWvTlCW8L5Utx9Aq2WHCnw+WfQbMLZRuBAunWmTIKhHcSLcL08WFAq4i1XUHwDQSfcXUXlkklE1XX22IcKDlSZcN9BBMmJJmXiTTanPXSJeH8NBKIdrESHWkOHgBKLLdiR9Iw3TFqTy0O9iPeeQCBSEh0rD23iii1cOigSKkyG6c0zoDSUX3K8+diiHcHRJmBDlKTC5Zy2TBmSNWKKecyQCdkiHoJZRnfWQoeYQiedHobES5554shQhH0dM11Bl7R5WY8JGXnooZNWREk2jDJqTaIHkdLYM6RCREp3hZGIUCb+W256aCwDduRLqLg+Q+NBxrAFDS9JTnRIKvTZkhAkcsoqa6cUuYInrowWw+dAOwJjp0WUkDatQIeQoqysruwaki+gQitmNphS69G2dnRy5Ld0xlKmSZQcY26e0aCi0iWxwjtnViqhsuS9YTIjrkiQoOIvnamwe1IuzxLszcEfQbLwnK646tJ85ZqbJkkK+ytvrTBlwoy52VAc1bvKohIsTahIg6svKIGiLCvX3pRLx006DNIh/XIZi8ZQzSfmkyltQicoJKOVyZJurSRnKi/fhsq8++as4dZcd+01SKyELfbYZJctNkyQpK322my3rTZG5sQt99x01y03TKTkrff+3nz3rTfcdgceON5+F1444IInfvdLhjfON+KKJ064445DHvngjFNe+UWXKz655odz3rngn4Pet+Wjz1266XtjZPbrsKPt9uyzf2377bjn3lAs+uoeUifbvLMNS28bR8kz7yT/Ds0pHbJ200v5oo7yyZvjc1RtQ19TLOFQTz0yKDlf+0+dXOO99+owmxHtaWvfEiXITH++99KYJD77kLifki/mzO+/+hbBH9v0R5JYBM9/58MGAC9yPwFWrSSUkAYCzxcO+6jEgWojYEfiN0HqrYM/BsHaRgZVkAYKUIMY2UX/Oqi8Z7ALFPLAhggtYgp18IOEJcRg2kSCDBYqzxv+vwGHPOQBj2do7SGZuIY++MEPdShEh/kDCSXWwUJzWPA/Q8yiO4pBEWXQg4lgTJdBMCgSY0xwHeBbiDuyyEZxeIkhvHgHGOcYD8rgD4UWWeH5pHE9gSCDjYA01kN4McdCQoNItCvg+bzRu4VcAh6AzKI4CLILfFjykpaMkUDgUUgw6kNlB3GbSbyhPHMwryHSiGQWRVhJTF5Sk3YwRSfBCI6HmNAknXgHGvtIEBiqUh5RA44rX1kQcMySiTi04wNDsosFiuOX8LhWK4cJSztkYh/HfMfXevFLeXCxINN0ZTXtoIxj8uObXFujKtNxkHBicpx2iMcx6dG1Z3RTkOD2HGYmLWTOa2ztkb+sZTv1iQ942mEdx/ykhrABzRkKU58GNQU2Z0mOB9mim8E0iDuJiZBrmPOK4HmmKt1xRDYR1KDW/OIstameYnQTnQOFqELKecwJGecS6ozkJBWy0X0qhJOzpAcol2LPX75RoyddCCGP6c/UgAKSqsQGQ3paUIaQI6HJhAo3VRnNqSZ1Idc8JkzRAgqRsjGjCaEqSgni0ULCI6t4sUVO3eEQtTpEpfyghxiNgwxIZqghdm1IMfixj0Ny7RJpBOxXGwKNofrOpDJ9bEYCK9mLULayFbksZjfL2c569rOgDa1oR0va0pr2tKjNXUAAACH5BAkEAHcALAAAAAB4AHgAhwAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWmBgYGpqan19fY6Ojpqamqampqmpqampqampqampqampqampqampqampqampqampqaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqurq6urq6urq6urq6ysrKysrKysrKysrK2tra2tra2tra2tra2tra6urq6urq6urq6urq+vr6+vr6+vr6+vr6+vr7CwsLCwsLCwsLGxsbGxsbGxsbKysrKysrOzs7Ozs7S0tLW1tbW1tba2tre3t7e3t7e3t7e3t7i4uLi4uLi4uLi4uLm5ubu7u7y8vL29vb29vb6+vr6+vr6+vr6+vr+/v7+/v7+/v7+/v8DAwMHBwcLCwsPDw8TExMTExMXFxcXFxcXFxcbGxsbGxsbGxsbGxsbGxsjIyMnJycvLy8zMzMzMzM3Nzc3Nzc7OztDQ0NLS0tPT09PT09TU1NTU1NXV1dbW1tjY2NnZ2dra2tra2tvb29vb29vb29zc3N3d3d/f3+Hh4eHh4eLi4uPj4+Pj4+Pj4+Tk5OXl5ebm5ufn5+jo6Onp6enp6enp6erq6uvr6+3t7e/v7/Dw8PLy8vT09Pb29vf39/f39/f39/f39/f39/f39wj+AO8IHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnEmzpk2FoKDp3KkT1M2bOXnu9PmzZlCh0IgWnXlUqNKlIBPpetiU59OFpgpBhRhr2rVUDqsODXssmKmtDEdJu8ZWWlikPR36OkbXVye0By0tY8v32lSGYuMybEW38DFdjfAOhNW377REgOEmZVgomGHDV6Fa8tqYbbDIcDMbhHW5cLDEiu/o6sxXNMHAkxV2Kl24VWqBidayXrYQtuuBumgf86X19h3GrK/Bwin5951Rwo85h7qXtTTICH0nLDSX9l/jAkH+JfebUDtCwsItgScYLPk09QfNG2xkmfby9QMTce6sLHtzhLMIF0xx+Am0WnKjxPefQbMJZ1uBBOnWmTMKhnYQL8L5AmFBqYx332sLEgSdcHcVFYsmEVXX2WMFySdQZcJ951AiKJqkyTfa+IJaQ5aMJ+MdLt7xSnQ7UjbKLLpgR5I03zSJTSwP9TIefOGFeEcj0b3yUCex6OKlliSZ0uSY30gjSkP6JcfbQC4GR9uADUnyipd06kJlSNiQSaYyRSIUy3gJbhndWQsVkkqddUIpki566pkjQxL2tcx0BVni5mW8pIUkonWeCVIj2jTaKDZgIjRKY9J8GNEo3RVWIkL+mnTJKaKzENiRL6LmKk2NBynD1jRJWlRIK/XNklAic846q6cfvZJnro0O06dAPQZzp0WNkDbtHYWUouysJ5LkS6jQkqnNj9R6tK0om35L5yyBltTIMuXqOQ2hKFkiq7t0pmKrSaYwWe+YzPA6UiKt8FvnK5K4FMuzA39jcFQK0xnLqy41Mu7Aa5KUML/w/vuSJs6Uq83EISXS7qytKFmTKdPkquFJoygLy7U3xULumNhsG1Ih+3o5C8ZQaUymoih1UucoIm+lCZNurTTnKy4bZwqzKVmC84Zcd+311yC1IvbYZJdt9tgwNaL22my37fbaGKEj99x012333DCdrff+3nHf7bffee8tONl9/2043i8NrviDFh3uODqBL873RY8fHrnkZxde+d2XY1625pvX3bnnaFMeOueJk242Rqqv/tLbsMcO9uy01257Q7Dgm1IhTSsGSjfydMOSJZpsvZUk0MijvDwzo0SjJtD7/JMv7CyvvDkN5wv99ppUfRMs41hvPTMoNcI995b0DhMo2YgvPjuUbnT+/NnPJMky1bsv/jQmSTL//9JLiS/Mob8Cxg8j/0ugJbyHElgAr4Duy8YBM+K/BM4vfSqRxDQg6L5xqMokhSCeBc9Xv5IwI38cVB47jnEQrInkeSPkXgA1YgsCpnB50SjhQERxj2y4MCT+5ovh9hYYEmbccHnd+M037nEPekBDhyERoRC7BxJJoBCC5vhgQWDBxC664zMlSYQURyiSY3CQHeRbiDu6yMZxgEVeMWQgR2zovmlA0SDLYKMetSiSCl6QJLBwXzd0lxBJ0EOPXRwHQWbxj0Y6spHG2kgI56c+jjxQHuZoHkOmgcguYo2Rj3RkJDkixiGaBBTyQOMdEcLDTt4DGgUBZSj/McqOBBEltpigQMbhSnroUJahrKVH5CgTXrjyHmBc5CwheTs1ulIdBwHmI4XZTIJA45h8lKYoq3kQQ7ryGwBaJi25aZBs9PKHAtEmM8m5mGPCMpzLpGY1edlJd6zyDup7HCc77xCMYyYzmuKUp+0kscZOKlIh+RRo7a7pyjcmJKHsFMUhO5kNhkCUnMbspC8tGtB9ioKebHznQi66T1gU9B7ucAhJ93mHZRwyUw1ZKUvv9xCZshQiNr2pSjuqU4nktKcj5SlQh0rUohr1qEhNqlKXytSmOvWpSQ0IACH5BAkEAHQALAAAAAB4AHgAhwAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWWNjY3h4eI2NjZ2dnaSkpKioqKmpqampqampqampqampqampqampqampqampqampqaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqurq6urq6urq6urq6ysrKysrKysrK2tra2tra2tra2tra2tra2tra2tra6urq6urq6urq6urq6urq6urq+vr7CwsLGxsbGxsbKysrKysrOzs7Ozs7Ozs7S0tLS0tLS0tLW1tbW1tbW1tba2tra2tre3t7i4uLi4uLm5ubm5ubq6uru7u7u7u7y8vLy8vL29vb6+vr+/v8DAwMHBwcLCwsLCwsLCwsPDw8PDw8PDw8PDw8TExMTExMfHx8jIyMnJycnJycnJycnJycrKysrKysrKysrKysvLy8zMzM7Ozs/Pz9DQ0NDQ0NDQ0NHR0dHR0dPT09TU1NbW1tjY2NjY2NnZ2dnZ2dnZ2dra2t3d3d7e3t/f39/f3+Dg4ODg4ODg4OHh4eLi4uPj4+Tk5OXl5ebm5ubm5ufn5+fn5+fn5+jo6Onp6enp6erq6uzs7Ozs7O3t7e3t7e7u7u7u7u/v7/Dw8PLy8vT09PX19ff39/n5+fr6+vv7+/v7+/v7+/v7+/v7+/v7+wj+AOkIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnEmzpk2FmZrp3Kkz082bOXnu9PmzZlChzYgWnXlUqNKlIBHFetiU59OFpQZBhegKGrVSDqsOdchpGC+wWxd+ckatrbOwSHs63DWs7q5LaQ9OOta2L7WpDMXKZYiqruFhsx7lHajKr19oiALHTcpwEK/DhzktpjPJq+O2uyTHvXrQFWbDvBRvjvW5L16ck0kXvHTaMKrNAhGxbX1soWDKCmfVHrZLK246jVtTUwV7tEJRw4dpPi6Qb2tnkRH+li1wEN3as6j+D7yk/G/C7QkLD58kfuAu5dDYH0R/8NHl2q7aD0Tk+fMw7bEhBMtwvBinHx2sKffJfAEaRNtwtx040G6fIcOgcwbpMlxoEg5USnnMFUQfQdAN99pPrsj3kHWfQSZigwNZNlx4ECFyIkmTcHONLtk1NEl5gA00okCsRKdaQ4CIEsstPY7kDDdQUpOfQ7qUpyIdQz4SHStUwXLLl62UNAqUZHLjzHQL8adcb0LCSIdwtRXY0COtfGnnLVeCRE2ZZRbTJEKulLfgQw/WhpZCgJxy552wjBQLn3zuyBCFfh1zI0OTwImZLgwpueiig36EyDWQQkpNhAh94pgzIUokynf+hl06m5efLhoLICDpUuquzuRJ0DBtQRPLnxENgsp9jSKESJ211hqqR6jsuSukvBDLGTW7+ErRI6YdSRAgpTRbKyyyeqQLqdOWeU2QBGl7kbcDfbKkuHfGIspJiBSTLp/QjKLSJLTSa+cpuKY0ypP7knmMux4hoorAd7YCb0qBJkwmwxshArGdsHCXEiLnJlyMSQ8LbG/BMk2CTLrXYMyRVPSqYm1Mo0CzK6cnidJsilu5gi6Z1MwMEiABfxmLxzaBXOaUKGVypygoLzbJk2+tVGcrQqc1CpopTeJyh2CHLfbYFp1i9tlop6322TA94vbbcMct99sYmWP33Xjnrff+3TCx4vffgAcu+N9172244X0PrrjihR/uON8vLS554I0/7njik09eueWIR5655hdx/jjmnzMeuuiHk1664JujjrfqqwOO0dq019723LjjTvbuvPfuO0Kq+LvSIAZSd8k18VzD0iWfXBJ1Xo8oE8/08dyi0iOfZP+JJJvdsg7104sz8UiZaJ89J+PXpIo34INvDEqSmG/+JcXTRF777a9T7keAcCK//JV43kseYYzv4a990DBJJf73P05wLya3EMcBJ7g/jgCCgRjMRPpIhrwJ4o8aFewIIBaIwf/R73rP8CD+vNGqkwyifCWU39c4UkAVgm8dvJiPSR7hvxhqz4H+JHmFBG1IPWWMLxP2oAbSirXBSfjQfJnI2kWMQUTqXaOC2LCHPeaBjA06ZBCIcFv9vsW8J2bPixZ5hAFVSI4WGqQVWowjOzgkEUCEEW4CJAgiYBhDrnmEFypcx/sW0o44GvIbh0LSHeMmRTpIoocYRCNGyDFBaEiyGIbMZJgeAojc5dEgTmTgEjOiCvxdQ3gLecQ8MhnHbxDEFf+IpSxjyTQ6LJKRXyyj+cb4kQ7GgxzWc8gzWBnHp8BylrKsJR1yx0uEPIKPIeTIJeIhSEkKiZhatNArkZnMggwidxF55Cc++ZFXRNMg38DmPOB1TG4q05a4a2RB7LiUWGDTHnT6HEg7kfnOTuKOnNQpJDHNURpu0vIgt4SbPPOCjHtusiD7nOU76eDPuTUTN6rEJjYAZdB/TJQO35zbQqFCDXVyJ6LdVBbuLpoXOGJTmwU16EdBCk79pJOY7dggSg+akIS+baQ22cU982mQnXoUUZ6kziPYgU1XKsSoM81NPKnTUGwmMqbuXEhF5cZSoKySmNRgCFQrM9XVqFOSY2WIT90GUKDc1JAwfWpHoxojuQG1Jq0QqD3a4ZC0qvWnbU1LMVbJLrnK1CGdRERgF/OIkfV1rpz83UL8KtmLULayFbksZiei2c169rOgDa1oR0va0pr2tKhNrWpXu7uAAAAh+QQJBABQACwAAAAAeAB4AIcAAAABAQECAgIDAwMEBAQFBQUGBgYHBwdycnKpqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqamqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrq6urq6urq6urq6urq6urq6urq6usrKysrKytra2tra2tra2tra2urq6urq6urq6urq6vr6+vr6+vr6+vr6+vr6+vr6+wsLCwsLCxsbGxsbGxsbGxsbGysrKysrKysrKzs7Ozs7Ozs7O0tLS1tbW2tra3t7e3t7e4uLi4uLi4uLi4uLi5ubm5ubm6urq6urq6urq7u7u7u7u9vb2+vr6/v7+/v7/AwMDAwMDBwcHCwsLDw8PFxcXGxsbGxsbGxsbGxsbGxsbHx8fHx8fHx8fHx8fHx8fIyMjKysrLy8vMzMzNzc3Nzc3Nzc3Ozs7Ozs7Ozs7Ozs7Ozs7Pz8/Q0NDS0tLT09PU1NTV1dXV1dXW1tbX19fY2Nja2trb29vc3Nzc3Nzd3d3d3d3e3t7f39/h4eHi4uLj4+Pj4+Pj4+Pk5OTk5OTk5OTl5eXm5ubo6Ojp6enq6urq6urq6urr6+vr6+vr6+vs7Ozt7e3u7u7v7+/w8PDx8fHx8fHx8fHy8vLz8/P09PT29vb19fUI/gChCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6ZNhaCs6dypE9TNmzl57vT5s2ZQodaIFp15VKjSpSAP3XrYlOfThasIQYVIK5u3VQ6rDg3LzBjYrQtDWfPG1lpYpD0dEmNGl1gntAcpQWPL19tUhmLjMnxFtzAzXIvwDnzVt2+2Q4DhJmVIyJhhw1ehUvLamC2xyHAzG6x1ubCxxIqh3OrM9y5OyaIJdipd+FVqgYeusfYGbWHgyQpx0WZGTOttKIx32074O7bAUcOZOd+6l7U1yAibJyQ0lzau47J3/vtlDjsh4eGXwBMktjsbpezlDy6yTLuWeoKHOHdGBj80wlvDGWPcfQKttlsoB2ln0GzDLUfgQLqxJk2C8RUEzHCfPUjQKuI5OJCCBEE3nGs/yZIeRNV19lhBIApU2XDfQXQIiSRdck44wGDXECXi/fVhhQLBEh1qDf1hCi45mmTNOUx6I8tDu4j33o/+DbRIdLBQdQswXNJSkilMhnlONdMNlN9uvVGJ1FPC0SZgQ5DQwuWcwJwokjdiismMjgnRIh6CDzFI21kK/fEKnXT6CNIteeaJI0MRNgYNjQxd0uZlwDB0JKKIjhLSIeE02qg3HhoUSmPXlPrQKN0VRmlB/p1sySmiuPwBEjCi5lqNnQYhw1Y2t/ApESGv0KdoQYfIOeusnoLEWK6iEiOsQDwSMyVGi5BGJEF/rLLsrLi86hEwoUIrZjjHUuvRtgONguS3dOJiykmHMGNuntnMm9IlssI75yu2pmTKkveGGQ2vIh0Ci7900gKJS7LgWTCTCH90CMNz4lKmSReXay4zJi3sr7wBy3RJNOaGU3FU7y4Ly7QymZJNrpmeZMqytqxckyweNwlzSH+0zKXGil0s5pMpgUKnKSUrdsmSbq0kJy0/42XKxiBdorOGXHft9dcerSL22GSXbfbYMEGi9tpst+322hjFI/fcdNdt99ww0aL3/t589+333nHfLbjgef9tuOGBD6443i8d7njfiS+ueOGPPx655IQ3XrnlF2G+OOWbI96554ODHrrfl5NOt+mn843R2bDHnvbbtNMO9u245657Q680q9IhVaPVSTj7hMMSKKmA0jRei0Szz/P7xIgSJKlUn8olyy+FSz3QP88OuyP9MYr11ZvyMFSvpNN99yCfdAn55IcSvEudcLP++vWI+5GR8MP/SfYuuRL37re+a5jkE/3rnymwFxNcsIOAENQfR/6QwAqO4nwseQXxIHg/bkiwI39AYAX7B4r5fWQR1+Dg/dKhKpIcYnwjhF8nAAgSZgxQhc+rR4YK8gmPAJAS/qaIIfkWSBJaPBCH0IsG+KDwCX9oo4cZ+QMkNAHAP2hCiOQbxRI1wgwkQi8cEgyHP/zRj2hgkCKLuIQmNHHGgvwBeVis3rU6sogbcpAdLRwILMbIR3vUTCKHUOMa1zgg+cAwhkwLCTFUWI/2KeQefIxkOlIBEUJQYpCYnCNCLhHEEWrSI0e83zW2SBBkRPKUWXoIITDJShP+oRMWJMkr7hcO3ykEEv04JR/V4cY/+PKXy7skK9e4tYIQAo7kM2FGNrgPdkgPUrrkIxQHAkxgunGYgySlQSBxSKxlpBP7aKQ2eRjNMUbDINX8pUEWgc010vAgf+BkKt7pEVp88CDq/ijnP9oIhXT68iCCHOYnF/KHcb7kFuX0xzDg6c+DHKKdmiikhiAZzXggxJ80FOYwi3mbaCQ0lehs6EFW2U6DogWX5TTeRUV6kClik6N44YY+pxnSdCYknu3k5232WM5z3pSl8oEoPbeSz2jeQ6fUBCpA2zlQvAwjoQstlFIN8tB2KtMmkLBHOXlJ0KkaRKOshOlNPFpOSnbVpgshKTZNSpNP5DKa3CiSV7fZTgYqBqHR3Kdc0Vqpdkp0K58oaiR9yhCMOoSdrKTEUIsCC4r64x4PMaxDAnqJvx4HGf/wR7p+ylfKaOISVz0pfyI7V4SENneS3d0ES6taiqS2tVFkGi1sZ0vb2tr2trjNrW53y9ve+va3wA2uQQICACH5BAkEAE4ALAAAAAB4AHgAhwAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLC2ZmZqmpqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqurq6urq6urq6urq6urq6urq6ysrKysrKysrK2tra2tra6urq6urq6urq6urq6urq6urq6urq+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr7CwsLCwsLCwsLCwsLCwsLGxsbGxsbKysrKysrOzs7Ozs7S0tLS0tLS0tLS0tLW1tbW1tbW1tbW1tba2tra2tra2tre3t7i4uLm5ubq6urq6uru7u7u7u7u7u7y8vLy8vL29vb29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMTExMTExMXFxcfHx8jIyMnJycrKysrKysrKysrKysvLy8vLy8vLy8vLy8zMzM7OztDQ0NHR0dLS0tLS0tLS0tPT09PT09PT09PT09PT09TU1NXV1dfX19jY2NnZ2dnZ2dra2tra2tzc3N3d3d7e3uDg4ODg4OHh4eHh4eHh4eLi4uTk5Obm5ufn5+fn5+jo6Ojo6Ojo6Onp6erq6uzs7O3t7e7u7u7u7u7u7u/v7+/v7/Dw8PHx8fHx8fPz8/T09PX19fX19fb29vf39/r6+vn5+fn5+Qj+AJ0IHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnEmzpk2FpLTp3KmT1M2bOXnu9PmzZlCh2ogWnXlUqNKlIP/setiU59OFr/hAhWiLWzhWDqsODQstGditC01pC8dWW1ikPR0ig0YXWSe0By1BY8s33FSGYuMylEW3MLRei/AOlNW3L7c/gOEmZcgnmWHDV6Fa6taY77HIcDMbvHW5cLLEip3s6sz3Lk7Jogl2Kl1YVmqBf9ayhrYw8GSFvWhDQ6b1thPGrMPZTug7tkBUwqE537qXtTbICJsn5DOXdi/jspP++2UOOyFh4ZbAEzyWnFv6g9oPLrJM+5Z6gn84s06WvfxBXsIlU9x9Aq2WnCnw+VfQbMItR+BAunVWTYKhHVSMcMg8WBAr4jk4UHwEQSecaz/VgklE1XX2WEEgClSZcN9BNAiJJGGSDjm+DPKQJeL99aGCAtUSHWoN8aGKL8XoWBI26TT5TS0P+SLeewK1uEh0UIbFSzFc2kcSKk2GmQ42oTSUX3K8/VjhQMHRJmBDktzC5ZzFnDhSOGKKmYySCtkiHoIPMUjbK5TNQiedvIy0S5554shQhH1BQyNDlrR5WTEMHXnooaeENAg5jDL6TSwKmdKYNh5ChEp3hU264Jb+mx7qy4Ad+RLqrdjYeVAybHWzC3YU8SELfYkiNIicscbaKUix4Hkro8jwSRCPx1B50SKkEUkQH68kGysvrnrkSznPNurjtB5pO9ApSHpLpy+qnDSIMuXmuQ0qKmECq7tzzkKrSagwWW+Y1Og60iC18EvnLZK4VIuzA6djMEiDKDwnL9PJO+7AypiUML/w/vsSJtSUW87EnrabbC3S0oQKN7f6gpIqye6Csk21kCtmOC2PxMe+XPqSMU2D2BpmliiRQqcqIm+FCZPYsCTnLT2nhkqZK2Fys4Zcd+311x+xIvbYZJdt9tgwSaL22my37fbaGM0j99x012333DDlovf+3nz37ffecd8tuOB5/2244YEPrjjeLx3ueN+JL6544Y8/HrnkhDdeueUXYb445Zsj3rnng4Meut+Xk0636afzjdHZsMee9tu00w727bjnrntDsSyr0iBVK6YJOfuQw9IpvTcN1SLU7OP8PsWiZEks1MeiCR6K8WLP8867o+5IeLBSPfWvWPtTLOlwzz1/J2ky/vioBA+TJt2or749mpjE7fvvm6I8S/PZnv3U55aSmIJ//HvF9WLCC3cM8IH58xkCJ8gK86UkFsR7oP28EUGS8OGAE+TfKeQXkkVoQ4P2SwepUjII8YXwfaTAnkmSIUAUOu8emDJIuC4iw4Jg4hX+LxyfAkliCwfa8HnU+J4TOtGPbuxQInjARAwPgodQBHF8rGgYSJJxxOeRo4MGIUc/+uEPaCgRIpIIBSlIsTUn8AF5V6ReG691jyO6Y4UImcUY93gPmU1kRmsMJLAOIgkXvvAVPfRIMVB4D/Yp5B57jOQ64vWQP2gikJh84kA0AcQQzjEjRrSfNs5IEGRE8pSzgMgfMMlKUg4ED6SgIEliYT9y+E4hi/DHKfe4DoLgYRHADCYwE3lJVq4Rawz5AxzHR8KNZHAf7ogeQ7Cxyz3S6JfCDGYiYWnMNWqxIZYw5C1Fool9NNKVC6rmGKdREGxmcxGJdIIkurnGeCoED5z+jMX/PGILMDZkHer8h7rcmU17qrGb/qTMN2+SC3X2w4++fOcwDTIIepJikBqCZDXhQUWJwvMgnaCnJm8zDYem0iAEFaY9nbBKei70PrlUp/E6KtGVOkGK3UQmgboR0B2mVJsIqSI9LXgbPaqTnUH1qE3ladF9bgWg1bwHKX860YSEFKH38YVDIUrTdy7VCRWlZzN/sgiN7rKX91TqQoppzJH+pKTqpGRCqPpRhbS0my/dSid0Wc1uMISuXxWIJegZisDWpKHVFOhf1cqQgxoTo3qFaiSRuhDAwsmYCwTPLMx6D4dYtiFXJUUnIGscZPyjH7nwLGOTSYpQoDM1i8gvkGpr+pDX7u6zu9MIbnOLkd3y1iK+/a1wh0vc4hr3uMhNrnKXy9zmOve50FVIQAAAIfkECQQAUAAsAAAAAHgAeACHAAAAAQEBAgICAwMDBAQEBQUFBgYGBwcHCAgICQkJhoaGqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6urq6urq6urq6urq6urq6urq6urq6urq6urrKysrKysrKysrKysrKysrKysra2tra2tra2trq6urq6ur6+vr6+vr6+vr6+vsLCwsLCwsLCwsbGxsbGxsbGxsbGxsrKysrKysrKysrKysrKysrKysrKys7Ozs7Ozs7Ozs7Ozs7OztLS0tLS0tbW1tra2t7e3t7e3t7e3uLi4uLi4ubm5ubm5ubm5urq6urq6u7u7u7u7vLy8vLy8vb29vb29v7+/wMDAwcHBwcHBwsLCwsLCw8PDxMTExcXFx8fHx8fHyMjIycnJysrKysrKy8vLzc3Nzs7Oz8/Pz8/Pz8/Pz8/P0NDQ0NDQ0NDQ0tLS09PT1dXV1tbW1tbW1tbW1tbW19fX19fX19fX2NjY2tra3Nzc3Nzc3d3d3t7e39/f4ODg4uLi4+Pj5OTk5eXl5eXl5ubm6Ojo6+vr7Ozs7Ozs7e3t7e3t7u7u7+/v8fHx8vLy8/Pz8/Pz9PT09PT09fX19vb29/f39/f3+Pj4+fn5+fn5CP4AoQgcSLCgwYMIEypcyLChw4cQI0qcSLGixYsYM2rcyLGjx48gQ4ocSbKkyZMoU6pcybKly5cwY8qcSbOmTYWrxOncqXPVzZs5ee70+bNmUKHiiBadeVSo0qUgFxF72JTn04W6FkGFGKxculsOqw516GpbtVxbGcISl66tuLBIezqctq3uNFRpD2LC1rZvuqkMxcpluKuu4W3KIuUduMuv33JaFwpOynBRtcOHXS2Ggsmr47bOAselvHAYZsPVFG8m9rkvXpyjrxpEddrwrs0CF7FtjU1y7IXKam+bFhl349bpbiecLJtgLeHbNOMeuA25uOIGmSdcRLe2sukEUf4h/7v8N8LCwjuBJ+gMeTlMCLUfjHS59rD1BBd5/jwtvnmDxghXDXb4sYYcLAfJVxBtwimHH0G7feZNgv+xJ1x/DxZ0y3gODqTgQM8J99pPwKgHUXWtQVbQh1BYJtx3EDmSykmdwLNOMo48hMl4gHlYIRTAQKdaQ4fkokw0OZYEDjxMngPMQ8mMB5+PcT0VCXRPkpVMNFzeR9IsTIYJDzgzVrafY71RidRTwdU2YEOYDMPlnNGMQtI5YopJTZIKBTMegg8xWJsuDB3SC510JjMSMXnmeSNDEfq1zYgNddImZqEtZCSiiM4SkiPrNNroOYQmBItj4nQIUS3dGUapQf6pbMkposocAlIyouYKjokHUdNWOcQQGNEiu9RnTEKOyDnrrJ6CpAueuTbqDJ8E7ejMlBhFYtqQBB2iy7KzJlOmSMmEGq2Y6/RYrUfcDjTLkeDSqQxaJjlCzbl5ktMsSqPIGu+cvdia0ixL4htmN7yO5Egw/9I5DLYrAQOtwfAkDJIjDc+ZjHQvOVKuwdSYxPC/8wosUyfdnLuOxZ/Cu2ww1NI0Czm5KnpSLssaYydUwJgb5jkxj3SIv1wqw/FWHouZJUqu0JmLyYt1siQ4LMk5TNCbzTJuSqPsnOHXYIctdkq3lG322WinbTZMmLTt9ttwx+02RvjUbffdeOdtN/5MxvTt99+AB+433XoXXjjfgieeOOGGN773S4pHDjjjjjeOuOSSU1754ZBjnvlFmzt+ueeLgx664aOTHrjmp9+duup/Y6T27LSzLfftt4+t++689y6RLoCqRAklD56izj/qsHTLL7cIu9WV/0T/TzEqjfLL9b+k8sdixeQjffTztDvSH7pgf/0uXhelCzvff4+hSamYb34uxN90Sjntt5/PKSYRK7/8s3BeSyIxDe/lr33hMMks/ve/XWgvJsWYxwEnyL+RLIKBGNRF+lSii+NNMH/lqCBJFrFADP7vFvVDSSTA8cH8uaNUKKFE+UwoP1hszyQFbOH39AEjkkBtIP6n2AUNzedAkgBDgjqUXjfEJ5BD3DAjh0DFLH4okD+sYojm0wXEOjKNJEpvHSI0yB8OQcYnUuQPnYDFLGbxqvwsD4vXCyNHIqGPJM4Dhgghox6dSBFKuGKNgMTaQDAxQxruwowdUUYL9fG+hOzxkRGRESAn2ZyCpEKIJpSjR5CYv3AwsSBjfKQeIznJUm7xIH+ARQZJoov8rSN4CxHlHrvVtVrWEmqpKCUgbdgQR7zRfCkUyTqkNw/qOSSUsizIIWxpS6gdQpeA3KBCRlFIsJTkFP9g5CfzKEs+0pKZXfvhKKC5Riom5A+X/IUAPwIMTcaym1RcJjhH8cNUknNrDfhZxClrAk9EQkGe4KQiJcg5i22CB57mBCgzzbkKch4tQ8gUZR7nSc+DOIKgLFtPPyc6T3NCQYrQ5CVE4elIinr0EGqEpjtxs1GOBjQhnSDoOtOCUIUotJnn/CM08bmZiD7Sn998aUIGSs5gbqamNjXpQnIJTVcAFSo+nWUslaqQi5Izo1BpaVI7ypBTkBMWHi0KSQtFVYXYE5oG/Yksn6rMsiokpqVMRVi3AkkiuVUhOp2FKwTZUzI+5Ka3dEgkZgGLfeKHrQYBbDh1hFjfNfGujr2IYisaWY1Mdq6VhchlM8vZznr2s6ANrWhHS9rSmva0qE2tah8SEAAh+QQJBABPACwAAAAAeAB4AIcAAAABAQECAgIDAwMEBAQFBQUGBgYHBwepqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqamqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrq6urq6urq6urq6urq6urq6usrKysrKysrKysrKysrKysrKysrKysrKytra2tra2tra2tra2tra2urq6urq6urq6vr6+vr6+vr6+vr6+wsLCwsLCwsLCxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGysrKysrKzs7O0tLS1tbW2tra3t7e3t7e3t7e3t7e3t7e3t7e3t7e4uLi4uLi4uLi4uLi4uLi5ubm6urq7u7u8vLy8vLy8vLy9vb29vb29vb29vb2+vr6+vr6/v7+/v7/AwMDCwsLDw8PExMTExMTFxcXGxsbHx8fIyMjJycnKysrLy8vMzMzMzMzNzc3Nzc3Nzc3Pz8/Q0NDR0dHS0tLT09PT09PT09PT09PU1NTU1NTU1NTV1dXV1dXW1tbY2NjZ2dna2tra2trb29vb29vb29vc3Nzd3d3e3t7h4eHi4uLj4+Pk5OTl5eXn5+fp6enp6enq6urr6+vs7Ozu7u7w8PDw8PDx8fHx8fHy8vLz8/P19fX29vb29vb39/f39/cI/gCfCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6ZNha/W6dyp89XNmzl57vT5s2ZQoeuIFp15VKjS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6ET1I8tyio2i6QTpQZyITWFKCBf4HOZH1YkQY7VzaeKpYzmbR5CZLnQgvnDoTxdzT2j+op49TSlNEUIJPgK0PaUwqCeTmtKhCqSh7bzicR4RUWja8iA+XcgvgfmMYS6Fo+V8JEqrCkmR4oYSs4SmVhUS1oUMo53QeOdNCgrNfDKkrqn8JzCReRNKFJWRPAWrUq1KkKCa8hh6XYomuiqMclE1nYwliFOn8Yw/HucUs8zsQAC7EFZMAxpfJalnEULahfDCrMK7JuViY6uR1tKWIra9rURyq9ve+va3wA2ucIdL3OIa97jITa5yzxYQACH5BAkEAFgALAAAAAB4AHgAhwAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLC1ZWVqmpqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqurq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6ysrKysrKysrKysrKysrKysrKysrKysrKysrK2tra2tra6urq+vr6+vr7CwsLGxsbGxsbKysrKysrKysrOzs7Ozs7Ozs7Ozs7S0tLS0tLW1tbW1tba2tra2tra2tra2tre3t7e3t7e3t7e3t7i4uLi4uLm5ubm5ubm5ubq6urq6urq6urq6urq6uru7u7u7u7y8vL6+vsDAwMHBwcLCwsTExMbGxsjIyMjIyMjIyMjIyMjIyMnJycnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dPT09TU1NXV1dbW1tfX19jY2NnZ2dvb29zc3N7e3t7e3uDg4OLi4uPj4+Tk5OTk5OTk5OTk5OXl5efn5+jo6Orq6uvr6+zs7O3t7e3t7e3t7e/v7/Hx8fPz8/X19fb29vn5+fr6+vn5+Qj+ALEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnEmzpk2Fx/bp3Knz2M2bOXnu9PmzZlCh+4gWnXlUqNKlIEl1e9iU59OFzkRBhYhtnz9mDqsOdZjMXjxnWxkiy+evbb6wSHs6bGevbjthaQ/Cgte2r7+pDMXKZWitrmF75VLlHQjNr999pALHTcpQVLzDh5MtxgKLn+O+5iTHvXqwG2bD8hRv7va5r6+FgikrFHbasLXNAkmxbQ0P9mTSBcvVttdOK24sjVv7g4bzt0Jmw+1pPi6Qb+t8kRHGBi5QFN3a5aj+D/Sl/G/C7QkLD68lfqA55ftgaXd+MNXl2oDbYyHluXW6+aMhJM5w8hinHxasKYfMQegZRNtwtx040G6fzcMgfQWhM1w7EhLETHnMFdSgh9HhVdQjEln3GWQiYtjdfaeFB9Ep3IG0ySYoPgRLefkJNKJA2kSnWkOhOJPOO6eYdOOSOTYkTnnyDfRjKtFp81Ay57yjJThKLrmkQ/wp15uULgpXW4ENyQKOlmy+89pIXsbZpELYlLfgQw/WhtZCoWjTZpvnjPRInITOiRCFfsHzpkO1mIkZOgwZ+eefYIVE6KWbLISMY/mEKBEz3xlmonZZTvpnOqGANCimlyqUTlv+/HSTHUWiWCNPXeIkdMqapppaqY2sForQjuZEiVEqpg1JUCjW9GrqOTVutGqwXhoqkLEbKTsQM0c622Y6e5o0LbU3ruRLqd6yqU2qKpFb7UmndJNum+HI4tK45JZ0yrxsnjMdTPhiai1I8qYLLrszBRwnvN322k2SP7E6cEjO9ErOoie2ilIo6GqZzr9pjTuxSMm06QzCm325UjjvhANxhyn5gjHMNNds880YuUstTLn07PPPQAftM0atFG300UgnbTRM5zTt9NNQR+000UpXXTXTUmedNdVWd730S1qHDTXXXneNtdhik1321WCjnfZFa3t9tttbwx231XPTHbX+2ncfnbfeT+esM6s8C2244TgnrvjijA8kCsooyWJve5vUIgx7K10TzjWzLvZIK8KELkwpKhkTzunhJAP5UqX4InrovIz8USjcoH66N8ZsJUour7+u7UjJ2G57NpPbtIksvffuS6YlkeKN8MJT0znAqbiefO/YikQN9NB7o3pMpfBy/fjMi0QK9+hzk3tLolg+fvKylD8SKdujD/01xZ/0CCzvJ5+LgSeRRe3sJ7xmrC4k1evf63zxsmV5BIADQcY3CGg775GEFOJToOhaMbBQLMMXB5yIKJixDQgKJBTMoKDtuMELBGpQdLWQX0GEsYxlJKMWsmPIJpCBjW1s41f+BiGF5lR4ujt55BHW6x8vTEiQUtTwicloBUV4YQ0fWlGKCeHFAAn4jRBm5BT988XvDJKMJ5rxGExMSCuaYcU2eiohyZig/YwIkgwmDxY55IwZ90i6h7SijYAcVUJC0Yz0kUQUyauFFwnyiDLusYZPOQU1JknJSTaQjYD0ITZkiJBUDNF2+QuJ+4TBiz42JBePfCLKJFlJSjZQFJm0IsgWYowtXkNJwhBjHgfiwVQuA3MDYWUrqdFALCQjlj5M40FCEcdwTM98nISNL5NhKGG2spib6GEsmxERDC7lFL5cBhYJYs1KFhMLvEDmNoDZIUc+ko7BHKYlD0INZEaoQ7X0CKcpySlPYh7kj8iEp3ga6UtB8lOe5xQICWO5SQn5YpohLKcrESIKbWZSGQdyoi/ZWRCJzhMhyFBnQhdzjGmOzKP+RMgmqrjN9rQinOM8CEpHKpB0IrOFxyFoKqOFhZkuBJOZtEY0oZJPXypTID5VCEBjKVCoMNOXM5NpP2k6EGUgExtHtQk4U0lNhiRVIdlEJke3EoqS7nGsUkVoQ0IKyGZktSilcOcsdTVVh7B0G9aIqXhgUUaqHnSYfh1ILbaBDYPqZ38P+SpDhDHUxvW0ro7FiGIjS5HJUlYilr2sZjfL2c569rOgDa1oR0va0pr2tJsJCAAh+QQJBABTACwAAAAAeAB4AIcAAAABAQECAgIDAwMEBAQFBQUGBgYHBwdycnKpqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqamqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; 2 | 3 | export default loadingGif; 4 | -------------------------------------------------------------------------------- /src/Model/Evaluator/Predictions/Prediction.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './styles.scss'; 3 | 4 | export interface IPrediction { 5 | prediction: string; 6 | label: string; 7 | src: string; 8 | }; 9 | 10 | interface IProps { 11 | prediction: IPrediction; 12 | } 13 | 14 | const Prediction: React.SFC = ({ 15 | prediction: { 16 | src, 17 | prediction, 18 | label, 19 | }, 20 | }) => ( 21 |
  • 22 | 23 |
      24 |
    • Prediction: {prediction}
    • 25 |
    • Label: {label}
    • 26 |
    27 |
  • 28 | ); 29 | 30 | export default Prediction; 31 | -------------------------------------------------------------------------------- /src/Model/Evaluator/Predictions/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './styles.scss'; 3 | import Prediction, { 4 | IPrediction, 5 | } from './Prediction'; 6 | 7 | interface IProps { 8 | predictions: IPrediction[]; 9 | } 10 | 11 | const Predictions: React.SFC = ({ 12 | predictions, 13 | }) => { 14 | return ( 15 |
      16 | {predictions.map((prediction: IPrediction, idx: number) => ( 17 | 18 | ))} 19 |
    20 | ); 21 | }; 22 | 23 | export default Predictions; 24 | -------------------------------------------------------------------------------- /src/Model/Evaluator/Predictions/styles.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | img { 3 | width: 100%; 4 | max-width: 300px; 5 | display: block; 6 | } 7 | } 8 | 9 | .info { 10 | border-radius: 0 0 5px 5px; 11 | background: rgba(0,13,51,0.6); 12 | padding: 10px !important; 13 | display: flex; 14 | color: rgba(255,255,255,0.8); 15 | flex: 1; 16 | margin-bottom: 20px !important; 17 | 18 | li { 19 | flex: 1; 20 | } 21 | } 22 | 23 | .label { 24 | text-align: right; 25 | } 26 | -------------------------------------------------------------------------------- /src/Model/Evaluator/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Dropzone from '../../Dropzone'; 3 | import { 4 | IFileData, 5 | getFilesAsImageArray, 6 | } from '../../utils/getFilesAsImages'; 7 | import Predictions from './Predictions'; 8 | import { 9 | IPrediction, 10 | } from './Predictions/Prediction'; 11 | 12 | interface IProps { 13 | predict: Function; 14 | predictions: IPrediction[]; 15 | } 16 | 17 | interface IState { 18 | imagesParsed: number; 19 | totalFiles: number; 20 | } 21 | 22 | class Evaluator extends React.Component { 23 | constructor(props: IProps) { 24 | super(props); 25 | 26 | this.state = { 27 | imagesParsed: 0, 28 | totalFiles: 0, 29 | }; 30 | } 31 | 32 | private onParseFiles = async (files: FileList) => { 33 | const imageFiles: IFileData[] = await getFilesAsImageArray(files); 34 | this.props.predict(imageFiles); 35 | } 36 | 37 | render() { 38 | return ( 39 | 40 | 44 | Drop Images to test 45 | 46 | 47 | ); 48 | } 49 | }; 50 | 51 | export default Evaluator; 52 | -------------------------------------------------------------------------------- /src/Model/Metrics/Info.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './styles.scss'; 3 | 4 | export interface IMetricsInfoProps { 5 | title: string; 6 | data: IDatum[]; 7 | } 8 | 9 | export interface IDatum { 10 | data: string | number; 11 | label: string; 12 | } 13 | 14 | const Info: React.SFC = ({ 15 | title, 16 | data, 17 | }) => { 18 | return ( 19 | 20 |

    {title}

    21 |
      22 | {data.map((datum: IDatum) => ( 23 |
    • 24 | {datum.data} 25 | 26 |
    • 27 | ))} 28 |
    29 |
    30 | ); 31 | }; 32 | 33 | export default Info; 34 | -------------------------------------------------------------------------------- /src/Model/Metrics/Logs/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import classNames from 'utils/classNames'; 3 | import styles from './styles.scss'; 4 | 5 | interface IProps { 6 | logs: { 7 | [index: string]: any; 8 | }; 9 | } 10 | 11 | interface IState { 12 | expanded: boolean; 13 | } 14 | 15 | class Logs extends React.Component { 16 | public state: IState = { 17 | expanded: false, 18 | }; 19 | 20 | handleClick = (e:any) => { 21 | this.setState({ 22 | expanded: !this.state.expanded, 23 | }); 24 | } 25 | 26 | render() { 27 | const { 28 | logs, 29 | } = this.props; 30 | 31 | return ( 32 |
    37 |
    38 | 39 | 40 |
    41 |
    42 |           {logs.loss.join('\n')}
    43 |         
    44 |
    45 | ); 46 | } 47 | } 48 | 49 | export default Logs; 50 | -------------------------------------------------------------------------------- /src/Model/Metrics/Logs/styles.scss: -------------------------------------------------------------------------------- 1 | .logs { 2 | position: relative; 3 | min-height: 40px; 4 | display: flex; 5 | flex: 1; 6 | margin-right: 20px; 7 | 8 | &.expanded { 9 | .expand { 10 | a { 11 | transform: rotate(90deg); 12 | } 13 | } 14 | 15 | pre { 16 | display: block; 17 | max-height: 100px; 18 | padding: 5px 10px; 19 | } 20 | } 21 | 22 | pre { 23 | transition-duration: 0.5s; 24 | max-height: 0px; 25 | margin: 0 auto; 26 | overflow-y: scroll; 27 | font-size: 12px; 28 | width: 100%; 29 | background: rgba(255,255,255,1.0); 30 | border-radius: 4px; 31 | padding: 0 10px; 32 | color: rgba(0,0,0,0.6); 33 | } 34 | } 35 | 36 | .expand { 37 | height: 21px; 38 | position: absolute; 39 | display: flex; 40 | align-items: center; 41 | top: -20px; 42 | left: 0; 43 | 44 | label { 45 | font-size: 12px; 46 | } 47 | 48 | a { 49 | font-size: 16px; 50 | transition-duration: 0.2s; 51 | display: inline-block; 52 | margin-right: 5px; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Model/Metrics/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './styles.scss'; 3 | import Info, { 4 | IDatum, 5 | } from './Info'; 6 | // import { 7 | // IImageData, 8 | // } from 'utils/getFilesAsImages'; 9 | import Logs from './Logs'; 10 | export interface ImageError { 11 | image: any; 12 | error: Error; 13 | file?: any; 14 | index: number; 15 | } 16 | 17 | interface IProps { 18 | labels?: string[]; 19 | downloading: boolean; 20 | onDownload?: Function; 21 | errors?: ImageError[]; 22 | logs: { 23 | [index: string]: any; 24 | }; 25 | accuracy: IDatum[]; 26 | } 27 | 28 | // interface IData { 29 | // label: string; 30 | // } 31 | 32 | const getData = (labels: string[] = []) => { 33 | const numOfLabels = labels.reduce((obj, label) => ({ 34 | ...obj, 35 | [label]: (obj[label] || 0) + 1, 36 | }), {}); 37 | 38 | return Object.keys(numOfLabels).map((label) => { 39 | return { 40 | label, 41 | data: numOfLabels[label], 42 | }; 43 | }); 44 | }; 45 | 46 | const getErrors = (errors: ImageError[] = []) => errors.map(({ file }, key) => ( 47 |

    48 | Error loading image: {`${file.path}/${file.file.name}`} 49 |

    50 | )); 51 | 52 | class Metrics extends React.Component { 53 | render() { 54 | const { 55 | labels, 56 | onDownload, 57 | downloading, 58 | accuracy, 59 | logs, 60 | errors, 61 | } = this.props; 62 | 63 | return ( 64 |
    65 | 69 |
    70 | {errors && getErrors(errors)} 71 |
    72 | 76 |
    77 | 78 | {onDownload && ( 79 | 85 | )} 86 |
    87 |
    88 | ); 89 | } 90 | } 91 | 92 | export default Metrics; 93 | 94 | export { IMetricsInfoProps, IDatum } from './Info'; 95 | -------------------------------------------------------------------------------- /src/Model/Metrics/styles.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | border-radius: 5px 5px 0 0; 3 | width: 100%; 4 | height: 100%; 5 | background: rgba(0,13,51,0.6); 6 | padding: 20px; 7 | color: rgba(255,255,255,0.8); 8 | font-size: 20px; 9 | display: flex; 10 | flex-direction: column; 11 | 12 | button { 13 | width: 120px; 14 | height: 40px; 15 | border: none; 16 | border-radius: 5px; 17 | } 18 | } 19 | 20 | .footer { 21 | flex: 1; 22 | display: flex; 23 | align-items: flex-end; 24 | } 25 | 26 | .datum { 27 | display: flex; 28 | flex-direction: column; 29 | justify-content: center; 30 | align-items: center; 31 | flex: 1; 32 | 33 | data { 34 | font-size: 40px; 35 | } 36 | 37 | label { 38 | color: rgba(255,255,255,0.6); 39 | } 40 | } 41 | 42 | .data { 43 | display: flex; 44 | margin-bottom: 20px !important; 45 | } 46 | 47 | .header { 48 | font-size: 16px; 49 | margin: 0; 50 | } 51 | 52 | .error { 53 | margin: 0; 54 | font-size: 12px; 55 | } 56 | 57 | .errors { 58 | margin-bottom: 20px; 59 | } 60 | -------------------------------------------------------------------------------- /src/Model/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './styles.scss'; 3 | // const styles = require('./styles.scss'); 4 | import Evaluator from './Evaluator'; 5 | import Metrics, { 6 | IDatum, 7 | ImageError, 8 | } from './Metrics'; 9 | import { 10 | IPrediction, 11 | } from './Evaluator/Predictions/Prediction'; 12 | // import { 13 | // IImageData, 14 | // } from '../utils/getFilesAsImages'; 15 | 16 | interface IProps { 17 | labels: string[]; 18 | downloading: boolean; 19 | onDownload?: Function; 20 | predict?: Function; 21 | predictions: IPrediction[]; 22 | errors?: ImageError[]; 23 | logs: { 24 | [index: string]: any; 25 | }; 26 | accuracy: { 27 | training?: number; 28 | evaluation?: number; 29 | }; 30 | } 31 | 32 | interface IState { 33 | } 34 | 35 | const getEvaluation = (predictions: any[]) => { 36 | if (predictions.length > 0) { 37 | return predictions.reduce((sum, { 38 | prediction, 39 | label, 40 | }) => sum + (prediction === label ? 1 : 0), 0) / predictions.length; 41 | } 42 | 43 | return null; 44 | }; 45 | 46 | class Model extends React.Component { 47 | render() { 48 | const { 49 | labels, 50 | onDownload, 51 | downloading, 52 | predict, 53 | predictions, 54 | logs, 55 | accuracy: { 56 | training, 57 | }, 58 | errors, 59 | } = this.props; 60 | 61 | const evaluation = getEvaluation(predictions); 62 | 63 | const accuracy: IDatum[] = [{ 64 | data: training ? `${Math.round(training * 100)}%` : '--', 65 | label: 'Training', 66 | }, { 67 | data: evaluation ? `${Math.round(evaluation * 100)}%` : '--', 68 | label: 'Evaluation', 69 | }]; 70 | 71 | return ( 72 |
    73 | 81 | {predict && ( 82 | 86 | )} 87 |
    88 | ); 89 | } 90 | } 91 | 92 | export default Model; 93 | 94 | export { ImageError } from './Metrics'; 95 | -------------------------------------------------------------------------------- /src/Model/styles.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | -------------------------------------------------------------------------------- /src/Preview/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import classNames from 'utils/classNames'; 3 | import styles from './styles.scss'; 4 | import Loading from '../Loading'; 5 | // import { 6 | // loadImage, 7 | // } from 'utils/getFilesAsImages'; 8 | // const styles = require('./styles.scss'); 9 | // import { 10 | // IImageData, 11 | // } from '../utils/getFilesAsImages'; 12 | 13 | interface IProps { 14 | images?: string[]; 15 | } 16 | 17 | interface IState { 18 | imageIdx: number; 19 | // images: { 20 | // [index:string]: HTMLImageElement | null; 21 | // }; 22 | } 23 | 24 | const LOOP_SPEED = 75; 25 | 26 | class Preview extends React.Component { 27 | constructor(props: IProps) { 28 | super(props); 29 | 30 | this.state = { 31 | imageIdx: 0, 32 | // images: { }, 33 | }; 34 | } 35 | 36 | private timeout: any; 37 | 38 | componentWillMount() { 39 | this.loopImages(); 40 | } 41 | 42 | // componentWillReceiveProps(nextProps: IProps) { 43 | // if ((this.props.images || []).length !== (nextProps.images || []).length) { 44 | // (nextProps.images || []).map((src: string) => { 45 | // if (this.state.images[src] === undefined) { 46 | // console.log('load image', src); 47 | 48 | // this.setState({ 49 | // images: { 50 | // ...this.state.images, 51 | // [src]: null, 52 | // }, 53 | // }); 54 | 55 | // loadImage(src).then(image => { 56 | // this.setState({ 57 | // images: { 58 | // ...this.state.images, 59 | // [src]: image, 60 | // }, 61 | // }); 62 | // }); 63 | // } 64 | // }); 65 | // } 66 | // } 67 | 68 | componentWillUnmount() { 69 | if (this.timeout) { 70 | clearTimeout(this.timeout); 71 | } 72 | } 73 | 74 | private last: any; 75 | loopImages = () => { 76 | if (this.last) { 77 | const now = (new Date()).getTime(); 78 | const diff = now - this.last; 79 | if (diff > LOOP_SPEED + 200) { // 200 ms wiggle room 80 | // TODO: this indicates a UI slowdown 81 | // console.log('diff', diff); 82 | } 83 | } 84 | if (this.timeout) { 85 | clearTimeout(this.timeout); 86 | } 87 | 88 | this.setState({ 89 | imageIdx: this.state.imageIdx + 1, 90 | }); 91 | 92 | this.last = (new Date()).getTime(); 93 | this.timeout = setTimeout(() => { 94 | this.loopImages(); 95 | }, LOOP_SPEED); 96 | } 97 | 98 | render() { 99 | // const images = Object.values(this.state.images); 100 | const { 101 | images, 102 | } = this.props; 103 | 104 | const src = images && images[this.state.imageIdx % images.length]; 105 | 106 | const className = classNames(styles.container, { 107 | [styles.images]: images && images.length > 0, 108 | }); 109 | 110 | return ( 111 |
    112 | {src ? ( 113 |
    119 | ) : ( 120 |
    121 | 122 | Reading images 123 |
    124 | )} 125 |
    126 | ); 127 | } 128 | } 129 | 130 | export default Preview; 131 | -------------------------------------------------------------------------------- /src/Preview/styles.scss: -------------------------------------------------------------------------------- 1 | .loader { 2 | opacity: 1; 3 | position: absolute; 4 | height: 100%; 5 | width: 100%; 6 | display: flex; 7 | justify-content: center; 8 | flex-direction: column; 9 | align-items: center; 10 | color: rgba(0,0,0,0.3); 11 | 12 | img { 13 | height: auto; 14 | width: auto; 15 | } 16 | } 17 | 18 | .container { 19 | border-radius: 5px; 20 | display: flex; 21 | justify-content: center; 22 | position: relative; 23 | align-items: center; 24 | width: 100%; 25 | height: 100%; 26 | border: 2px dashed rgba(0,0,0,0.2); 27 | 28 | .img { 29 | width: 100%; 30 | height: 100%; 31 | background-color: rgba(0,0,0,0.2); 32 | background-repeat: no-repeat; 33 | background-position: 50% 50%; 34 | background-size: cover; 35 | position: relative; 36 | } 37 | 38 | &.images { 39 | border: 2px dashed rgba(0,0,0,0); 40 | } 41 | } 42 | 43 | .loading { 44 | width: 16px; 45 | height: 16px; 46 | } 47 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.scss' { 2 | const content: any; 3 | export default content; 4 | } 5 | declare module '*.css' { 6 | const content: any; 7 | export default content; 8 | } 9 | 10 | declare module '*.png' 11 | declare module '*.jpg' 12 | declare module '*.jpeg' 13 | declare module '*.gif' 14 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './App'; 2 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface IProps { 2 | } 3 | 4 | export interface ITrainResult { 5 | epoch: number[]; 6 | history: { 7 | acc: number[]; 8 | loss: number[]; 9 | }; 10 | model: any; 11 | params: any; 12 | validationData: any; 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/__tests__/classNames.test.ts: -------------------------------------------------------------------------------- 1 | import classNames from '../classNames'; 2 | 3 | describe('classNames', () => { 4 | test('it handles nothing', () => { 5 | expect(classNames()).toEqual(''); 6 | }); 7 | 8 | test('it handles a single class', () => { 9 | expect(classNames('foo')).toEqual('foo'); 10 | }); 11 | 12 | test('it handles multiple classes', () => { 13 | expect(classNames('foo', 'bar', 'baz')).toEqual('foo bar baz'); 14 | }); 15 | 16 | test('it does not include undefined or null', () => { 17 | expect(classNames('foo', undefined, null, 'baz')).toEqual('foo baz'); 18 | }); 19 | 20 | test('it handles an object where value is true', () => { 21 | expect(classNames({ 22 | foo: true, 23 | })).toEqual('foo'); 24 | }); 25 | 26 | test('it handles an object where value are true and false', () => { 27 | expect(classNames({ 28 | foo: true, 29 | bar: false, 30 | })).toEqual('foo'); 31 | }); 32 | 33 | test('it handles an object where value are true and false', () => { 34 | expect(classNames('bar', { 35 | foo: true, 36 | quom: false, 37 | }, 'baz')).toEqual('bar foo baz'); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/utils/classNames.ts: -------------------------------------------------------------------------------- 1 | interface IClassNameObject { 2 | [index:string]: boolean | undefined; 3 | } 4 | 5 | const arr: string[] = []; 6 | 7 | type Name = string|IClassNameObject|undefined|null; 8 | 9 | const transformObjToArr = (name: IClassNameObject) => Object.entries(name).filter(entry => entry[1]).map(([ key ]) => key); 10 | 11 | const classNames = (...names: Name[]) => (names || []).reduce((arr, name: Name) => { 12 | if (!name) { 13 | return arr; 14 | } 15 | 16 | if (typeof name === 'string') { 17 | return arr.concat(name); 18 | } 19 | 20 | return arr.concat(transformObjToArr(name)); 21 | }, arr).join(' '); 22 | 23 | export default classNames; 24 | -------------------------------------------------------------------------------- /src/utils/getFilesAsImages.ts: -------------------------------------------------------------------------------- 1 | export interface IFileData { 2 | label: string; 3 | file: any; 4 | src: string; 5 | }; 6 | 7 | export interface IImageData { 8 | label: string; 9 | image: HTMLImageElement; 10 | }; 11 | 12 | export const loadImage = async (src: string) => new Promise((resolve, reject) => { 13 | const image = new Image(); 14 | image.src = src; 15 | image.onload = () => resolve(image); 16 | image.onerror = (err) => reject(err); 17 | }); 18 | 19 | export const getFilesAsImageArray = async (files: FileList): Promise => { 20 | const classes = Object.keys(files); 21 | const images = []; 22 | for (let i = 0; i < classes.length; i++) { 23 | const label = classes[i]; 24 | for (let j = 0; j < files[label].length; j++) { 25 | const file = files[label][j]; 26 | images.push({ 27 | label, 28 | src: file.src, 29 | file, 30 | }); 31 | } 32 | } 33 | return images; 34 | }; 35 | 36 | export default getFilesAsImageArray; 37 | 38 | export const splitImagesFromLabels = async (images: IFileData[]) => { 39 | const origData: { 40 | images: string[]; 41 | labels: string[]; 42 | files: any[]; 43 | } = { 44 | images: [], 45 | labels: [], 46 | files: [], 47 | }; 48 | 49 | return images.reduce((data, { 50 | src, 51 | label, 52 | file, 53 | }: IFileData) => ({ 54 | images: data.images.concat(src), 55 | labels: data.labels.concat(label), 56 | files: data.files.concat(file), 57 | }), origData); 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/nextFrame.ts: -------------------------------------------------------------------------------- 1 | const nextFrame = () => new Promise(resolve => { 2 | window.requestAnimationFrame(() => resolve()); 3 | }); 4 | export default nextFrame; 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["esnext", "dom"], 8 | "sourceMap": true, 9 | "allowJs": false, 10 | "declaration": false, 11 | "removeComments": true, 12 | "moduleResolution": "node", 13 | "rootDir": "src", 14 | "jsx": "react", 15 | "forceConsistentCasingInFileNames": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noUnusedLocals": true, 19 | "noImplicitAny": true, 20 | "strictNullChecks": true, 21 | "suppressImplicitAnyIndexErrors": true, 22 | "paths": { 23 | "*": [ 24 | "src/*" 25 | ] 26 | } 27 | }, 28 | "include": [ 29 | "src/**/*" 30 | ], 31 | "exclude": [ 32 | "__tests__", 33 | "__mocks__", 34 | "**/*.test.ts", 35 | "**/*.scss.d.ts", 36 | "src/types.ts", 37 | "node_modules" 38 | ] 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-react", 5 | "tslint-config-prettier" 6 | ], 7 | "linterOptions": { 8 | "exclude": [ 9 | "config/**/*.js", 10 | "node_modules/**/*.ts" 11 | ] 12 | }, 13 | "rules": { 14 | "prefer-for-of": false, 15 | "object-literal-sort-keys": false, 16 | "ordered-imports": false, 17 | "no-debugger": false, 18 | "no-console": false 19 | }, 20 | "jsRules": { 21 | "prefer-for-of": false, 22 | "object-literal-sort-keys": false, 23 | "ordered-imports": false, 24 | "no-debugger": false, 25 | "no-console": false 26 | } 27 | } 28 | 29 | --------------------------------------------------------------------------------