[
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\n\non:\n  # Triggers the workflow on push or pull request events but only for the master branch\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        node-version: [16]\n        os: [ubuntu-latest, macos-latest, windows-latest]\n\n    steps:\n    - uses: actions/checkout@v3\n    - uses: actions/setup-node@v3\n      with:\n        node-version: ${{ matrix.node-version }}\n    - name: Install dependencies\n      run: npm install\n    - name: Build\n      run: npm run build\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\npublic/bundle.*\npackage-lock.json\npublic/assets/js/bundle.*\npublic/assets/js/\npublic/assets/css/bundle.*\n*.swp\nyarn.lock\ndist/\nlocal-build/\ntiny-vgg/data/\npnpm-lock.yaml"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Polo Club of Data Science\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# CNN Explainer\n\nAn interactive visualization system designed to help non-experts learn about Convolutional Neural Networks (CNNs)\n\n[![build](https://github.com/poloclub/cnn-explainer/workflows/build/badge.svg)](https://github.com/poloclub/cnn-explainer/actions)\n[![arxiv badge](https://img.shields.io/badge/arXiv-2004.15004-red)](http://arxiv.org/abs/2004.15004)\n[![DOI:10.1109/TVCG.2020.3030418](https://img.shields.io/badge/DOI-10.1109/TVCG.2020.3030418-blue)](https://doi.org/10.1109/TVCG.2020.3030418)\n\n<a href=\"https://youtu.be/HnWIHWFbuUQ\" target=\"_blank\"><img src=\"https://i.imgur.com/sCsudVg.png\" style=\"max-width:100%;\"></a>\n\nFor more information, check out our manuscript:\n\n[**CNN Explainer: Learning Convolutional Neural Networks with Interactive Visualization**](https://arxiv.org/abs/2004.15004).\nWang, Zijie J., Robert Turko, Omar Shaikh, Haekyu Park, Nilaksh Das, Fred Hohman, Minsuk Kahng, and Duen Horng Chau.\n*IEEE Transactions on Visualization and Computer Graphics (TVCG), 2020.*\n\n## Live Demo\n\nFor a live demo, visit: http://poloclub.github.io/cnn-explainer/\n\n## Running Locally\n\nClone or download this repository:\n\n```bash\ngit clone git@github.com:poloclub/cnn-explainer.git\n\n# use degit if you don't want to download commit histories\ndegit poloclub/cnn-explainer\n```\n\nInstall the dependencies:\n\n```bash\nnpm install\n```\n\nThen run CNN Explainer:\n\n```bash\nnpm run dev\n```\n\nNavigate to [localhost:3000](https://localhost:3000). You should see CNN Explainer running in your broswer :)\n\nTo see how we trained the CNN, visit the directory [`./tiny-vgg/`](tiny-vgg).\nIf you want to use CNN Explainer with your own CNN model or image classes, see [#8](/../../issues/8) and [#14](/../../issues/14).\n\n## Credits\n\nCNN Explainer was created by\n<a href=\"https://zijie.wang/\">Jay Wang</a>,\n<a href=\"https://www.linkedin.com/in/robert-turko/\">Robert Turko</a>,\n<a href=\"http://oshaikh.com/\">Omar Shaikh</a>,\n<a href=\"https://haekyu.com/\">Haekyu Park</a>,\n<a href=\"http://nilakshdas.com/\">Nilaksh Das</a>,\n<a href=\"https://fredhohman.com/\">Fred Hohman</a>,\n<a href=\"http://minsuk.com\">Minsuk Kahng</a>, and\n<a href=\"https://www.cc.gatech.edu/~dchau/\">Polo Chau</a>,\nwhich was the result of a research collaboration between\nGeorgia Tech and Oregon State.\n\nWe thank\n[Anmol Chhabria](https://www.linkedin.com/in/anmolchhabria),\n[Kaan Sancak](https://kaansancak.com),\n[Kantwon Rogers](https://www.kantwon.com), and the\n[Georgia Tech Visualization Lab](http://vis.gatech.edu)\nfor their support and constructive feedback.\n\n## Citation\n\n```bibTeX\n@article{wangCNNExplainerLearning2020,\n  title = {{{CNN Explainer}}: {{Learning Convolutional Neural Networks}} with {{Interactive Visualization}}},\n  shorttitle = {{{CNN Explainer}}},\n  author = {Wang, Zijie J. and Turko, Robert and Shaikh, Omar and Park, Haekyu and Das, Nilaksh and Hohman, Fred and Kahng, Minsuk and Chau, Duen Horng},\n  journal={IEEE Transactions on Visualization and Computer Graphics (TVCG)},\n  year={2020},\n  publisher={IEEE}\n}\n```\n\n## License\n\nThe software is available under the [MIT License](https://github.com/poloclub/cnn-explainer/blob/master/LICENSE).\n\n## Contact\n\nIf you have any questions, feel free to [open an issue](https://github.com/poloclub/cnn-explainer/issues/new/choose) or contact [Jay Wang](https://zijie.wang).\n"
  },
  {
    "path": "deploy-gh-page.sh",
    "content": "npm run build\ncp -r ./public/assets ./dist\ncp -r ./public/bundle* ./dist\ncp -r ./public/global.css ./dist\nnpx gh-pages -m \"Deploy $(git log '--format=format:%H' master -1)\" -d ./dist"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"svelte-app\",\n  \"version\": \"1.0.0\",\n  \"devDependencies\": {\n    \"@rollup/plugin-replace\": \"^2.3.2\",\n    \"gh-pages\": \"^6.0.0\",\n    \"rollup\": \"^1.27.13\",\n    \"rollup-plugin-commonjs\": \"^10.0.0\",\n    \"rollup-plugin-livereload\": \"^1.0.0\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-svelte\": \"~6.1.1\",\n    \"rollup-plugin-terser\": \"^5.1.3\",\n    \"svelte\": \"^3.31.0\"\n  },\n  \"dependencies\": {\n    \"@tensorflow/tfjs\": \"^1.4.0\",\n    \"sirv-cli\": \"^0.4.4\"\n  },\n  \"scripts\": {\n    \"build\": \"rollup -c\",\n    \"dev\": \"rollup -c -w\",\n    \"start\": \"sirv public --single\",\n    \"start:dev\": \"sirv public --single --dev --port 3000\",\n    \"deploy\": \"npx\"\n  }\n}\n"
  },
  {
    "path": "public/assets/data/model.json",
    "content": "{\"format\": \"layers-model\", \"generatedBy\": \"keras v2.2.4-tf\", \"convertedBy\": \"TensorFlow.js Converter v1.3.1.1\", \"modelTopology\": {\"keras_version\": \"2.2.4-tf\", \"backend\": \"tensorflow\", \"model_config\": {\"class_name\": \"Sequential\", \"config\": {\"name\": \"sequential\", \"layers\": [{\"class_name\": \"Conv2D\", \"config\": {\"name\": \"conv_1_1\", \"trainable\": true, \"batch_input_shape\": [null, 64, 64, 3], \"dtype\": \"float32\", \"filters\": 10, \"kernel_size\": [3, 3], \"strides\": [1, 1], \"padding\": \"valid\", \"data_format\": \"channels_last\", \"dilation_rate\": [1, 1], \"activation\": \"linear\", \"use_bias\": true, \"kernel_initializer\": {\"class_name\": \"GlorotUniform\", \"config\": {\"seed\": null}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"kernel_regularizer\": null, \"bias_regularizer\": null, \"activity_regularizer\": null, \"kernel_constraint\": null, \"bias_constraint\": null}}, {\"class_name\": \"Activation\", \"config\": {\"name\": \"relu_1_1\", \"trainable\": true, \"dtype\": \"float32\", \"activation\": \"relu\"}}, {\"class_name\": \"Conv2D\", \"config\": {\"name\": \"conv_1_2\", \"trainable\": true, \"dtype\": \"float32\", \"filters\": 10, \"kernel_size\": [3, 3], \"strides\": [1, 1], \"padding\": \"valid\", \"data_format\": \"channels_last\", \"dilation_rate\": [1, 1], \"activation\": \"linear\", \"use_bias\": true, \"kernel_initializer\": {\"class_name\": \"GlorotUniform\", \"config\": {\"seed\": null}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"kernel_regularizer\": null, \"bias_regularizer\": null, \"activity_regularizer\": null, \"kernel_constraint\": null, \"bias_constraint\": null}}, {\"class_name\": \"Activation\", \"config\": {\"name\": \"relu_1_2\", \"trainable\": true, \"dtype\": \"float32\", \"activation\": \"relu\"}}, {\"class_name\": \"MaxPooling2D\", \"config\": {\"name\": \"max_pool_1\", \"trainable\": true, \"dtype\": \"float32\", \"pool_size\": [2, 2], \"padding\": \"valid\", \"strides\": [2, 2], \"data_format\": \"channels_last\"}}, {\"class_name\": \"Conv2D\", \"config\": {\"name\": \"conv_2_1\", \"trainable\": true, \"dtype\": \"float32\", \"filters\": 10, \"kernel_size\": [3, 3], \"strides\": [1, 1], \"padding\": \"valid\", \"data_format\": \"channels_last\", \"dilation_rate\": [1, 1], \"activation\": \"linear\", \"use_bias\": true, \"kernel_initializer\": {\"class_name\": \"GlorotUniform\", \"config\": {\"seed\": null}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"kernel_regularizer\": null, \"bias_regularizer\": null, \"activity_regularizer\": null, \"kernel_constraint\": null, \"bias_constraint\": null}}, {\"class_name\": \"Activation\", \"config\": {\"name\": \"relu_2_1\", \"trainable\": true, \"dtype\": \"float32\", \"activation\": \"relu\"}}, {\"class_name\": \"Conv2D\", \"config\": {\"name\": \"conv_2_2\", \"trainable\": true, \"dtype\": \"float32\", \"filters\": 10, \"kernel_size\": [3, 3], \"strides\": [1, 1], \"padding\": \"valid\", \"data_format\": \"channels_last\", \"dilation_rate\": [1, 1], \"activation\": \"linear\", \"use_bias\": true, \"kernel_initializer\": {\"class_name\": \"GlorotUniform\", \"config\": {\"seed\": null}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"kernel_regularizer\": null, \"bias_regularizer\": null, \"activity_regularizer\": null, \"kernel_constraint\": null, \"bias_constraint\": null}}, {\"class_name\": \"Activation\", \"config\": {\"name\": \"relu_2_2\", \"trainable\": true, \"dtype\": \"float32\", \"activation\": \"relu\"}}, {\"class_name\": \"MaxPooling2D\", \"config\": {\"name\": \"max_pool_2\", \"trainable\": true, \"dtype\": \"float32\", \"pool_size\": [2, 2], \"padding\": \"valid\", \"strides\": [2, 2], \"data_format\": \"channels_last\"}}, {\"class_name\": \"Flatten\", \"config\": {\"name\": \"flatten\", \"trainable\": true, \"dtype\": \"float32\", \"data_format\": \"channels_last\"}}, {\"class_name\": \"Dense\", \"config\": {\"name\": \"output\", \"trainable\": true, \"dtype\": \"float32\", \"units\": 10, \"activation\": \"softmax\", \"use_bias\": true, \"kernel_initializer\": {\"class_name\": \"GlorotUniform\", \"config\": {\"seed\": null}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"kernel_regularizer\": null, \"bias_regularizer\": null, \"activity_regularizer\": null, \"kernel_constraint\": null, \"bias_constraint\": null}}]}}}, \"weightsManifest\": [{\"paths\": [\"group1-shard1of1.bin\"], \"weights\": [{\"name\": \"conv_1_1/kernel\", \"shape\": [3, 3, 3, 10], \"dtype\": \"float32\"}, {\"name\": \"conv_1_1/bias\", \"shape\": [10], \"dtype\": \"float32\"}, {\"name\": \"conv_1_2/kernel\", \"shape\": [3, 3, 10, 10], \"dtype\": \"float32\"}, {\"name\": \"conv_1_2/bias\", \"shape\": [10], \"dtype\": \"float32\"}, {\"name\": \"conv_2_1/kernel\", \"shape\": [3, 3, 10, 10], \"dtype\": \"float32\"}, {\"name\": \"conv_2_1/bias\", \"shape\": [10], \"dtype\": \"float32\"}, {\"name\": \"conv_2_2/kernel\", \"shape\": [3, 3, 10, 10], \"dtype\": \"float32\"}, {\"name\": \"conv_2_2/bias\", \"shape\": [10], \"dtype\": \"float32\"}, {\"name\": \"output/kernel\", \"shape\": [1690, 10], \"dtype\": \"float32\"}, {\"name\": \"output/bias\", \"shape\": [10], \"dtype\": \"float32\"}]}]}"
  },
  {
    "path": "public/assets/data/nn_10.json",
    "content": "[\n  {\n    \"name\": \"conv_1_1\",\n    \"input_shape\": [\n      64,\n      64,\n      3\n    ],\n    \"output_shape\": [\n      62,\n      62,\n      10\n    ],\n    \"num_neurons\": 10,\n    \"weights\": [\n      {\n        \"bias\": 0.05529399216175079,\n        \"weights\": [\n          [\n            [\n              0.005686734337359667,\n              0.5303338170051575,\n              0.4508902430534363\n            ],\n            [\n              0.04920876398682594,\n              -0.029017755761742592,\n              0.23914846777915955\n            ],\n            [\n              -0.06062067300081253,\n              0.10217977315187454,\n              0.2789939343929291\n            ]\n          ],\n          [\n            [\n              -0.11416204273700714,\n              0.2790183424949646,\n              0.14520207047462463\n            ],\n            [\n              -0.14735761284828186,\n              -0.21102763712406158,\n              -0.15575028955936432\n            ],\n            [\n              -0.10717931389808655,\n              -0.28811952471733093,\n              -0.0018726304406300187\n            ]\n          ],\n          [\n            [\n              -0.06842527538537979,\n              0.14463859796524048,\n              -0.012262370437383652\n            ],\n            [\n              -0.36216601729393005,\n              -0.14219844341278076,\n              -0.1687535047531128\n            ],\n            [\n              0.13701502978801727,\n              0.10424327105283737,\n              0.0899757370352745\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.15698345005512238,\n        \"weights\": [\n          [\n            [\n              0.11193452030420303,\n              -0.02605212852358818,\n              -0.17117753624916077\n            ],\n            [\n              -0.1265387237071991,\n              0.11026564985513687,\n              -0.23662784695625305\n            ],\n            [\n              -0.113533616065979,\n              -0.06741151958703995,\n              0.00405964907258749\n            ]\n          ],\n          [\n            [\n              -0.20665371417999268,\n              0.09713144600391388,\n              0.18439456820487976\n            ],\n            [\n              0.22104552388191223,\n              0.06683074682950974,\n              0.08409177511930466\n            ],\n            [\n              -0.1461413949728012,\n              -0.0339348129928112,\n              0.09842409938573837\n            ]\n          ],\n          [\n            [\n              -0.2126864641904831,\n              -0.24490225315093994,\n              0.10998662561178207\n            ],\n            [\n              0.09896539151668549,\n              -0.2154281884431839,\n              -0.04586761072278023\n            ],\n            [\n              0.12391256541013718,\n              -0.06876956671476364,\n              -0.17699216306209564\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.1929195076227188,\n        \"weights\": [\n          [\n            [\n              0.303184449672699,\n              -0.22255642712116241,\n              0.15304619073867798\n            ],\n            [\n              -0.3187333941459656,\n              -0.15920908749103546,\n              0.33759790658950806\n            ],\n            [\n              0.19879762828350067,\n              0.30086207389831543,\n              -0.14565040171146393\n            ]\n          ],\n          [\n            [\n              -0.24475222826004028,\n              -0.11444883793592453,\n              -0.03977220505475998\n            ],\n            [\n              -0.5766695141792297,\n              -0.26573851704597473,\n              0.2617243826389313\n            ],\n            [\n              -0.059220075607299805,\n              0.05921584367752075,\n              -0.23321840167045593\n            ]\n          ],\n          [\n            [\n              0.12728002667427063,\n              -0.22710849344730377,\n              0.34297189116477966\n            ],\n            [\n              -0.3018116354942322,\n              0.054087359458208084,\n              0.4344978928565979\n            ],\n            [\n              0.0469631627202034,\n              0.14949560165405273,\n              -0.1706211119890213\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.13662371039390564,\n        \"weights\": [\n          [\n            [\n              -0.2468211054801941,\n              -0.07405976951122284,\n              -0.056588608771562576\n            ],\n            [\n              -0.0507829450070858,\n              0.18406149744987488,\n              0.11345680058002472\n            ],\n            [\n              -0.28884875774383545,\n              0.1144554391503334,\n              -0.20539431273937225\n            ]\n          ],\n          [\n            [\n              0.1392928510904312,\n              0.2163618952035904,\n              -0.0069107115268707275\n            ],\n            [\n              -0.04500287026166916,\n              0.18057486414909363,\n              0.3434883654117584\n            ],\n            [\n              0.09264994412660599,\n              0.17778418958187103,\n              -0.20051360130310059\n            ]\n          ],\n          [\n            [\n              0.18443480134010315,\n              0.18599331378936768,\n              -0.2483920156955719\n            ],\n            [\n              0.17116613686084747,\n              0.015662359073758125,\n              0.24437673389911652\n            ],\n            [\n              0.08613235503435135,\n              -0.18919849395751953,\n              0.14660470187664032\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.01665310189127922,\n        \"weights\": [\n          [\n            [\n              0.0513838492333889,\n              -0.18428930640220642,\n              -0.13631688058376312\n            ],\n            [\n              -0.020188838243484497,\n              0.014654036611318588,\n              -0.2090645283460617\n            ],\n            [\n              0.19347725808620453,\n              0.008561060763895512,\n              0.12752579152584076\n            ]\n          ],\n          [\n            [\n              0.10648118704557419,\n              -0.09161978214979172,\n              0.15131479501724243\n            ],\n            [\n              -0.07589973509311676,\n              0.03778020665049553,\n              0.07684637606143951\n            ],\n            [\n              -0.21972328424453735,\n              -0.06632781028747559,\n              -0.21909500658512115\n            ]\n          ],\n          [\n            [\n              -0.11479348689317703,\n              -0.10508662462234497,\n              0.07523884624242783\n            ],\n            [\n              -0.14848960936069489,\n              -0.04849429056048393,\n              -0.14041589200496674\n            ],\n            [\n              -0.18590395152568817,\n              0.031492143869400024,\n              0.10233276337385178\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.10964040458202362,\n        \"weights\": [\n          [\n            [\n              -0.12156059592962265,\n              0.053360339254140854,\n              0.1554896980524063\n            ],\n            [\n              0.08920006453990936,\n              -0.12873634696006775,\n              -0.03641781583428383\n            ],\n            [\n              -0.26828017830848694,\n              0.07801597565412521,\n              0.004753198008984327\n            ]\n          ],\n          [\n            [\n              -0.17305849492549896,\n              -0.18035148084163666,\n              -0.05541511997580528\n            ],\n            [\n              0.06097719073295593,\n              -0.1959034502506256,\n              -0.18717913329601288\n            ],\n            [\n              -0.20423458516597748,\n              0.21905024349689484,\n              -0.11647790670394897\n            ]\n          ],\n          [\n            [\n              0.24856887757778168,\n              0.24108672142028809,\n              -0.180223748087883\n            ],\n            [\n              -0.10905743390321732,\n              -0.15688706934452057,\n              0.07556945085525513\n            ],\n            [\n              0.060350243002176285,\n              -0.07040304690599442,\n              0.21788306534290314\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.029825814068317413,\n        \"weights\": [\n          [\n            [\n              -0.19718821346759796,\n              -0.16889642179012299,\n              0.12187362462282181\n            ],\n            [\n              0.06039876490831375,\n              -0.14378444850444794,\n              0.07948035001754761\n            ],\n            [\n              -0.025484031066298485,\n              -0.16321809589862823,\n              -0.019062098115682602\n            ]\n          ],\n          [\n            [\n              -0.032011669129133224,\n              -0.14139215648174286,\n              0.11273358762264252\n            ],\n            [\n              -0.10067833960056305,\n              -0.20742711424827576,\n              0.13296671211719513\n            ],\n            [\n              0.1675073355436325,\n              -0.1571367383003235,\n              0.22434785962104797\n            ]\n          ],\n          [\n            [\n              -0.10166903585195541,\n              -0.19185051321983337,\n              0.08926276117563248\n            ],\n            [\n              0.09333138912916183,\n              0.037962570786476135,\n              0.0007648404571227729\n            ],\n            [\n              -0.05245523899793625,\n              -0.1298869550228119,\n              0.03659629821777344\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.11516544222831726,\n        \"weights\": [\n          [\n            [\n              -0.14063894748687744,\n              0.3289564251899719,\n              0.2985765039920807\n            ],\n            [\n              0.09437553584575653,\n              -0.1446211189031601,\n              -0.3673877418041229\n            ],\n            [\n              -0.2984912395477295,\n              -0.11701186001300812,\n              0.22971859574317932\n            ]\n          ],\n          [\n            [\n              0.09642521291971207,\n              0.413046658039093,\n              0.3844977021217346\n            ],\n            [\n              0.2882622480392456,\n              0.1406082808971405,\n              -0.2174244523048401\n            ],\n            [\n              -0.24284584820270538,\n              0.041832320392131805,\n              0.24873341619968414\n            ]\n          ],\n          [\n            [\n              -0.0859166830778122,\n              0.11321970075368881,\n              0.13290554285049438\n            ],\n            [\n              -0.09440730512142181,\n              -0.26801732182502747,\n              -0.4094286561012268\n            ],\n            [\n              -0.36698707938194275,\n              -0.018707290291786194,\n              0.08283202350139618\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.07734016329050064,\n        \"weights\": [\n          [\n            [\n              -0.03253738954663277,\n              -0.026013268157839775,\n              0.016034334897994995\n            ],\n            [\n              -0.22023527324199677,\n              0.1287398338317871,\n              -0.04254328832030296\n            ],\n            [\n              -0.008483637124300003,\n              -0.016512051224708557,\n              0.21947342157363892\n            ]\n          ],\n          [\n            [\n              -0.22642101347446442,\n              0.20579710602760315,\n              0.11521881073713303\n            ],\n            [\n              -0.10988859832286835,\n              0.052845388650894165,\n              0.13670620322227478\n            ],\n            [\n              -0.12536796927452087,\n              0.04135845974087715,\n              -0.1554696261882782\n            ]\n          ],\n          [\n            [\n              -0.18598666787147522,\n              0.12068557739257812,\n              0.12700338661670685\n            ],\n            [\n              0.1888495832681656,\n              -0.15277566015720367,\n              -0.147127166390419\n            ],\n            [\n              -0.1856405884027481,\n              0.07612188160419464,\n              -0.2162964791059494\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.03841400146484375,\n        \"weights\": [\n          [\n            [\n              -0.23960189521312714,\n              -0.16422033309936523,\n              0.09344270825386047\n            ],\n            [\n              -0.22473567724227905,\n              -0.025265153497457504,\n              -0.3128132224082947\n            ],\n            [\n              -0.05619398131966591,\n              0.14468322694301605,\n              -0.23834674060344696\n            ]\n          ],\n          [\n            [\n              0.15905526280403137,\n              0.39751049876213074,\n              0.40521639585494995\n            ],\n            [\n              0.07429175078868866,\n              0.2791271209716797,\n              0.15926817059516907\n            ],\n            [\n              0.3116576373577118,\n              0.47250816226005554,\n              0.27831581234931946\n            ]\n          ],\n          [\n            [\n              -0.14721138775348663,\n              0.11997095495462418,\n              -0.10465282946825027\n            ],\n            [\n              -0.10158450901508331,\n              -0.20062342286109924,\n              -0.3983880579471588\n            ],\n            [\n              -0.352329283952713,\n              -0.22438788414001465,\n              -0.14405152201652527\n            ]\n          ]\n        ]\n      }\n    ]\n  },\n  {\n    \"name\": \"relu_1_1\",\n    \"input_shape\": [\n      62,\n      62,\n      10\n    ],\n    \"output_shape\": [\n      62,\n      62,\n      10\n    ],\n    \"num_neurons\": 10\n  },\n  {\n    \"name\": \"conv_1_2\",\n    \"input_shape\": [\n      62,\n      62,\n      10\n    ],\n    \"output_shape\": [\n      60,\n      60,\n      10\n    ],\n    \"num_neurons\": 10,\n    \"weights\": [\n      {\n        \"bias\": -0.015000003390014172,\n        \"weights\": [\n          [\n            [\n              0.06984533369541168,\n              0.12250980734825134,\n              -0.06276966631412506\n            ],\n            [\n              0.027698174118995667,\n              0.28825634717941284,\n              0.11451877653598785\n            ],\n            [\n              0.1613343358039856,\n              -0.07120175659656525,\n              -0.02248406782746315\n            ]\n          ],\n          [\n            [\n              0.06695717573165894,\n              0.004763114266097546,\n              0.03319444879889488\n            ],\n            [\n              0.04770096018910408,\n              -0.07627807557582855,\n              -0.1811506152153015\n            ],\n            [\n              -0.09521480649709702,\n              -0.020181972533464432,\n              -0.14486107230186462\n            ]\n          ],\n          [\n            [\n              -0.061954572796821594,\n              -0.07911273837089539,\n              -0.3499084711074829\n            ],\n            [\n              -0.24773694574832916,\n              -0.21033312380313873,\n              -0.01210795622318983\n            ],\n            [\n              0.053867269307374954,\n              -0.07325703650712967,\n              -0.2212786078453064\n            ]\n          ],\n          [\n            [\n              -0.13988210260868073,\n              0.06434164941310883,\n              -0.09834592044353485\n            ],\n            [\n              -0.026962799951434135,\n              0.12251508235931396,\n              -0.08030775189399719\n            ],\n            [\n              -0.22049900889396667,\n              -0.03990422561764717,\n              -0.13152480125427246\n            ]\n          ],\n          [\n            [\n              -0.17654503881931305,\n              0.1668800264596939,\n              -0.138304203748703\n            ],\n            [\n              0.03783787786960602,\n              -0.18137794733047485,\n              -0.10964040458202362\n            ],\n            [\n              0.08009066432714462,\n              -0.028887981548905373,\n              -0.08581449836492538\n            ]\n          ],\n          [\n            [\n              0.12367407232522964,\n              -0.04227888956665993,\n              -0.12297787517309189\n            ],\n            [\n              -0.03683138266205788,\n              -0.07924527674913406,\n              0.07216089963912964\n            ],\n            [\n              0.09089690446853638,\n              -0.14476703107357025,\n              -0.07134923338890076\n            ]\n          ],\n          [\n            [\n              0.05118329077959061,\n              0.013351107016205788,\n              0.12198783457279205\n            ],\n            [\n              0.15751278400421143,\n              -0.048728566616773605,\n              -0.17966294288635254\n            ],\n            [\n              0.11106358468532562,\n              -0.05154714733362198,\n              0.15185466408729553\n            ]\n          ],\n          [\n            [\n              -0.002612997544929385,\n              -0.05618676543235779,\n              0.04629763215780258\n            ],\n            [\n              0.20909954607486725,\n              0.2826078534126282,\n              0.06790830194950104\n            ],\n            [\n              -0.24769099056720734,\n              -0.018110016360878944,\n              0.03419424593448639\n            ]\n          ],\n          [\n            [\n              -0.20570960640907288,\n              0.16505253314971924,\n              0.010972544550895691\n            ],\n            [\n              -0.07593987882137299,\n              0.18975234031677246,\n              0.12766748666763306\n            ],\n            [\n              -0.16967566311359406,\n              -0.14139984548091888,\n              -0.06672513484954834\n            ]\n          ],\n          [\n            [\n              -0.09351741522550583,\n              -0.05367553234100342,\n              0.006337949074804783\n            ],\n            [\n              0.09447716921567917,\n              0.05620996654033661,\n              -0.1393093317747116\n            ],\n            [\n              0.09124802052974701,\n              -0.008008070290088654,\n              -0.08058559894561768\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.03633357957005501,\n        \"weights\": [\n          [\n            [\n              -0.028699593618512154,\n              0.01408400945365429,\n              0.053278084844350815\n            ],\n            [\n              0.17656444013118744,\n              0.020243415609002113,\n              -0.27064037322998047\n            ],\n            [\n              -0.03375490754842758,\n              -0.07598341256380081,\n              -0.054536327719688416\n            ]\n          ],\n          [\n            [\n              -0.016911577433347702,\n              0.011905811727046967,\n              -0.004597058519721031\n            ],\n            [\n              -0.15162792801856995,\n              -0.040205202996730804,\n              0.029262922704219818\n            ],\n            [\n              0.12659160792827606,\n              0.1467726081609726,\n              0.13518129289150238\n            ]\n          ],\n          [\n            [\n              0.08571384847164154,\n              0.04543592408299446,\n              0.1619890332221985\n            ],\n            [\n              0.04412376508116722,\n              -0.20871470868587494,\n              0.13537569344043732\n            ],\n            [\n              -0.11269881576299667,\n              0.316618412733078,\n              -0.1193186566233635\n            ]\n          ],\n          [\n            [\n              0.1737903505563736,\n              -0.04851733520627022,\n              0.053338903933763504\n            ],\n            [\n              0.2689688801765442,\n              0.18783660233020782,\n              0.0049596684984862804\n            ],\n            [\n              0.08433520793914795,\n              0.2502553164958954,\n              -0.10368360579013824\n            ]\n          ],\n          [\n            [\n              0.03098849020898342,\n              -0.16898685693740845,\n              -0.14307470619678497\n            ],\n            [\n              -0.04081837087869644,\n              0.1253221333026886,\n              -0.175978884100914\n            ],\n            [\n              -0.040029484778642654,\n              -0.028231589123606682,\n              0.1737002283334732\n            ]\n          ],\n          [\n            [\n              0.007191381882876158,\n              0.10486532747745514,\n              0.09948095679283142\n            ],\n            [\n              0.08876245468854904,\n              0.04661615192890167,\n              0.055014386773109436\n            ],\n            [\n              0.11458297818899155,\n              0.14941437542438507,\n              0.14682719111442566\n            ]\n          ],\n          [\n            [\n              -0.029932519420981407,\n              0.06707341223955154,\n              0.0011027005966752768\n            ],\n            [\n              0.013172822073101997,\n              -0.027043195441365242,\n              -0.07705768197774887\n            ],\n            [\n              -0.1878139227628708,\n              -0.10642603039741516,\n              0.15161097049713135\n            ]\n          ],\n          [\n            [\n              -0.24196398258209229,\n              0.2093714028596878,\n              0.016427693888545036\n            ],\n            [\n              0.18196193873882294,\n              0.15705186128616333,\n              -0.16816706955432892\n            ],\n            [\n              0.2550819218158722,\n              -0.012771722860634327,\n              -0.03235609456896782\n            ]\n          ],\n          [\n            [\n              0.05405253916978836,\n              0.04333319142460823,\n              0.18497972190380096\n            ],\n            [\n              0.040979642421007156,\n              0.04391923174262047,\n              0.003401109715923667\n            ],\n            [\n              0.06960181146860123,\n              -0.16919413208961487,\n              -0.01900867559015751\n            ]\n          ],\n          [\n            [\n              -0.12346938252449036,\n              -0.0845215693116188,\n              0.03198058158159256\n            ],\n            [\n              -0.09701372683048248,\n              -0.31881439685821533,\n              0.05198657885193825\n            ],\n            [\n              0.0014515378279611468,\n              -0.0015912456437945366,\n              -0.13556411862373352\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.26450011134147644,\n        \"weights\": [\n          [\n            [\n              0.11858198046684265,\n              0.08620305359363556,\n              -0.22812843322753906\n            ],\n            [\n              0.0737232193350792,\n              -0.1764031946659088,\n              -0.10877794027328491\n            ],\n            [\n              -0.04786592349410057,\n              0.06122968718409538,\n              -0.22324822843074799\n            ]\n          ],\n          [\n            [\n              0.0688820481300354,\n              -0.12272901833057404,\n              -0.11515816301107407\n            ],\n            [\n              -0.1755588799715042,\n              -0.18296514451503754,\n              0.005947907920926809\n            ],\n            [\n              0.05370483547449112,\n              -0.17687486112117767,\n              -0.07346649467945099\n            ]\n          ],\n          [\n            [\n              -0.28695711493492126,\n              -0.14942891895771027,\n              0.22552116215229034\n            ],\n            [\n              0.14913678169250488,\n              0.25072580575942993,\n              -0.13216601312160492\n            ],\n            [\n              0.25203147530555725,\n              0.013846390880644321,\n              0.07630975544452667\n            ]\n          ],\n          [\n            [\n              0.14870744943618774,\n              0.26395735144615173,\n              0.07945573329925537\n            ],\n            [\n              0.02941100113093853,\n              -0.06789202243089676,\n              -0.09479090571403503\n            ],\n            [\n              -0.13743452727794647,\n              0.0012073514517396688,\n              -0.19661739468574524\n            ]\n          ],\n          [\n            [\n              -0.01009973231703043,\n              -0.1716722846031189,\n              0.1032852828502655\n            ],\n            [\n              -0.005336238536983728,\n              -0.015667693689465523,\n              0.01772109791636467\n            ],\n            [\n              0.15534654259681702,\n              0.030132949352264404,\n              0.059307970106601715\n            ]\n          ],\n          [\n            [\n              -0.17051032185554504,\n              -0.03172222524881363,\n              0.017660612240433693\n            ],\n            [\n              0.06734868139028549,\n              -0.16958904266357422,\n              0.0980442464351654\n            ],\n            [\n              0.08474505692720413,\n              -0.16626432538032532,\n              -0.14391560852527618\n            ]\n          ],\n          [\n            [\n              -0.047566138207912445,\n              -0.12840814888477325,\n              0.07308086007833481\n            ],\n            [\n              -0.10277002304792404,\n              0.03899145871400833,\n              -0.08504311740398407\n            ],\n            [\n              0.08088894188404083,\n              0.07359552383422852,\n              -0.1757054328918457\n            ]\n          ],\n          [\n            [\n              0.2511260211467743,\n              0.16259819269180298,\n              -0.2731759548187256\n            ],\n            [\n              -0.15424731373786926,\n              -0.2542499899864197,\n              0.1603444516658783\n            ],\n            [\n              -0.03646847605705261,\n              -0.02279706671833992,\n              -0.0198900755494833\n            ]\n          ],\n          [\n            [\n              0.1734747290611267,\n              0.03886084258556366,\n              0.013219695538282394\n            ],\n            [\n              -0.04848269373178482,\n              0.12644779682159424,\n              -0.13532158732414246\n            ],\n            [\n              0.17683625221252441,\n              0.13510529696941376,\n              0.06751107424497604\n            ]\n          ],\n          [\n            [\n              -0.1525471806526184,\n              -0.1715271919965744,\n              0.09087955206632614\n            ],\n            [\n              -0.030184892937541008,\n              0.008808514103293419,\n              -0.14952726662158966\n            ],\n            [\n              -0.12190154939889908,\n              -0.08360952883958817,\n              -0.27969890832901\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.16449642181396484,\n        \"weights\": [\n          [\n            [\n              0.01370085310190916,\n              -0.003117413492873311,\n              0.018573859706521034\n            ],\n            [\n              0.02700856886804104,\n              0.0726592019200325,\n              -0.16934508085250854\n            ],\n            [\n              -0.013026679866015911,\n              -0.06702490895986557,\n              -0.02738550864160061\n            ]\n          ],\n          [\n            [\n              0.21202805638313293,\n              0.1058105081319809,\n              -0.07208364456892014\n            ],\n            [\n              -0.06420744210481644,\n              0.011604615487158298,\n              0.027805227786302567\n            ],\n            [\n              0.00818086601793766,\n              0.13248521089553833,\n              0.1713949739933014\n            ]\n          ],\n          [\n            [\n              0.15266969799995422,\n              0.07938865572214127,\n              0.1686062216758728\n            ],\n            [\n              0.21102824807167053,\n              0.20970553159713745,\n              0.04996765777468681\n            ],\n            [\n              0.25311100482940674,\n              0.120435930788517,\n              0.0902794823050499\n            ]\n          ],\n          [\n            [\n              0.17864742875099182,\n              0.14377900958061218,\n              -0.08969362825155258\n            ],\n            [\n              0.0035156651865690947,\n              -0.1548624336719513,\n              -0.038364484906196594\n            ],\n            [\n              0.04785368591547012,\n              0.010373460128903389,\n              -0.048020996153354645\n            ]\n          ],\n          [\n            [\n              -0.014619680121541023,\n              0.15819314122200012,\n              -0.14283199608325958\n            ],\n            [\n              -0.1338491141796112,\n              -0.027468981221318245,\n              -0.09391266107559204\n            ],\n            [\n              -0.11103012412786484,\n              -0.16180108487606049,\n              -0.02236912027001381\n            ]\n          ],\n          [\n            [\n              0.12564800679683685,\n              -0.10915327072143555,\n              -0.053323157131671906\n            ],\n            [\n              0.06013397499918938,\n              0.07818517088890076,\n              0.04390117898583412\n            ],\n            [\n              0.05919521674513817,\n              -0.05086110159754753,\n              0.11842437833547592\n            ]\n          ],\n          [\n            [\n              0.009561538696289062,\n              0.13100337982177734,\n              -0.0885411724448204\n            ],\n            [\n              0.0943290963768959,\n              0.007461306173354387,\n              -0.08001946657896042\n            ],\n            [\n              0.12241971492767334,\n              0.1626623272895813,\n              -0.17123454809188843\n            ]\n          ],\n          [\n            [\n              0.006828694604337215,\n              6.963577470742166e-05,\n              -0.17743292450904846\n            ],\n            [\n              -0.18081223964691162,\n              0.020155085250735283,\n              -0.12944959104061127\n            ],\n            [\n              -0.10669642686843872,\n              -0.1092572808265686,\n              -0.01022006943821907\n            ]\n          ],\n          [\n            [\n              0.08811519294977188,\n              -0.010638792999088764,\n              0.037426482886075974\n            ],\n            [\n              0.029551351442933083,\n              0.10919863730669022,\n              -0.17812275886535645\n            ],\n            [\n              -0.01921534352004528,\n              -0.020105307921767235,\n              -0.017948994413018227\n            ]\n          ],\n          [\n            [\n              -0.13676193356513977,\n              -0.06529264152050018,\n              -0.03060491383075714\n            ],\n            [\n              -0.16058501601219177,\n              0.22819195687770844,\n              0.17382000386714935\n            ],\n            [\n              0.02250857651233673,\n              0.1810695230960846,\n              0.21420879662036896\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.3483165502548218,\n        \"weights\": [\n          [\n            [\n              0.032918818295001984,\n              0.16698546707630157,\n              0.18808385729789734\n            ],\n            [\n              -0.18842415511608124,\n              -0.19741851091384888,\n              -0.3181258738040924\n            ],\n            [\n              0.14529986679553986,\n              -0.0633912906050682,\n              -0.1235964298248291\n            ]\n          ],\n          [\n            [\n              -0.17358721792697906,\n              -0.14975450932979584,\n              -0.12765149772167206\n            ],\n            [\n              -0.1116621121764183,\n              0.061584655195474625,\n              -0.0584794245660305\n            ],\n            [\n              -0.0361844040453434,\n              -0.0742720440030098,\n              -0.08337784558534622\n            ]\n          ],\n          [\n            [\n              -0.16112861037254333,\n              -0.20642298460006714,\n              0.18931011855602264\n            ],\n            [\n              -0.12355746328830719,\n              -0.1905362457036972,\n              0.071263886988163\n            ],\n            [\n              0.05011044070124626,\n              0.07991564273834229,\n              0.07828167080879211\n            ]\n          ],\n          [\n            [\n              -0.06476299464702606,\n              0.05114796757698059,\n              -0.1346709281206131\n            ],\n            [\n              0.11902449280023575,\n              -0.16854026913642883,\n              0.12135276943445206\n            ],\n            [\n              -0.19429254531860352,\n              0.16592955589294434,\n              0.11022176593542099\n            ]\n          ],\n          [\n            [\n              0.08290845155715942,\n              -0.08158741891384125,\n              0.1433313637971878\n            ],\n            [\n              3.642140654847026e-05,\n              -0.07293203473091125,\n              0.07498873025178909\n            ],\n            [\n              0.14000684022903442,\n              -0.16949677467346191,\n              -0.12858793139457703\n            ]\n          ],\n          [\n            [\n              0.14515793323516846,\n              -0.11156363785266876,\n              0.18032991886138916\n            ],\n            [\n              0.0346059650182724,\n              0.022723345085978508,\n              0.1322818100452423\n            ],\n            [\n              0.14743609726428986,\n              -0.030711062252521515,\n              0.03786199167370796\n            ]\n          ],\n          [\n            [\n              0.10720982402563095,\n              -0.11306586116552353,\n              0.0857093334197998\n            ],\n            [\n              -0.11355438083410263,\n              -0.14333486557006836,\n              0.03453052043914795\n            ],\n            [\n              -0.004926359746605158,\n              0.039875756949186325,\n              -0.11913741379976273\n            ]\n          ],\n          [\n            [\n              0.294634073972702,\n              0.2950073778629303,\n              0.21420356631278992\n            ],\n            [\n              -0.21549411118030548,\n              -0.017663640901446342,\n              -0.14800578355789185\n            ],\n            [\n              0.1540672779083252,\n              0.13856224715709686,\n              -0.07062943279743195\n            ]\n          ],\n          [\n            [\n              0.09850916266441345,\n              -0.024218950420618057,\n              -0.05085296928882599\n            ],\n            [\n              -0.1842269003391266,\n              0.048921141773462296,\n              -0.15639637410640717\n            ],\n            [\n              0.15068410336971283,\n              0.1781669557094574,\n              0.13751555979251862\n            ]\n          ],\n          [\n            [\n              -0.3126057982444763,\n              -0.3406130373477936,\n              -0.03664514049887657\n            ],\n            [\n              -0.19378718733787537,\n              -0.2823072373867035,\n              -0.24569031596183777\n            ],\n            [\n              -0.011888631619513035,\n              -0.2881462574005127,\n              -0.16774912178516388\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.04205586388707161,\n        \"weights\": [\n          [\n            [\n              -0.050872303545475006,\n              -0.05784498527646065,\n              0.08709856122732162\n            ],\n            [\n              -0.02384408563375473,\n              -0.09397155791521072,\n              0.03822978958487511\n            ],\n            [\n              0.07812274247407913,\n              -0.041850049048662186,\n              0.0914970338344574\n            ]\n          ],\n          [\n            [\n              0.20612703263759613,\n              0.10431799292564392,\n              0.09797626733779907\n            ],\n            [\n              -0.08422691375017166,\n              -0.12570352852344513,\n              0.03475036844611168\n            ],\n            [\n              0.10499346256256104,\n              0.17909479141235352,\n              -0.07103309035301208\n            ]\n          ],\n          [\n            [\n              -0.0803171768784523,\n              0.14055944979190826,\n              -0.0013921728823333979\n            ],\n            [\n              -0.05157731845974922,\n              0.14367206394672394,\n              0.16281574964523315\n            ],\n            [\n              0.1779191792011261,\n              0.17450547218322754,\n              -0.02203560434281826\n            ]\n          ],\n          [\n            [\n              -0.17883086204528809,\n              -0.04414394870400429,\n              -0.06294535100460052\n            ],\n            [\n              0.0421249195933342,\n              -0.15779680013656616,\n              0.08037769794464111\n            ],\n            [\n              -0.2165309339761734,\n              0.14304062724113464,\n              -0.13399317860603333\n            ]\n          ],\n          [\n            [\n              0.15507706999778748,\n              0.132803812623024,\n              0.011086942628026009\n            ],\n            [\n              0.17226529121398926,\n              0.12231457978487015,\n              0.011460813693702221\n            ],\n            [\n              -0.05953741818666458,\n              0.009429561905562878,\n              0.17142777144908905\n            ]\n          ],\n          [\n            [\n              0.06041353940963745,\n              0.05031156539916992,\n              0.12597262859344482\n            ],\n            [\n              0.10787922143936157,\n              -0.04708215966820717,\n              0.046055953949689865\n            ],\n            [\n              -0.1617310792207718,\n              0.16448339819908142,\n              0.10370638221502304\n            ]\n          ],\n          [\n            [\n              -0.07778611779212952,\n              -0.07144667208194733,\n              0.021185168996453285\n            ],\n            [\n              -0.014000819995999336,\n              -0.1030643954873085,\n              0.0849083736538887\n            ],\n            [\n              -0.14547759294509888,\n              -0.13143889605998993,\n              -0.14898383617401123\n            ]\n          ],\n          [\n            [\n              -0.10427332669496536,\n              0.013692626729607582,\n              -0.041272081434726715\n            ],\n            [\n              0.04340681806206703,\n              0.011249948292970657,\n              -0.06556360423564911\n            ],\n            [\n              0.1761879026889801,\n              -0.22885175049304962,\n              0.22462105751037598\n            ]\n          ],\n          [\n            [\n              -0.09201741963624954,\n              -0.12168310582637787,\n              0.02565200999379158\n            ],\n            [\n              0.004468440543860197,\n              -0.10384641587734222,\n              0.08242103457450867\n            ],\n            [\n              -0.10442862659692764,\n              0.1434611976146698,\n              -0.0901908352971077\n            ]\n          ],\n          [\n            [\n              0.13533282279968262,\n              0.22042447328567505,\n              -0.0668957456946373\n            ],\n            [\n              -0.07557129114866257,\n              0.05515684932470322,\n              0.23244601488113403\n            ],\n            [\n              0.19463591277599335,\n              0.07740461826324463,\n              0.13794946670532227\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.014336160384118557,\n        \"weights\": [\n          [\n            [\n              0.0215923935174942,\n              -0.16690650582313538,\n              0.015022673644125462\n            ],\n            [\n              0.2356853038072586,\n              -0.16496746242046356,\n              0.13733810186386108\n            ],\n            [\n              -0.013798801228404045,\n              0.12202651798725128,\n              -0.09092853218317032\n            ]\n          ],\n          [\n            [\n              0.1390375792980194,\n              0.054428163915872574,\n              -0.16656725108623505\n            ],\n            [\n              -0.09568659216165543,\n              0.07450474798679352,\n              0.10316252708435059\n            ],\n            [\n              -0.05642378702759743,\n              0.11634775251150131,\n              0.040672823786735535\n            ]\n          ],\n          [\n            [\n              -0.09930086135864258,\n              0.14880722761154175,\n              -0.022999756038188934\n            ],\n            [\n              0.07259044796228409,\n              -0.20595431327819824,\n              0.10381084680557251\n            ],\n            [\n              -0.0962037593126297,\n              0.057415831834077835,\n              0.11492513865232468\n            ]\n          ],\n          [\n            [\n              -0.07774700969457626,\n              0.17856010794639587,\n              -0.060527559369802475\n            ],\n            [\n              0.025360064581036568,\n              0.03321215882897377,\n              0.024507902562618256\n            ],\n            [\n              -0.10944100469350815,\n              -0.02191023901104927,\n              -0.15557676553726196\n            ]\n          ],\n          [\n            [\n              -0.11318005621433258,\n              -0.10387733578681946,\n              0.016798585653305054\n            ],\n            [\n              -0.1566755175590515,\n              -0.05321263521909714,\n              -0.058401837944984436\n            ],\n            [\n              0.019487643614411354,\n              0.12416136264801025,\n              0.16156084835529327\n            ]\n          ],\n          [\n            [\n              0.17321249842643738,\n              0.15694253146648407,\n              -0.1380874663591385\n            ],\n            [\n              0.08091419190168381,\n              -0.026859184727072716,\n              0.055830758064985275\n            ],\n            [\n              -0.08057769387960434,\n              -0.03078553080558777,\n              -0.14363344013690948\n            ]\n          ],\n          [\n            [\n              0.051236167550086975,\n              0.167289599776268,\n              0.02166205458343029\n            ],\n            [\n              0.0026116727385669947,\n              0.14677225053310394,\n              -0.13095352053642273\n            ],\n            [\n              -0.005355099681764841,\n              -0.14963918924331665,\n              0.16254675388336182\n            ]\n          ],\n          [\n            [\n              0.09966745227575302,\n              0.1953197717666626,\n              0.1834649294614792\n            ],\n            [\n              -0.04643874615430832,\n              0.003983447328209877,\n              -0.1375250369310379\n            ],\n            [\n              0.13853110373020172,\n              0.02780631184577942,\n              -0.021691713482141495\n            ]\n          ],\n          [\n            [\n              -0.17471736669540405,\n              -0.003995680715888739,\n              0.0908963605761528\n            ],\n            [\n              0.05729997158050537,\n              -0.03710588812828064,\n              0.14208091795444489\n            ],\n            [\n              0.017629601061344147,\n              0.14826329052448273,\n              -0.04206182062625885\n            ]\n          ],\n          [\n            [\n              0.04621823504567146,\n              0.19993871450424194,\n              0.12714999914169312\n            ],\n            [\n              0.2284945249557495,\n              0.06086286902427673,\n              -0.09190674126148224\n            ],\n            [\n              -0.014598064124584198,\n              0.09851402789354324,\n              -0.038618091493844986\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.10236959904432297,\n        \"weights\": [\n          [\n            [\n              0.13946866989135742,\n              0.09682520478963852,\n              0.1726500242948532\n            ],\n            [\n              0.1549411416053772,\n              0.20717331767082214,\n              0.15288777649402618\n            ],\n            [\n              0.2388300597667694,\n              0.20189401507377625,\n              0.2861592471599579\n            ]\n          ],\n          [\n            [\n              0.1471264809370041,\n              -0.15974710881710052,\n              -0.15733277797698975\n            ],\n            [\n              0.12168208509683609,\n              0.046046722680330276,\n              0.06857374310493469\n            ],\n            [\n              0.09625067561864853,\n              0.02841225638985634,\n              -0.18588554859161377\n            ]\n          ],\n          [\n            [\n              0.21173368394374847,\n              -0.1557256281375885,\n              0.19390174746513367\n            ],\n            [\n              0.09824199974536896,\n              -0.03420214727520943,\n              -0.008039628155529499\n            ],\n            [\n              -0.07244376838207245,\n              0.12665802240371704,\n              -0.14270643889904022\n            ]\n          ],\n          [\n            [\n              -0.10829263925552368,\n              -0.21082808077335358,\n              -0.018086524680256844\n            ],\n            [\n              -0.18155768513679504,\n              -0.11288563162088394,\n              -0.27771344780921936\n            ],\n            [\n              -0.07960627973079681,\n              -0.23326782882213593,\n              0.029522554948925972\n            ]\n          ],\n          [\n            [\n              0.1645551472902298,\n              0.07067253440618515,\n              -0.15274055302143097\n            ],\n            [\n              0.09972383826971054,\n              0.05966971442103386,\n              0.05424782633781433\n            ],\n            [\n              0.09747873246669769,\n              0.17227253317832947,\n              0.07710618525743484\n            ]\n          ],\n          [\n            [\n              -0.142201766371727,\n              0.06311608850955963,\n              -0.09166456758975983\n            ],\n            [\n              0.036629579961299896,\n              -0.16250286996364594,\n              -0.103266641497612\n            ],\n            [\n              0.14275093376636505,\n              -0.09976468980312347,\n              -0.11892399936914444\n            ]\n          ],\n          [\n            [\n              -0.062094107270240784,\n              -0.07857262343168259,\n              -0.06865912675857544\n            ],\n            [\n              0.14247825741767883,\n              0.017749130725860596,\n              -0.17575152218341827\n            ],\n            [\n              0.034957896918058395,\n              -0.18680624663829803,\n              -0.07339288294315338\n            ]\n          ],\n          [\n            [\n              0.15000995993614197,\n              0.15952040255069733,\n              0.025392794981598854\n            ],\n            [\n              0.008648335002362728,\n              0.05714226886630058,\n              0.10262136161327362\n            ],\n            [\n              -0.02552706189453602,\n              0.24016767740249634,\n              -0.07566685974597931\n            ]\n          ],\n          [\n            [\n              -0.0037647292483597994,\n              -0.07180869579315186,\n              -0.10357300192117691\n            ],\n            [\n              -0.13562867045402527,\n              -0.18133904039859772,\n              0.10665327310562134\n            ],\n            [\n              -0.1242426410317421,\n              -0.08196112513542175,\n              0.015137712471187115\n            ]\n          ],\n          [\n            [\n              -0.08718607574701309,\n              -0.11071056127548218,\n              -0.19932056963443756\n            ],\n            [\n              0.12107475847005844,\n              0.031282905489206314,\n              -0.008140921592712402\n            ],\n            [\n              -0.10637729614973068,\n              0.05876592919230461,\n              -0.14555838704109192\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.09469860047101974,\n        \"weights\": [\n          [\n            [\n              -0.2105904221534729,\n              0.0822480246424675,\n              0.1040528193116188\n            ],\n            [\n              -0.4372435510158539,\n              -0.1394747942686081,\n              -0.13298483192920685\n            ],\n            [\n              0.07332262396812439,\n              0.3908269703388214,\n              0.38406169414520264\n            ]\n          ],\n          [\n            [\n              -0.045297324657440186,\n              0.09923484921455383,\n              0.009942020289599895\n            ],\n            [\n              -0.10473351180553436,\n              0.1444244235754013,\n              0.036649953573942184\n            ],\n            [\n              0.05448807030916214,\n              -0.1387009620666504,\n              -0.09983030706644058\n            ]\n          ],\n          [\n            [\n              -0.16015511751174927,\n              0.11032894253730774,\n              0.07853087782859802\n            ],\n            [\n              -0.3843325972557068,\n              -0.12732912600040436,\n              -0.13995136320590973\n            ],\n            [\n              -0.12522496283054352,\n              0.1591143161058426,\n              -0.1607891321182251\n            ]\n          ],\n          [\n            [\n              -0.18878810107707977,\n              -0.02027733065187931,\n              -0.17432159185409546\n            ],\n            [\n              0.09949029237031937,\n              0.1345290243625641,\n              0.33093181252479553\n            ],\n            [\n              -0.0773019939661026,\n              -0.12286946922540665,\n              -0.07437783479690552\n            ]\n          ],\n          [\n            [\n              -0.05076870322227478,\n              -0.17143796384334564,\n              -0.02844293601810932\n            ],\n            [\n              -0.056532178074121475,\n              -0.05581158027052879,\n              0.13571591675281525\n            ],\n            [\n              -0.0011971743078902364,\n              0.1389237940311432,\n              0.045508045703172684\n            ]\n          ],\n          [\n            [\n              -0.14648418128490448,\n              0.008594298735260963,\n              0.06316842883825302\n            ],\n            [\n              -0.1327832192182541,\n              0.05533916503190994,\n              0.0405786857008934\n            ],\n            [\n              0.016748623922467232,\n              -0.0036282374057918787,\n              -0.06971527636051178\n            ]\n          ],\n          [\n            [\n              -0.06318449974060059,\n              -0.04867660999298096,\n              0.0033905047457665205\n            ],\n            [\n              -0.14766040444374084,\n              0.07115902006626129,\n              -0.1082526445388794\n            ],\n            [\n              0.18094147741794586,\n              0.075848788022995,\n              0.010570191778242588\n            ]\n          ],\n          [\n            [\n              -0.4461718201637268,\n              -0.12685231864452362,\n              -0.09470995515584946\n            ],\n            [\n              -0.37245672941207886,\n              0.06401273608207703,\n              -0.10587650537490845\n            ],\n            [\n              0.1323092132806778,\n              0.2841692864894867,\n              0.3288729190826416\n            ]\n          ],\n          [\n            [\n              0.11332128196954727,\n              0.07668281346559525,\n              0.17105501890182495\n            ],\n            [\n              0.07676290720701218,\n              0.18461892008781433,\n              -0.030073756352066994\n            ],\n            [\n              -0.058217138051986694,\n              0.06935951113700867,\n              -0.10205817967653275\n            ]\n          ],\n          [\n            [\n              0.0596625953912735,\n              0.11606793105602264,\n              -0.03409811481833458\n            ],\n            [\n              -0.15589463710784912,\n              -0.03166348114609718,\n              -0.09652978181838989\n            ],\n            [\n              -0.03323217108845711,\n              0.14403294026851654,\n              0.047793734818696976\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.01194368302822113,\n        \"weights\": [\n          [\n            [\n              -0.018009334802627563,\n              0.03910083323717117,\n              -0.12360009551048279\n            ],\n            [\n              -0.16260863840579987,\n              0.06422073394060135,\n              0.1096610575914383\n            ],\n            [\n              -0.09977766126394272,\n              0.08435740321874619,\n              -0.15145544707775116\n            ]\n          ],\n          [\n            [\n              -0.15748460590839386,\n              -0.02257942594587803,\n              -0.12646594643592834\n            ],\n            [\n              -0.17101167142391205,\n              -0.10232622921466827,\n              0.060039620846509933\n            ],\n            [\n              -0.037509892135858536,\n              0.13630206882953644,\n              0.13288167119026184\n            ]\n          ],\n          [\n            [\n              0.17928721010684967,\n              0.06221248582005501,\n              -0.1642381101846695\n            ],\n            [\n              0.15033742785453796,\n              -0.12057711184024811,\n              -0.1177884042263031\n            ],\n            [\n              -0.029275977984070778,\n              0.13740703463554382,\n              0.0059226457960903645\n            ]\n          ],\n          [\n            [\n              -0.08302946388721466,\n              -0.09800400584936142,\n              -0.10539329797029495\n            ],\n            [\n              -0.10979030281305313,\n              0.12786464393138885,\n              0.15422867238521576\n            ],\n            [\n              0.1178753450512886,\n              0.037778858095407486,\n              -0.13282115757465363\n            ]\n          ],\n          [\n            [\n              0.049398407340049744,\n              -0.09233351051807404,\n              0.020540276542305946\n            ],\n            [\n              -0.07510533183813095,\n              -0.0051919217221438885,\n              0.03071579709649086\n            ],\n            [\n              0.06637870520353317,\n              0.14142747223377228,\n              0.0987064316868782\n            ]\n          ],\n          [\n            [\n              -0.06529685854911804,\n              0.15087558329105377,\n              0.1440766304731369\n            ],\n            [\n              -0.09470537304878235,\n              0.0037186474073678255,\n              0.06637263298034668\n            ],\n            [\n              -0.12547937035560608,\n              0.16972284018993378,\n              0.02478511817753315\n            ]\n          ],\n          [\n            [\n              0.1591171771287918,\n              0.13206999003887177,\n              -0.128485769033432\n            ],\n            [\n              -0.06297139078378677,\n              0.07761325687170029,\n              0.1717512309551239\n            ],\n            [\n              0.006974450778216124,\n              0.10103538632392883,\n              0.15904146432876587\n            ]\n          ],\n          [\n            [\n              -0.0991336777806282,\n              0.10918153822422028,\n              -0.1413096934556961\n            ],\n            [\n              0.12189608812332153,\n              -0.030078722164034843,\n              0.16664233803749084\n            ],\n            [\n              -0.03848503902554512,\n              -0.044872891157865524,\n              -0.05667019262909889\n            ]\n          ],\n          [\n            [\n              -0.05376162379980087,\n              0.12212453037500381,\n              0.001853001187555492\n            ],\n            [\n              0.007966096512973309,\n              -0.052032239735126495,\n              0.08911513537168503\n            ],\n            [\n              0.10235605388879776,\n              -0.1754452884197235,\n              0.11022020131349564\n            ]\n          ],\n          [\n            [\n              0.0523245595395565,\n              -0.08961309492588043,\n              0.028888678178191185\n            ],\n            [\n              -0.182817280292511,\n              -0.14101825654506683,\n              0.07048813253641129\n            ],\n            [\n              -0.08123685419559479,\n              0.020566679537296295,\n              0.1554270088672638\n            ]\n          ]\n        ]\n      }\n    ]\n  },\n  {\n    \"name\": \"relu_1_2\",\n    \"input_shape\": [\n      60,\n      60,\n      10\n    ],\n    \"output_shape\": [\n      60,\n      60,\n      10\n    ],\n    \"num_neurons\": 10\n  },\n  {\n    \"name\": \"max_pool_1\",\n    \"input_shape\": [\n      60,\n      60,\n      10\n    ],\n    \"output_shape\": [\n      30,\n      30,\n      10\n    ],\n    \"num_neurons\": 10\n  },\n  {\n    \"name\": \"conv_2_1\",\n    \"input_shape\": [\n      30,\n      30,\n      10\n    ],\n    \"output_shape\": [\n      28,\n      28,\n      10\n    ],\n    \"num_neurons\": 10,\n    \"weights\": [\n      {\n        \"bias\": 0.23078683018684387,\n        \"weights\": [\n          [\n            [\n              0.012396186590194702,\n              0.106514573097229,\n              -0.059165868908166885\n            ],\n            [\n              0.0473598875105381,\n              -0.06922586262226105,\n              -0.010398129001259804\n            ],\n            [\n              -0.10683242231607437,\n              0.16645176708698273,\n              -0.027350030839443207\n            ]\n          ],\n          [\n            [\n              0.10285908728837967,\n              0.01539174560457468,\n              0.2610231935977936\n            ],\n            [\n              0.09296361356973648,\n              0.18699924647808075,\n              0.16006100177764893\n            ],\n            [\n              0.23022422194480896,\n              0.10022423416376114,\n              0.12351842224597931\n            ]\n          ],\n          [\n            [\n              -0.11672525107860565,\n              0.04385579749941826,\n              -0.1581880748271942\n            ],\n            [\n              0.06049090251326561,\n              -0.09956669807434082,\n              -0.054375164210796356\n            ],\n            [\n              0.02838168479502201,\n              0.19887058436870575,\n              0.07822155952453613\n            ]\n          ],\n          [\n            [\n              -0.011373915709555149,\n              0.23057536780834198,\n              -0.05637764558196068\n            ],\n            [\n              0.12049947679042816,\n              0.23012472689151764,\n              -0.07205697149038315\n            ],\n            [\n              0.13936983048915863,\n              0.16423775255680084,\n              0.1870001256465912\n            ]\n          ],\n          [\n            [\n              0.25594502687454224,\n              0.2258918136358261,\n              -0.029594261199235916\n            ],\n            [\n              0.21196569502353668,\n              0.015812525525689125,\n              0.0857214406132698\n            ],\n            [\n              0.08068202435970306,\n              -0.11682596802711487,\n              -0.14986300468444824\n            ]\n          ],\n          [\n            [\n              -0.03351468965411186,\n              -0.027632812038064003,\n              0.2219613939523697\n            ],\n            [\n              0.23095473647117615,\n              0.2543334364891052,\n              0.11295383423566818\n            ],\n            [\n              0.2238248735666275,\n              0.14109322428703308,\n              0.28218019008636475\n            ]\n          ],\n          [\n            [\n              -0.0726231187582016,\n              0.15335825085639954,\n              -0.1863558143377304\n            ],\n            [\n              -0.04176076874136925,\n              -0.07777108252048492,\n              -0.13225321471691132\n            ],\n            [\n              0.11718687415122986,\n              0.2275283932685852,\n              -0.09361421316862106\n            ]\n          ],\n          [\n            [\n              -0.2195308804512024,\n              -0.06082876771688461,\n              0.03180938959121704\n            ],\n            [\n              -0.04475773125886917,\n              -0.1764926314353943,\n              -0.19074630737304688\n            ],\n            [\n              0.02116229757666588,\n              -0.10730506479740143,\n              -0.12850704789161682\n            ]\n          ],\n          [\n            [\n              -0.18486733734607697,\n              -0.1645059436559677,\n              -0.011868911795318127\n            ],\n            [\n              -0.2320302575826645,\n              -0.2014266848564148,\n              -0.0683027133345604\n            ],\n            [\n              -0.052557531744241714,\n              -0.09631044417619705,\n              -0.05061513930559158\n            ]\n          ],\n          [\n            [\n              -0.12093799561262131,\n              -0.02727758139371872,\n              -0.003536903066560626\n            ],\n            [\n              -0.17244760692119598,\n              0.15485739707946777,\n              0.08143389225006104\n            ],\n            [\n              0.16484986245632172,\n              -0.09964308887720108,\n              -0.08044561743736267\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.057308949530124664,\n        \"weights\": [\n          [\n            [\n              -0.09050483256578445,\n              0.12815767526626587,\n              0.16287687420845032\n            ],\n            [\n              0.183803990483284,\n              0.20328572392463684,\n              0.12396799027919769\n            ],\n            [\n              -9.452683298150077e-05,\n              -0.08443509787321091,\n              0.10762670636177063\n            ]\n          ],\n          [\n            [\n              -0.1335304081439972,\n              -0.13950252532958984,\n              -0.21879605948925018\n            ],\n            [\n              -0.24052183330059052,\n              0.03482829034328461,\n              -0.2282852828502655\n            ],\n            [\n              -0.22395604848861694,\n              -0.0027982203755527735,\n              0.060258373618125916\n            ]\n          ],\n          [\n            [\n              0.07878094166517258,\n              0.01724150963127613,\n              0.0008939485996961594\n            ],\n            [\n              0.11977328360080719,\n              -0.18965256214141846,\n              0.030577486380934715\n            ],\n            [\n              0.13316135108470917,\n              0.12767747044563293,\n              0.10731510818004608\n            ]\n          ],\n          [\n            [\n              0.183749720454216,\n              -0.06252773851156235,\n              -0.1400405764579773\n            ],\n            [\n              -0.14373596012592316,\n              -0.09674247354269028,\n              -0.13893984258174896\n            ],\n            [\n              -0.06367962062358856,\n              0.14348967373371124,\n              -0.010082925669848919\n            ]\n          ],\n          [\n            [\n              0.050250597298145294,\n              -0.09106949716806412,\n              0.043723855167627335\n            ],\n            [\n              -0.22382380068302155,\n              -0.06815892457962036,\n              -0.24148085713386536\n            ],\n            [\n              -0.22215908765792847,\n              0.13405835628509521,\n              -0.03889786824584007\n            ]\n          ],\n          [\n            [\n              -0.029541397467255592,\n              0.039604585617780685,\n              -0.06830139458179474\n            ],\n            [\n              0.08915697783231735,\n              -0.14642280340194702,\n              0.010759814642369747\n            ],\n            [\n              0.1909075230360031,\n              0.06867215782403946,\n              0.19335366785526276\n            ]\n          ],\n          [\n            [\n              -0.14572195708751678,\n              0.08272267132997513,\n              -0.06870009750127792\n            ],\n            [\n              -0.07135029137134552,\n              -0.08967690169811249,\n              0.17823578417301178\n            ],\n            [\n              -0.016790935769677162,\n              -0.0723540410399437,\n              -0.12720520794391632\n            ]\n          ],\n          [\n            [\n              -0.07198317348957062,\n              -0.08225284516811371,\n              -0.06538373976945877\n            ],\n            [\n              0.009972434490919113,\n              0.08435807377099991,\n              0.21338078379631042\n            ],\n            [\n              0.21013794839382172,\n              0.27571287751197815,\n              0.16767624020576477\n            ]\n          ],\n          [\n            [\n              -0.09659308195114136,\n              -0.08892834931612015,\n              0.04783710464835167\n            ],\n            [\n              0.07307176291942596,\n              0.1534288227558136,\n              0.08913584798574448\n            ],\n            [\n              -0.09973634779453278,\n              -0.15064027905464172,\n              -0.046413201838731766\n            ]\n          ],\n          [\n            [\n              0.06449321657419205,\n              -0.06896071135997772,\n              0.02535885013639927\n            ],\n            [\n              0.12327052652835846,\n              -0.053086407482624054,\n              0.07709107547998428\n            ],\n            [\n              0.17837822437286377,\n              0.03317869082093239,\n              -0.1522664576768875\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.026059698313474655,\n        \"weights\": [\n          [\n            [\n              -0.15007951855659485,\n              -0.0826105996966362,\n              0.06387073546648026\n            ],\n            [\n              0.0058092703111469746,\n              -0.14636841416358948,\n              -0.149959534406662\n            ],\n            [\n              0.13165725767612457,\n              -0.040089916437864304,\n              -0.17642652988433838\n            ]\n          ],\n          [\n            [\n              0.10313130170106888,\n              -0.07163388282060623,\n              -0.14289669692516327\n            ],\n            [\n              -0.1887245625257492,\n              0.12385864555835724,\n              0.1596568524837494\n            ],\n            [\n              -0.08863251656293869,\n              -0.013152513653039932,\n              -0.0032642274163663387\n            ]\n          ],\n          [\n            [\n              0.149405837059021,\n              -0.11012585461139679,\n              0.019091080874204636\n            ],\n            [\n              0.009260197170078754,\n              0.13162799179553986,\n              -0.017952140420675278\n            ],\n            [\n              -0.14289560914039612,\n              -0.11887412518262863,\n              -0.0405728742480278\n            ]\n          ],\n          [\n            [\n              -0.04376418888568878,\n              -0.11715403199195862,\n              0.0011360639473423362\n            ],\n            [\n              -0.16810937225818634,\n              0.144540473818779,\n              -0.1474577635526657\n            ],\n            [\n              0.0034044382628053427,\n              -0.14074952900409698,\n              0.007462761830538511\n            ]\n          ],\n          [\n            [\n              0.1627795696258545,\n              0.024526961147785187,\n              0.063499316573143\n            ],\n            [\n              0.151484414935112,\n              -0.03054751269519329,\n              -0.15957514941692352\n            ],\n            [\n              0.07261790335178375,\n              0.07631902396678925,\n              0.057671498507261276\n            ]\n          ],\n          [\n            [\n              0.04817947372794151,\n              0.04407208412885666,\n              0.112233966588974\n            ],\n            [\n              0.10323022305965424,\n              0.08272760361433029,\n              -0.17297306656837463\n            ],\n            [\n              -0.10106699913740158,\n              -0.011175623163580894,\n              0.08249827474355698\n            ]\n          ],\n          [\n            [\n              0.02647663652896881,\n              -0.17062802612781525,\n              0.1257237046957016\n            ],\n            [\n              0.04216809570789337,\n              -0.10751289874315262,\n              0.02213270217180252\n            ],\n            [\n              -0.17760638892650604,\n              0.058295294642448425,\n              0.015924541279673576\n            ]\n          ],\n          [\n            [\n              -0.024231404066085815,\n              -0.03134108707308769,\n              0.16960833966732025\n            ],\n            [\n              -0.042533110827207565,\n              -0.13449126482009888,\n              0.0006716327625326812\n            ],\n            [\n              -0.18493595719337463,\n              0.09931904077529907,\n              -0.08988532423973083\n            ]\n          ],\n          [\n            [\n              -0.1356506645679474,\n              -0.06827053427696228,\n              0.17023585736751556\n            ],\n            [\n              -0.028375260531902313,\n              -0.08702178299427032,\n              -0.1335667222738266\n            ],\n            [\n              -0.061089497059583664,\n              -0.09718924760818481,\n              -0.08918967097997665\n            ]\n          ],\n          [\n            [\n              0.10196224600076675,\n              -0.18089181184768677,\n              0.14458014070987701\n            ],\n            [\n              -0.009191855788230896,\n              0.14005054533481598,\n              -0.08165542781352997\n            ],\n            [\n              -0.1418958604335785,\n              0.0806783139705658,\n              -0.030212635174393654\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.00868820771574974,\n        \"weights\": [\n          [\n            [\n              -0.15327812731266022,\n              -0.15684106945991516,\n              -0.09862995147705078\n            ],\n            [\n              -0.1392715722322464,\n              0.02297556959092617,\n              -0.049294907599687576\n            ],\n            [\n              -0.016246501356363297,\n              -0.0766921415925026,\n              -0.12175631523132324\n            ]\n          ],\n          [\n            [\n              0.16165898740291595,\n              -0.047843266278505325,\n              -0.03394342213869095\n            ],\n            [\n              0.16841118037700653,\n              -0.1869945377111435,\n              0.015181371010839939\n            ],\n            [\n              -0.04449327290058136,\n              -0.04168952628970146,\n              -0.02938416227698326\n            ]\n          ],\n          [\n            [\n              -0.09043948352336884,\n              -0.12080211937427521,\n              0.13188543915748596\n            ],\n            [\n              0.18012335896492004,\n              -0.08274321258068085,\n              -0.1497965157032013\n            ],\n            [\n              -0.10115096718072891,\n              0.002968881046399474,\n              -0.15274283289909363\n            ]\n          ],\n          [\n            [\n              0.12413588166236877,\n              0.15294747054576874,\n              -0.1342247724533081\n            ],\n            [\n              0.015276852063834667,\n              -0.1625896394252777,\n              -0.15562738478183746\n            ],\n            [\n              -0.15663205087184906,\n              -0.1001645028591156,\n              -0.15538941323757172\n            ]\n          ],\n          [\n            [\n              0.09208566695451736,\n              -0.011178149841725826,\n              -0.16122280061244965\n            ],\n            [\n              0.10234502702951431,\n              0.10364406555891037,\n              -0.08907386660575867\n            ],\n            [\n              0.0834117978811264,\n              0.005532237701117992,\n              0.06636765599250793\n            ]\n          ],\n          [\n            [\n              0.16093949973583221,\n              -0.02300426922738552,\n              -0.1140693873167038\n            ],\n            [\n              -0.1308731585741043,\n              0.08393826335668564,\n              -0.15887396037578583\n            ],\n            [\n              0.04560978710651398,\n              0.07385460287332535,\n              0.16902343928813934\n            ]\n          ],\n          [\n            [\n              -0.07676400244235992,\n              0.07403796911239624,\n              0.022018754854798317\n            ],\n            [\n              0.002957345684990287,\n              0.1573934704065323,\n              0.036665670573711395\n            ],\n            [\n              0.010597487911581993,\n              -0.12240803241729736,\n              -0.032673951238393784\n            ]\n          ],\n          [\n            [\n              0.04240453243255615,\n              -0.11351556330919266,\n              -0.04438462108373642\n            ],\n            [\n              0.006118774879723787,\n              -0.015009335242211819,\n              -0.08410405367612839\n            ],\n            [\n              -0.0913618803024292,\n              0.03194887191057205,\n              0.08386318385601044\n            ]\n          ],\n          [\n            [\n              -0.15381479263305664,\n              -0.11809832602739334,\n              0.12145192921161652\n            ],\n            [\n              -0.05429745092988014,\n              0.1322549730539322,\n              0.03329390659928322\n            ],\n            [\n              -0.13567295670509338,\n              -0.10557886958122253,\n              0.09567809104919434\n            ]\n          ],\n          [\n            [\n              0.10477942228317261,\n              -0.0323621928691864,\n              -0.052374619990587234\n            ],\n            [\n              -0.1321583241224289,\n              -0.056856222450733185,\n              0.003001779317855835\n            ],\n            [\n              0.13107536733150482,\n              0.1097632497549057,\n              -0.04634663462638855\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.10796646773815155,\n        \"weights\": [\n          [\n            [\n              -0.1507304608821869,\n              0.026182429865002632,\n              0.1444566398859024\n            ],\n            [\n              -0.08902022987604141,\n              0.10641860216856003,\n              -0.13652002811431885\n            ],\n            [\n              -0.039226654917001724,\n              -0.022110262885689735,\n              0.12704786658287048\n            ]\n          ],\n          [\n            [\n              0.3481782078742981,\n              0.047287702560424805,\n              0.04006631299853325\n            ],\n            [\n              0.011805927380919456,\n              0.01431749016046524,\n              -0.09476961940526962\n            ],\n            [\n              0.1401517689228058,\n              -0.13059474527835846,\n              0.11255893856287003\n            ]\n          ],\n          [\n            [\n              0.11205146461725235,\n              0.206292986869812,\n              0.377199649810791\n            ],\n            [\n              -0.028613677248358727,\n              0.1723896861076355,\n              0.0018812445923686028\n            ],\n            [\n              0.11386403441429138,\n              -0.06011408194899559,\n              0.02250262349843979\n            ]\n          ],\n          [\n            [\n              -0.05323943868279457,\n              0.0508950911462307,\n              0.29620838165283203\n            ],\n            [\n              0.17404206097126007,\n              -0.13460589945316315,\n              0.09607753902673721\n            ],\n            [\n              0.030427904799580574,\n              0.10790881514549255,\n              -0.0872771218419075\n            ]\n          ],\n          [\n            [\n              -0.2658837139606476,\n              -0.1246415376663208,\n              -0.1741020381450653\n            ],\n            [\n              0.009110961109399796,\n              -0.2966923415660858,\n              0.01331463735550642\n            ],\n            [\n              -0.3045470714569092,\n              -0.0006362893618643284,\n              -0.3392437696456909\n            ]\n          ],\n          [\n            [\n              0.058949440717697144,\n              0.048762403428554535,\n              0.09043963253498077\n            ],\n            [\n              -0.14432676136493683,\n              0.1221139207482338,\n              0.07445020973682404\n            ],\n            [\n              -0.0939536914229393,\n              0.11804372817277908,\n              0.11689592897891998\n            ]\n          ],\n          [\n            [\n              0.0008243181509897113,\n              0.20011498034000397,\n              -0.0012665813555940986\n            ],\n            [\n              -0.030231043696403503,\n              0.03821808844804764,\n              0.18870511651039124\n            ],\n            [\n              0.12854346632957458,\n              0.10788316279649734,\n              0.23676030337810516\n            ]\n          ],\n          [\n            [\n              -0.04490512236952782,\n              -0.1087682768702507,\n              -0.04493439570069313\n            ],\n            [\n              0.19902750849723816,\n              0.11878892034292221,\n              0.34698039293289185\n            ],\n            [\n              -0.0190410278737545,\n              0.15304304659366608,\n              0.2643662095069885\n            ]\n          ],\n          [\n            [\n              -0.0857265517115593,\n              -0.07916845381259918,\n              -0.09775075316429138\n            ],\n            [\n              -0.19191114604473114,\n              0.02176794782280922,\n              0.05157535523176193\n            ],\n            [\n              0.04070887342095375,\n              0.03840877115726471,\n              -0.2585756778717041\n            ]\n          ],\n          [\n            [\n              0.043157365173101425,\n              -0.1509348303079605,\n              -0.004716380499303341\n            ],\n            [\n              -0.03060164675116539,\n              0.0992133617401123,\n              -0.1614258885383606\n            ],\n            [\n              -0.0033713087905198336,\n              0.10944762825965881,\n              -0.16037793457508087\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.05496266856789589,\n        \"weights\": [\n          [\n            [\n              -0.13509221374988556,\n              -0.08933935314416885,\n              0.14709274470806122\n            ],\n            [\n              0.05901208147406578,\n              0.06928139925003052,\n              0.07829902321100235\n            ],\n            [\n              0.002249462064355612,\n              -0.18047143518924713,\n              0.03880225494503975\n            ]\n          ],\n          [\n            [\n              0.030934272333979607,\n              -0.02754145674407482,\n              0.054108597338199615\n            ],\n            [\n              0.25470981001853943,\n              0.10047401487827301,\n              0.14475074410438538\n            ],\n            [\n              0.11149384081363678,\n              -0.048276420682668686,\n              0.00662165367975831\n            ]\n          ],\n          [\n            [\n              0.05291430652141571,\n              0.19186468422412872,\n              0.07003743201494217\n            ],\n            [\n              -0.11174073815345764,\n              0.1881842464208603,\n              0.1360943466424942\n            ],\n            [\n              0.07475761324167252,\n              -0.07336270064115524,\n              -0.10000384598970413\n            ]\n          ],\n          [\n            [\n              -0.07975286990404129,\n              -0.24706263840198517,\n              -0.26596394181251526\n            ],\n            [\n              -0.23612700402736664,\n              -0.01565210521221161,\n              0.07619105279445648\n            ],\n            [\n              -0.04895380511879921,\n              -0.2918459177017212,\n              -0.18951596319675446\n            ]\n          ],\n          [\n            [\n              0.14410583674907684,\n              -0.035015467554330826,\n              0.1238742396235466\n            ],\n            [\n              -0.11094899475574493,\n              0.2838386595249176,\n              0.1437252163887024\n            ],\n            [\n              -0.026335369795560837,\n              -0.02288086898624897,\n              0.10518255829811096\n            ]\n          ],\n          [\n            [\n              -0.1502123922109604,\n              -0.1990748941898346,\n              -0.12756723165512085\n            ],\n            [\n              0.03942368924617767,\n              -0.11737325042486191,\n              0.08303513377904892\n            ],\n            [\n              -0.05691595748066902,\n              0.05863361805677414,\n              0.018958356231451035\n            ]\n          ],\n          [\n            [\n              0.01500424649566412,\n              -0.13895905017852783,\n              -0.019347142428159714\n            ],\n            [\n              0.13329140841960907,\n              0.1639011800289154,\n              0.009016373194754124\n            ],\n            [\n              -0.13765521347522736,\n              -0.084888756275177,\n              0.06924717873334885\n            ]\n          ],\n          [\n            [\n              -0.20339468121528625,\n              0.0933724194765091,\n              0.043660301715135574\n            ],\n            [\n              -0.06258143484592438,\n              -0.012316839769482613,\n              0.1963297426700592\n            ],\n            [\n              0.10155200958251953,\n              0.2652052640914917,\n              0.3104066252708435\n            ]\n          ],\n          [\n            [\n              -0.3128395676612854,\n              -0.29480090737342834,\n              -0.24699248373508453\n            ],\n            [\n              -0.12206172943115234,\n              -0.19278986752033234,\n              -0.3234478533267975\n            ],\n            [\n              0.03785733878612518,\n              -0.26259225606918335,\n              -0.30989569425582886\n            ]\n          ],\n          [\n            [\n              -0.09871251881122589,\n              -0.12416747212409973,\n              0.047914400696754456\n            ],\n            [\n              0.16389912366867065,\n              0.011755961924791336,\n              -0.017375703901052475\n            ],\n            [\n              0.08810996264219284,\n              -0.03147492557764053,\n              -0.08156110346317291\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.03608671948313713,\n        \"weights\": [\n          [\n            [\n              0.07521034777164459,\n              0.032540932297706604,\n              -0.1476229876279831\n            ],\n            [\n              -0.10402955114841461,\n              -0.10514653474092484,\n              0.10495533049106598\n            ],\n            [\n              0.1492186337709427,\n              -0.12216785550117493,\n              0.07365602254867554\n            ]\n          ],\n          [\n            [\n              -0.03775200992822647,\n              -0.09271999448537827,\n              -0.13640622794628143\n            ],\n            [\n              0.007787204813212156,\n              0.13475951552391052,\n              0.08501224964857101\n            ],\n            [\n              0.038410719484090805,\n              -0.18841730058193207,\n              -0.07372402399778366\n            ]\n          ],\n          [\n            [\n              -0.06593351066112518,\n              -0.06819465011358261,\n              0.10519562661647797\n            ],\n            [\n              -0.07410232722759247,\n              0.09198575466871262,\n              0.17219869792461395\n            ],\n            [\n              0.060195110738277435,\n              -0.015554492361843586,\n              0.15317052602767944\n            ]\n          ],\n          [\n            [\n              -0.10770566016435623,\n              -0.0653575137257576,\n              0.16414234042167664\n            ],\n            [\n              -0.10187935829162598,\n              -0.04773027449846268,\n              0.038777489215135574\n            ],\n            [\n              0.05748850852251053,\n              0.041632648557424545,\n              0.062476158142089844\n            ]\n          ],\n          [\n            [\n              -0.025755811482667923,\n              -0.07449911534786224,\n              0.032723233103752136\n            ],\n            [\n              -0.1973990947008133,\n              -0.1767793446779251,\n              -0.06521757692098618\n            ],\n            [\n              0.029782965779304504,\n              0.02546435222029686,\n              -0.03705110400915146\n            ]\n          ],\n          [\n            [\n              -0.09324422478675842,\n              -0.14537101984024048,\n              0.1318194717168808\n            ],\n            [\n              0.00044559736852534115,\n              -0.12903304398059845,\n              -0.12332028895616531\n            ],\n            [\n              0.14647816121578217,\n              -0.10266920924186707,\n              0.14999932050704956\n            ]\n          ],\n          [\n            [\n              -0.06336613744497299,\n              0.08409980684518814,\n              -0.08580014109611511\n            ],\n            [\n              0.1320841759443283,\n              -0.1531745195388794,\n              -0.1763831228017807\n            ],\n            [\n              0.04841432720422745,\n              -0.013923467136919498,\n              0.08944347500801086\n            ]\n          ],\n          [\n            [\n              -0.16080684959888458,\n              0.04525016248226166,\n              0.0414198562502861\n            ],\n            [\n              -0.14402882754802704,\n              -0.1412883847951889,\n              0.055425647646188736\n            ],\n            [\n              0.017574450001120567,\n              0.18039840459823608,\n              -0.009708801284432411\n            ]\n          ],\n          [\n            [\n              -0.13676346838474274,\n              0.014800326898694038,\n              0.05060349404811859\n            ],\n            [\n              -0.04449760541319847,\n              0.025106903165578842,\n              0.0704495757818222\n            ],\n            [\n              0.10736533999443054,\n              -0.050611693412065506,\n              -0.014067563228309155\n            ]\n          ],\n          [\n            [\n              0.037774667143821716,\n              -0.1306772530078888,\n              -0.13443945348262787\n            ],\n            [\n              0.005202122963964939,\n              -0.09896185249090195,\n              0.16476544737815857\n            ],\n            [\n              -0.057337626814842224,\n              0.060669589787721634,\n              -0.07003173232078552\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.009175595827400684,\n        \"weights\": [\n          [\n            [\n              0.1372649222612381,\n              0.07310942560434341,\n              0.09293381869792938\n            ],\n            [\n              -0.01409513596445322,\n              0.18146024644374847,\n              0.19604869186878204\n            ],\n            [\n              -0.008923155255615711,\n              -0.19196783006191254,\n              -0.13125421106815338\n            ]\n          ],\n          [\n            [\n              0.014929680153727531,\n              -0.09620988368988037,\n              -0.15984515845775604\n            ],\n            [\n              0.09972062706947327,\n              -0.022798551246523857,\n              -0.055318545550107956\n            ],\n            [\n              -0.17736919224262238,\n              -0.1110055148601532,\n              -0.1602240949869156\n            ]\n          ],\n          [\n            [\n              0.19213220477104187,\n              0.09443316608667374,\n              0.16093586385250092\n            ],\n            [\n              0.0033481072168797255,\n              -0.037993114441633224,\n              -0.12192386388778687\n            ],\n            [\n              -0.1685992330312729,\n              -0.14172862470149994,\n              0.07944667339324951\n            ]\n          ],\n          [\n            [\n              -0.12170552462339401,\n              -0.13767358660697937,\n              0.07502470165491104\n            ],\n            [\n              0.03369561582803726,\n              -0.15253625810146332,\n              -0.11817822605371475\n            ],\n            [\n              -0.100308857858181,\n              -0.016297699883580208,\n              -0.10435254126787186\n            ]\n          ],\n          [\n            [\n              0.04112238064408302,\n              0.0213188286870718,\n              0.11308953911066055\n            ],\n            [\n              -0.0010636821389198303,\n              0.08405053615570068,\n              0.17462211847305298\n            ],\n            [\n              -0.09219279885292053,\n              0.06761076301336288,\n              -0.008089696988463402\n            ]\n          ],\n          [\n            [\n              -0.061231330037117004,\n              -0.17982029914855957,\n              -0.21145972609519958\n            ],\n            [\n              -0.10267185419797897,\n              -0.10252882540225983,\n              0.08570924401283264\n            ],\n            [\n              -0.025686895474791527,\n              0.16276656091213226,\n              0.022308165207505226\n            ]\n          ],\n          [\n            [\n              -0.016727356240153313,\n              0.14001750946044922,\n              0.12016607820987701\n            ],\n            [\n              0.141238272190094,\n              -0.09624945372343063,\n              -0.1236259713768959\n            ],\n            [\n              0.08482365310192108,\n              -0.150004044175148,\n              0.09036954492330551\n            ]\n          ],\n          [\n            [\n              0.08908596634864807,\n              0.13462431728839874,\n              -0.05469755828380585\n            ],\n            [\n              0.09516725689172745,\n              -0.1146475225687027,\n              0.20659831166267395\n            ],\n            [\n              -0.04286177083849907,\n              0.029708532616496086,\n              0.007774696219712496\n            ]\n          ],\n          [\n            [\n              0.009699777700006962,\n              -0.09435168653726578,\n              0.06956082582473755\n            ],\n            [\n              0.08944177627563477,\n              -0.14151452481746674,\n              0.08015299588441849\n            ],\n            [\n              0.1195245310664177,\n              0.06250646710395813,\n              -0.15702283382415771\n            ]\n          ],\n          [\n            [\n              0.1309179812669754,\n              -0.05220411345362663,\n              0.12045758962631226\n            ],\n            [\n              -0.13330580294132233,\n              -0.0012973755365237594,\n              0.1349809318780899\n            ],\n            [\n              -0.027604976668953896,\n              -0.16998353600502014,\n              0.1366959810256958\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.003776360535994172,\n        \"weights\": [\n          [\n            [\n              0.17279647290706635,\n              -0.06964075565338135,\n              0.04323156550526619\n            ],\n            [\n              -0.025604162365198135,\n              0.14047738909721375,\n              -0.004479078575968742\n            ],\n            [\n              0.029341932386159897,\n              0.030494775623083115,\n              -0.1335068941116333\n            ]\n          ],\n          [\n            [\n              0.09240026026964188,\n              -0.11094912886619568,\n              0.08517055958509445\n            ],\n            [\n              0.012049105018377304,\n              -0.07688391953706741,\n              0.1787722110748291\n            ],\n            [\n              -0.023024313151836395,\n              -0.1682383269071579,\n              -0.178447887301445\n            ]\n          ],\n          [\n            [\n              -0.1495608389377594,\n              0.16619254648685455,\n              0.1342262476682663\n            ],\n            [\n              0.1576603502035141,\n              -0.10944052785634995,\n              -0.01419719960540533\n            ],\n            [\n              -0.03726334124803543,\n              -0.04629863426089287,\n              -0.10657647252082825\n            ]\n          ],\n          [\n            [\n              -0.17824728786945343,\n              -0.08168867975473404,\n              0.025643901899456978\n            ],\n            [\n              -0.12964068353176117,\n              0.09894271939992905,\n              -0.10827117413282394\n            ],\n            [\n              -0.16557833552360535,\n              -0.0790194496512413,\n              0.13079170882701874\n            ]\n          ],\n          [\n            [\n              -0.02129005640745163,\n              -0.14314498007297516,\n              -0.056888509541749954\n            ],\n            [\n              0.135353684425354,\n              0.08285944163799286,\n              -0.12849228084087372\n            ],\n            [\n              0.07882042229175568,\n              -0.16718867421150208,\n              -0.1280491203069687\n            ]\n          ],\n          [\n            [\n              0.17641016840934753,\n              -0.09271540492773056,\n              0.03547276556491852\n            ],\n            [\n              -0.11888423562049866,\n              0.0356455072760582,\n              -0.1658187210559845\n            ],\n            [\n              -0.1298179030418396,\n              0.01567753776907921,\n              0.054983582347631454\n            ]\n          ],\n          [\n            [\n              -0.01863483153283596,\n              0.1717471331357956,\n              -0.07820325344800949\n            ],\n            [\n              -0.17182089388370514,\n              0.16218425333499908,\n              -0.018255263566970825\n            ],\n            [\n              -0.0573904886841774,\n              -0.07738706469535828,\n              -0.18235725164413452\n            ]\n          ],\n          [\n            [\n              0.0639929473400116,\n              -0.11592573672533035,\n              0.060891155153512955\n            ],\n            [\n              -0.10414804518222809,\n              0.11736100167036057,\n              -0.11977871507406235\n            ],\n            [\n              0.051007796078920364,\n              0.022053631022572517,\n              -0.07404788583517075\n            ]\n          ],\n          [\n            [\n              -0.146500825881958,\n              0.1362224519252777,\n              0.02524641901254654\n            ],\n            [\n              -0.16801369190216064,\n              -0.02728229947388172,\n              -0.18054360151290894\n            ],\n            [\n              0.16980679333209991,\n              0.15864145755767822,\n              0.052760049700737\n            ]\n          ],\n          [\n            [\n              -0.020358631387352943,\n              -0.06838512420654297,\n              -0.09648600220680237\n            ],\n            [\n              0.1290944218635559,\n              0.11064353585243225,\n              0.014462131075561047\n            ],\n            [\n              0.047761350870132446,\n              -0.14560990035533905,\n              -0.08718659728765488\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.07763895392417908,\n        \"weights\": [\n          [\n            [\n              0.2694922685623169,\n              0.24197611212730408,\n              0.08383424580097198\n            ],\n            [\n              0.0990048423409462,\n              -0.0034763102885335684,\n              0.15709368884563446\n            ],\n            [\n              0.13681627810001373,\n              0.07826804369688034,\n              0.07874651998281479\n            ]\n          ],\n          [\n            [\n              0.11787533760070801,\n              0.23093080520629883,\n              0.2037479430437088\n            ],\n            [\n              0.01225980930030346,\n              -0.014696580357849598,\n              0.21061557531356812\n            ],\n            [\n              -0.18517103791236877,\n              0.06154485419392586,\n              -0.10908146947622299\n            ]\n          ],\n          [\n            [\n              0.32395485043525696,\n              0.2772344648838043,\n              0.2637268304824829\n            ],\n            [\n              0.31862667202949524,\n              0.20011520385742188,\n              -0.022607995197176933\n            ],\n            [\n              0.27067869901657104,\n              0.25704577565193176,\n              0.0024352900218218565\n            ]\n          ],\n          [\n            [\n              0.00820037443190813,\n              0.1509440690279007,\n              -0.17756050825119019\n            ],\n            [\n              -0.15615518391132355,\n              -0.11637229472398758,\n              0.0665382444858551\n            ],\n            [\n              0.006587250158190727,\n              0.10080977529287338,\n              -0.036548878997564316\n            ]\n          ],\n          [\n            [\n              0.2496289610862732,\n              0.2784920930862427,\n              0.06407802551984787\n            ],\n            [\n              0.06363044679164886,\n              0.038830626755952835,\n              0.0685986652970314\n            ],\n            [\n              0.23036327958106995,\n              0.08188434690237045,\n              0.13114432990550995\n            ]\n          ],\n          [\n            [\n              0.13804379105567932,\n              -0.06537020951509476,\n              0.07749289274215698\n            ],\n            [\n              0.06075216084718704,\n              -0.10845176130533218,\n              -0.13249245285987854\n            ],\n            [\n              -0.28506502509117126,\n              0.0771365612745285,\n              0.03315943852066994\n            ]\n          ],\n          [\n            [\n              0.10850785672664642,\n              0.19386376440525055,\n              0.04043261706829071\n            ],\n            [\n              -0.11965324729681015,\n              0.15540647506713867,\n              0.054148077964782715\n            ],\n            [\n              -0.08049509674310684,\n              -0.09168446809053421,\n              -0.03272629529237747\n            ]\n          ],\n          [\n            [\n              -0.15657858550548553,\n              -0.1601806879043579,\n              0.08438631892204285\n            ],\n            [\n              0.14753501117229462,\n              0.07282920926809311,\n              0.08307341486215591\n            ],\n            [\n              0.11728155612945557,\n              -0.2642086446285248,\n              -0.043825458735227585\n            ]\n          ],\n          [\n            [\n              -0.047177258878946304,\n              0.19377855956554413,\n              0.23226378858089447\n            ],\n            [\n              0.11053099483251572,\n              -0.07336296886205673,\n              0.19029884040355682\n            ],\n            [\n              -0.07901981472969055,\n              0.16959023475646973,\n              0.20988865196704865\n            ]\n          ],\n          [\n            [\n              -0.1105785146355629,\n              0.022895105183124542,\n              -0.05301348865032196\n            ],\n            [\n              -0.11357446014881134,\n              0.12780886888504028,\n              0.10286659002304077\n            ],\n            [\n              0.18008482456207275,\n              0.057069484144449234,\n              -0.1692861020565033\n            ]\n          ]\n        ]\n      }\n    ]\n  },\n  {\n    \"name\": \"relu_2_1\",\n    \"input_shape\": [\n      28,\n      28,\n      10\n    ],\n    \"output_shape\": [\n      28,\n      28,\n      10\n    ],\n    \"num_neurons\": 10\n  },\n  {\n    \"name\": \"conv_2_2\",\n    \"input_shape\": [\n      28,\n      28,\n      10\n    ],\n    \"output_shape\": [\n      26,\n      26,\n      10\n    ],\n    \"num_neurons\": 10,\n    \"weights\": [\n      {\n        \"bias\": -0.06928090751171112,\n        \"weights\": [\n          [\n            [\n              0.08598040044307709,\n              -0.15661418437957764,\n              0.10458510369062424\n            ],\n            [\n              -0.11801018565893173,\n              0.0066466866992414,\n              -0.07464653998613358\n            ],\n            [\n              -0.0566081739962101,\n              -0.07635676115751266,\n              0.016953084617853165\n            ]\n          ],\n          [\n            [\n              -0.04266742616891861,\n              0.15755142271518707,\n              -0.08596903830766678\n            ],\n            [\n              0.03977324068546295,\n              0.17321203649044037,\n              0.16583320498466492\n            ],\n            [\n              -0.1584334522485733,\n              -0.11751523613929749,\n              0.15463431179523468\n            ]\n          ],\n          [\n            [\n              -0.17958573997020721,\n              0.1346961259841919,\n              0.07698938250541687\n            ],\n            [\n              -0.17861729860305786,\n              0.023941045626997948,\n              0.016261372715234756\n            ],\n            [\n              0.05962218716740608,\n              0.04584416374564171,\n              -0.11381524801254272\n            ]\n          ],\n          [\n            [\n              0.11156799644231796,\n              -0.012116417288780212,\n              0.1565047651529312\n            ],\n            [\n              0.007633236236870289,\n              0.13187412917613983,\n              -0.15669921040534973\n            ],\n            [\n              -0.1256016343832016,\n              -0.04784016311168671,\n              -0.10156258195638657\n            ]\n          ],\n          [\n            [\n              0.23189041018486023,\n              0.18846964836120605,\n              0.09803152829408646\n            ],\n            [\n              0.10253984481096268,\n              0.2656000554561615,\n              -0.08241341263055801\n            ],\n            [\n              0.10929454118013382,\n              0.20914490520954132,\n              0.2639399468898773\n            ]\n          ],\n          [\n            [\n              0.11014262586832047,\n              0.12597136199474335,\n              -0.062423381954431534\n            ],\n            [\n              0.23459452390670776,\n              0.18304942548274994,\n              -0.00461974460631609\n            ],\n            [\n              0.19337813556194305,\n              0.17780911922454834,\n              0.15392877161502838\n            ]\n          ],\n          [\n            [\n              0.16176021099090576,\n              0.08786827325820923,\n              -0.16233132779598236\n            ],\n            [\n              0.1456843465566635,\n              0.17537158727645874,\n              0.13992878794670105\n            ],\n            [\n              0.09141495823860168,\n              0.1278669387102127,\n              0.07846090197563171\n            ]\n          ],\n          [\n            [\n              0.09421935677528381,\n              0.008094422519207,\n              0.07347014546394348\n            ],\n            [\n              -0.049914367496967316,\n              -0.09917126595973969,\n              0.031234242022037506\n            ],\n            [\n              -0.14336086809635162,\n              0.15580561757087708,\n              0.0460781529545784\n            ]\n          ],\n          [\n            [\n              -0.11763220280408859,\n              0.13204853236675262,\n              0.04893353208899498\n            ],\n            [\n              -0.012086287140846252,\n              0.018663855269551277,\n              -0.04036339744925499\n            ],\n            [\n              0.005858162883669138,\n              0.04900277405977249,\n              -0.0608283169567585\n            ]\n          ],\n          [\n            [\n              -0.09456668049097061,\n              0.004962638486176729,\n              0.13074404001235962\n            ],\n            [\n              0.016134988516569138,\n              -0.20001815259456635,\n              0.10673729330301285\n            ],\n            [\n              -0.0930432379245758,\n              0.11103270202875137,\n              -0.05254105478525162\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.016896897926926613,\n        \"weights\": [\n          [\n            [\n              0.010515295900404453,\n              0.07174450904130936,\n              0.03518962115049362\n            ],\n            [\n              -0.15565933287143707,\n              0.17348918318748474,\n              -0.046614233404397964\n            ],\n            [\n              -0.10957509279251099,\n              -0.07884623855352402,\n              -0.08424291759729385\n            ]\n          ],\n          [\n            [\n              -0.04069344699382782,\n              -0.018266776576638222,\n              -0.052818767726421356\n            ],\n            [\n              -0.12165004014968872,\n              0.16037918627262115,\n              0.0028885514475405216\n            ],\n            [\n              0.09751822054386139,\n              -0.008691028691828251,\n              0.05092262104153633\n            ]\n          ],\n          [\n            [\n              -0.1637870818376541,\n              -0.11834993958473206,\n              0.03644406422972679\n            ],\n            [\n              0.17490926384925842,\n              -0.16657812893390656,\n              -0.1686854362487793\n            ],\n            [\n              -0.00875480379909277,\n              0.10025767982006073,\n              0.079607293009758\n            ]\n          ],\n          [\n            [\n              -0.16998125612735748,\n              0.011822454631328583,\n              0.10799288004636765\n            ],\n            [\n              0.1708323359489441,\n              0.11254071444272995,\n              -0.023379087448120117\n            ],\n            [\n              -0.09230536222457886,\n              0.13267692923545837,\n              0.06022457033395767\n            ]\n          ],\n          [\n            [\n              -0.039116840809583664,\n              -0.09745854884386063,\n              0.10001355409622192\n            ],\n            [\n              -0.058901846408843994,\n              -0.0633816346526146,\n              0.25829941034317017\n            ],\n            [\n              -0.0033515046816319227,\n              -0.10516081750392914,\n              0.1933746188879013\n            ]\n          ],\n          [\n            [\n              -0.0191354937851429,\n              0.033260591328144073,\n              0.10637985169887543\n            ],\n            [\n              -0.06287524849176407,\n              -0.16584312915802002,\n              -0.178731769323349\n            ],\n            [\n              -0.11020718514919281,\n              -0.26101186871528625,\n              -0.27676376700401306\n            ]\n          ],\n          [\n            [\n              0.12988366186618805,\n              -0.09975159913301468,\n              -0.03594619408249855\n            ],\n            [\n              0.013879583217203617,\n              -0.014339292421936989,\n              -0.12960761785507202\n            ],\n            [\n              -0.029267409816384315,\n              0.0656919926404953,\n              -0.06838901340961456\n            ]\n          ],\n          [\n            [\n              -0.16180476546287537,\n              0.13674040138721466,\n              -0.14401094615459442\n            ],\n            [\n              0.12005572766065598,\n              0.05233161896467209,\n              0.02275143563747406\n            ],\n            [\n              -0.044939711689949036,\n              0.1748659610748291,\n              -0.027873260900378227\n            ]\n          ],\n          [\n            [\n              0.14486064016819,\n              0.11711373925209045,\n              -0.13860833644866943\n            ],\n            [\n              0.003040055511519313,\n              0.09735389053821564,\n              -0.03913307562470436\n            ],\n            [\n              0.14987336099147797,\n              -0.16979654133319855,\n              0.05046921595931053\n            ]\n          ],\n          [\n            [\n              0.33600518107414246,\n              0.23251022398471832,\n              0.17040139436721802\n            ],\n            [\n              0.014293010346591473,\n              -0.04002862796187401,\n              0.18930195271968842\n            ],\n            [\n              0.08930770307779312,\n              0.11070305109024048,\n              0.16571563482284546\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.0003043923934455961,\n        \"weights\": [\n          [\n            [\n              -0.03225057199597359,\n              -0.1675012707710266,\n              0.1059720367193222\n            ],\n            [\n              -0.1665038913488388,\n              -0.04912973567843437,\n              -0.062342386692762375\n            ],\n            [\n              -0.10942523181438446,\n              -0.12790238857269287,\n              -0.17531943321228027\n            ]\n          ],\n          [\n            [\n              -0.12098591774702072,\n              -0.14656835794448853,\n              0.12434645742177963\n            ],\n            [\n              -0.1253986656665802,\n              -0.06918247044086456,\n              -0.08831476420164108\n            ],\n            [\n              -0.10906275361776352,\n              0.0639200359582901,\n              -0.018018294125795364\n            ]\n          ],\n          [\n            [\n              -0.13535581529140472,\n              -0.025706501677632332,\n              0.07317788898944855\n            ],\n            [\n              -0.1467301845550537,\n              9.577369200997055e-05,\n              -0.10828758031129837\n            ],\n            [\n              0.0753730982542038,\n              -0.018032625317573547,\n              -0.08529771864414215\n            ]\n          ],\n          [\n            [\n              -0.1071033701300621,\n              -0.14495904743671417,\n              0.16617079079151154\n            ],\n            [\n              -0.05282296612858772,\n              -0.16811032593250275,\n              0.15499021112918854\n            ],\n            [\n              -0.014911152422428131,\n              0.04684891551733017,\n              -0.02959609217941761\n            ]\n          ],\n          [\n            [\n              0.06498781591653824,\n              -0.04017537459731102,\n              -0.10274551808834076\n            ],\n            [\n              0.07708664238452911,\n              -0.17044679820537567,\n              0.009095845744013786\n            ],\n            [\n              -0.0026617608964443207,\n              -0.05690290406346321,\n              -0.13626231253147125\n            ]\n          ],\n          [\n            [\n              -0.02382458932697773,\n              -0.16046307981014252,\n              -0.04977470263838768\n            ],\n            [\n              0.027230404317378998,\n              -0.13205501437187195,\n              0.08445755392313004\n            ],\n            [\n              0.17525137960910797,\n              -0.01425952184945345,\n              0.05640949308872223\n            ]\n          ],\n          [\n            [\n              -0.011486219242215157,\n              0.03407638147473335,\n              0.06504546105861664\n            ],\n            [\n              0.07462088763713837,\n              0.11084514111280441,\n              -0.17557725310325623\n            ],\n            [\n              -0.00040298295789398253,\n              -0.053970303386449814,\n              -0.044276539236307144\n            ]\n          ],\n          [\n            [\n              0.06198526918888092,\n              0.13931012153625488,\n              0.12043576687574387\n            ],\n            [\n              -0.1402665078639984,\n              0.05957089737057686,\n              0.003116732696071267\n            ],\n            [\n              -0.10933493077754974,\n              -0.04451589658856392,\n              -0.11461278051137924\n            ]\n          ],\n          [\n            [\n              0.17624329030513763,\n              0.1451093852519989,\n              -0.01907532475888729\n            ],\n            [\n              -0.16605983674526215,\n              0.10669174790382385,\n              -0.0948493480682373\n            ],\n            [\n              -0.1267155110836029,\n              -0.12291537970304489,\n              0.05642882362008095\n            ]\n          ],\n          [\n            [\n              0.17027094960212708,\n              -0.08252441138029099,\n              -0.10560049116611481\n            ],\n            [\n              -0.13899169862270355,\n              -0.01087633054703474,\n              0.015592963434755802\n            ],\n            [\n              -0.05057007446885109,\n              -0.1585644632577896,\n              0.06018302962183952\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.025450965389609337,\n        \"weights\": [\n          [\n            [\n              -0.14883872866630554,\n              -0.1539710909128189,\n              0.030757807195186615\n            ],\n            [\n              -0.13624624907970428,\n              -0.017219746485352516,\n              0.0077850897796452045\n            ],\n            [\n              0.07874865084886551,\n              -0.03389166668057442,\n              0.18048839271068573\n            ]\n          ],\n          [\n            [\n              0.0376468263566494,\n              0.11783362925052643,\n              0.09815100580453873\n            ],\n            [\n              0.10770928859710693,\n              0.1612078994512558,\n              -0.12880432605743408\n            ],\n            [\n              -0.010542704723775387,\n              0.1984904557466507,\n              -0.11256863176822662\n            ]\n          ],\n          [\n            [\n              0.06596554070711136,\n              0.11651577055454254,\n              -0.11643873155117035\n            ],\n            [\n              0.15006743371486664,\n              -0.1437334269285202,\n              0.1470753401517868\n            ],\n            [\n              -0.13797885179519653,\n              -0.018675317987799644,\n              0.15400108695030212\n            ]\n          ],\n          [\n            [\n              0.12248498201370239,\n              -0.06398702412843704,\n              0.18048100173473358\n            ],\n            [\n              0.04396796599030495,\n              0.002043294021859765,\n              -0.08613097667694092\n            ],\n            [\n              0.1374073177576065,\n              0.005020493175834417,\n              -0.13388139009475708\n            ]\n          ],\n          [\n            [\n              -0.06050415709614754,\n              -0.11082898080348969,\n              0.09905088692903519\n            ],\n            [\n              0.143870010972023,\n              0.15556935966014862,\n              0.01168390829116106\n            ],\n            [\n              -0.030719488859176636,\n              -0.162771075963974,\n              -0.0974450558423996\n            ]\n          ],\n          [\n            [\n              -0.296623170375824,\n              0.09341170638799667,\n              0.05299468711018562\n            ],\n            [\n              0.12450848519802094,\n              -0.1708381175994873,\n              -0.05469423532485962\n            ],\n            [\n              -0.11729957908391953,\n              -0.13073882460594177,\n              -0.09305202215909958\n            ]\n          ],\n          [\n            [\n              0.14354491233825684,\n              -0.07621651887893677,\n              0.1559310257434845\n            ],\n            [\n              0.16547739505767822,\n              0.04346732795238495,\n              -0.16540241241455078\n            ],\n            [\n              -0.14048179984092712,\n              0.1427590399980545,\n              0.12544023990631104\n            ]\n          ],\n          [\n            [\n              -0.05909881368279457,\n              0.17422327399253845,\n              0.19445350766181946\n            ],\n            [\n              0.054037272930145264,\n              -0.1322638839483261,\n              0.07989992946386337\n            ],\n            [\n              -0.09926623106002808,\n              0.09489045292139053,\n              0.0921379029750824\n            ]\n          ],\n          [\n            [\n              -0.13270999491214752,\n              -0.042694009840488434,\n              -0.020755372941493988\n            ],\n            [\n              -0.021717816591262817,\n              -0.1382790207862854,\n              0.12393639981746674\n            ],\n            [\n              -0.03843085840344429,\n              -0.052387405186891556,\n              -0.03458016365766525\n            ]\n          ],\n          [\n            [\n              0.260768860578537,\n              0.11117256432771683,\n              0.18648739159107208\n            ],\n            [\n              0.27562054991722107,\n              0.19844390451908112,\n              0.2226613461971283\n            ],\n            [\n              0.25097647309303284,\n              0.11254110932350159,\n              0.11099715530872345\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.08187747746706009,\n        \"weights\": [\n          [\n            [\n              0.10404898971319199,\n              0.08964062482118607,\n              0.02767273783683777\n            ],\n            [\n              -0.21951045095920563,\n              -0.05491212010383606,\n              -0.23909711837768555\n            ],\n            [\n              -0.12873464822769165,\n              -0.12541238963603973,\n              -0.016261311247944832\n            ]\n          ],\n          [\n            [\n              0.05838247761130333,\n              -0.040936172008514404,\n              0.08199704438447952\n            ],\n            [\n              -0.1735961139202118,\n              -0.13730625808238983,\n              0.17307841777801514\n            ],\n            [\n              -0.12099836766719818,\n              -0.10004908591508865,\n              0.07688338309526443\n            ]\n          ],\n          [\n            [\n              -0.18259228765964508,\n              0.15632861852645874,\n              0.006600016262382269\n            ],\n            [\n              -0.023014860227704048,\n              0.07120193541049957,\n              -0.09667934477329254\n            ],\n            [\n              0.16096729040145874,\n              0.14017324149608612,\n              -0.11965309828519821\n            ]\n          ],\n          [\n            [\n              -0.12984591722488403,\n              0.11726689338684082,\n              0.16403205692768097\n            ],\n            [\n              -0.08682794868946075,\n              0.004607963841408491,\n              0.052352651953697205\n            ],\n            [\n              0.13781727850437164,\n              -0.033284109085798264,\n              -0.13342851400375366\n            ]\n          ],\n          [\n            [\n              0.17575091123580933,\n              -0.05865838751196861,\n              0.3193773031234741\n            ],\n            [\n              0.22509527206420898,\n              0.09300155937671661,\n              0.16211801767349243\n            ],\n            [\n              0.2365456372499466,\n              0.09619361162185669,\n              0.035926446318626404\n            ]\n          ],\n          [\n            [\n              -0.0903039500117302,\n              0.2372758835554123,\n              0.23922602832317352\n            ],\n            [\n              -0.0025516848545521498,\n              -0.05170881748199463,\n              -0.003260094905272126\n            ],\n            [\n              -0.013456434942781925,\n              0.2358999252319336,\n              0.027249205857515335\n            ]\n          ],\n          [\n            [\n              0.06481575965881348,\n              0.04652019962668419,\n              0.17423945665359497\n            ],\n            [\n              0.041122306138277054,\n              0.06162906438112259,\n              -0.12034609913825989\n            ],\n            [\n              -0.0683126226067543,\n              0.05408501252532005,\n              0.009873728267848492\n            ]\n          ],\n          [\n            [\n              -0.023901041597127914,\n              0.16978727281093597,\n              -0.13701239228248596\n            ],\n            [\n              -0.15532830357551575,\n              0.19634653627872467,\n              -0.06749705225229263\n            ],\n            [\n              0.06686746329069138,\n              -0.04560288041830063,\n              0.1947353482246399\n            ]\n          ],\n          [\n            [\n              0.09764733165502548,\n              0.11716950684785843,\n              -0.05606110766530037\n            ],\n            [\n              0.0921536460518837,\n              -0.17302048206329346,\n              -0.05728591978549957\n            ],\n            [\n              -0.039875198155641556,\n              -0.15957432985305786,\n              0.1714814454317093\n            ]\n          ],\n          [\n            [\n              -0.09091954678297043,\n              0.1893412321805954,\n              0.2223561853170395\n            ],\n            [\n              -0.09084659069776535,\n              -0.090516097843647,\n              0.25710973143577576\n            ],\n            [\n              0.015550807118415833,\n              -0.0536077618598938,\n              -0.07631698250770569\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.0007078819326125085,\n        \"weights\": [\n          [\n            [\n              0.10348378121852875,\n              0.1658703088760376,\n              0.21937958896160126\n            ],\n            [\n              -0.1605636179447174,\n              -0.10375072062015533,\n              0.0317123606801033\n            ],\n            [\n              -0.08316430449485779,\n              0.10123857110738754,\n              -0.13323435187339783\n            ]\n          ],\n          [\n            [\n              0.046227842569351196,\n              0.25546056032180786,\n              0.2341330200433731\n            ],\n            [\n              0.24970883131027222,\n              0.026491131633520126,\n              0.20424413681030273\n            ],\n            [\n              0.05459621176123619,\n              0.022550098598003387,\n              0.017616553232073784\n            ]\n          ],\n          [\n            [\n              0.12702660262584686,\n              0.05750229209661484,\n              0.12593555450439453\n            ],\n            [\n              0.09516311436891556,\n              -0.021120281890034676,\n              0.17927201092243195\n            ],\n            [\n              -0.15829746425151825,\n              0.08277112990617752,\n              -0.02489512600004673\n            ]\n          ],\n          [\n            [\n              -0.09650641679763794,\n              -0.16835880279541016,\n              0.06058763340115547\n            ],\n            [\n              -0.17324136197566986,\n              0.05352270230650902,\n              0.13822206854820251\n            ],\n            [\n              -0.05745401233434677,\n              0.1021539568901062,\n              0.04325346648693085\n            ]\n          ],\n          [\n            [\n              0.08210805058479309,\n              0.17616593837738037,\n              0.15967094898223877\n            ],\n            [\n              0.3011374771595001,\n              0.20852616429328918,\n              0.1891082376241684\n            ],\n            [\n              0.07962772250175476,\n              -0.10633142292499542,\n              -0.01192254014313221\n            ]\n          ],\n          [\n            [\n              -0.07665342092514038,\n              -0.0029840569477528334,\n              -0.10289561003446579\n            ],\n            [\n              -0.11194054782390594,\n              -0.2018415778875351,\n              -0.03850949928164482\n            ],\n            [\n              -0.10226105898618698,\n              0.027894427999854088,\n              -0.33567458391189575\n            ]\n          ],\n          [\n            [\n              -0.05404815077781677,\n              -0.1340097039937973,\n              -0.04483448714017868\n            ],\n            [\n              0.10605424642562866,\n              0.033482909202575684,\n              -0.03591015562415123\n            ],\n            [\n              0.050270434468984604,\n              -0.08913973718881607,\n              0.12602296471595764\n            ]\n          ],\n          [\n            [\n              0.05558621138334274,\n              -0.015462568961083889,\n              -0.011693321168422699\n            ],\n            [\n              -0.030964285135269165,\n              -0.12944014370441437,\n              0.009920598939061165\n            ],\n            [\n              0.09361882507801056,\n              -0.06596191972494125,\n              0.02901480719447136\n            ]\n          ],\n          [\n            [\n              -0.16552084684371948,\n              0.07774512469768524,\n              -0.1292998492717743\n            ],\n            [\n              -0.17971312999725342,\n              -0.1001170203089714,\n              0.022619865834712982\n            ],\n            [\n              0.01972196064889431,\n              0.11501184105873108,\n              0.12538138031959534\n            ]\n          ],\n          [\n            [\n              0.019463486969470978,\n              -0.0983787402510643,\n              0.13546645641326904\n            ],\n            [\n              -0.16044984757900238,\n              0.12485826760530472,\n              0.05440671741962433\n            ],\n            [\n              -0.1767689734697342,\n              0.08979399502277374,\n              -0.13887923955917358\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.08010556548833847,\n        \"weights\": [\n          [\n            [\n              0.1133585199713707,\n              -0.00997573509812355,\n              0.14980067312717438\n            ],\n            [\n              0.2416609227657318,\n              0.2957213819026947,\n              0.31225094199180603\n            ],\n            [\n              0.15922629833221436,\n              0.30081695318222046,\n              0.2270483672618866\n            ]\n          ],\n          [\n            [\n              -0.1329600065946579,\n              -0.07163365930318832,\n              0.0458776131272316\n            ],\n            [\n              -0.09909605979919434,\n              -0.06709331274032593,\n              0.037862274795770645\n            ],\n            [\n              -0.18296410143375397,\n              -0.1338837444782257,\n              -0.26663830876350403\n            ]\n          ],\n          [\n            [\n              0.06541728228330612,\n              -0.13355036079883575,\n              -0.020394109189510345\n            ],\n            [\n              -0.11164810508489609,\n              0.1490645706653595,\n              0.017893286421895027\n            ],\n            [\n              -0.09118642657995224,\n              -0.1515958458185196,\n              -0.03566630929708481\n            ]\n          ],\n          [\n            [\n              0.07353078573942184,\n              -0.16447250545024872,\n              0.17671015858650208\n            ],\n            [\n              -0.04292967915534973,\n              -0.05507134273648262,\n              0.14150682091712952\n            ],\n            [\n              0.10491141676902771,\n              0.011298518627882004,\n              0.10192622989416122\n            ]\n          ],\n          [\n            [\n              0.18014416098594666,\n              -0.07646407932043076,\n              -0.05285857990384102\n            ],\n            [\n              -0.1046762764453888,\n              0.01189336646348238,\n              0.033325064927339554\n            ],\n            [\n              -0.18640351295471191,\n              -0.10287334024906158,\n              0.1082197055220604\n            ]\n          ],\n          [\n            [\n              0.19771923124790192,\n              0.169350266456604,\n              0.14940451085567474\n            ],\n            [\n              0.18463142216205597,\n              0.11183974891901016,\n              -0.0458606481552124\n            ],\n            [\n              -0.04137343913316727,\n              0.24504254758358002,\n              0.2552352547645569\n            ]\n          ],\n          [\n            [\n              0.03883717954158783,\n              0.018807582557201385,\n              -0.10296282172203064\n            ],\n            [\n              0.07945653796195984,\n              0.04317868500947952,\n              -0.1169438287615776\n            ],\n            [\n              0.07721126079559326,\n              0.05919022485613823,\n              0.05295775085687637\n            ]\n          ],\n          [\n            [\n              0.07933920621871948,\n              -0.11539453268051147,\n              -0.14242030680179596\n            ],\n            [\n              -0.09015672653913498,\n              0.0723484605550766,\n              0.11390630900859833\n            ],\n            [\n              -0.20067369937896729,\n              0.12236489355564117,\n              -0.00815193448215723\n            ]\n          ],\n          [\n            [\n              0.1511957347393036,\n              0.04844445362687111,\n              0.016093147918581963\n            ],\n            [\n              -0.02125474624335766,\n              0.016247756779193878,\n              -0.1110120490193367\n            ],\n            [\n              0.12395704537630081,\n              0.09948017448186874,\n              -0.04832472652196884\n            ]\n          ],\n          [\n            [\n              0.20658090710639954,\n              0.22431017458438873,\n              0.1386585384607315\n            ],\n            [\n              0.022130215540528297,\n              0.2725863456726074,\n              0.01770108751952648\n            ],\n            [\n              -0.17148301005363464,\n              0.12963199615478516,\n              0.02227788418531418\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.00032840375206433237,\n        \"weights\": [\n          [\n            [\n              0.14926579594612122,\n              -0.1658632755279541,\n              -0.006891955621540546\n            ],\n            [\n              0.018407020717859268,\n              0.11032596230506897,\n              -0.09037923067808151\n            ],\n            [\n              0.054062776267528534,\n              -0.024696068838238716,\n              -0.13622203469276428\n            ]\n          ],\n          [\n            [\n              0.04308362305164337,\n              0.011902612634003162,\n              -0.14171727001667023\n            ],\n            [\n              -0.029846854507923126,\n              -0.10324931889772415,\n              -0.060329001396894455\n            ],\n            [\n              0.18067266047000885,\n              -0.17709118127822876,\n              -0.017955072224140167\n            ]\n          ],\n          [\n            [\n              -0.07637296617031097,\n              -0.04532130807638168,\n              0.09160733222961426\n            ],\n            [\n              -0.07682713866233826,\n              0.11216340959072113,\n              -0.08164174854755402\n            ],\n            [\n              -0.15361127257347107,\n              -0.12153203040361404,\n              0.07293781638145447\n            ]\n          ],\n          [\n            [\n              -0.06226680055260658,\n              -0.020958133041858673,\n              -0.17354628443717957\n            ],\n            [\n              0.005389615427702665,\n              -0.021011322736740112,\n              -0.12607698142528534\n            ],\n            [\n              -0.02782212570309639,\n              -0.10399992018938065,\n              -0.14286470413208008\n            ]\n          ],\n          [\n            [\n              -0.0819178894162178,\n              0.05096708983182907,\n              0.0012606605887413025\n            ],\n            [\n              -0.05451017990708351,\n              0.1276327669620514,\n              -0.15054619312286377\n            ],\n            [\n              -0.12790453433990479,\n              0.04455510899424553,\n              -0.11942426860332489\n            ]\n          ],\n          [\n            [\n              0.07089143991470337,\n              -0.027805596590042114,\n              -0.0731336772441864\n            ],\n            [\n              -0.15991254150867462,\n              0.0080441078171134,\n              -0.01506744883954525\n            ],\n            [\n              0.09929340332746506,\n              0.031093627214431763,\n              0.07412277162075043\n            ]\n          ],\n          [\n            [\n              0.03911280632019043,\n              0.004630458075553179,\n              -0.15607154369354248\n            ],\n            [\n              -0.03609822317957878,\n              -0.0874827653169632,\n              0.04198885336518288\n            ],\n            [\n              0.003667711280286312,\n              0.12561820447444916,\n              0.05015202611684799\n            ]\n          ],\n          [\n            [\n              -0.17954029142856598,\n              -0.17719751596450806,\n              -0.12609604001045227\n            ],\n            [\n              0.06174042820930481,\n              -0.12848852574825287,\n              -0.03465423732995987\n            ],\n            [\n              -0.009152312763035297,\n              0.06498356908559799,\n              0.08579559624195099\n            ]\n          ],\n          [\n            [\n              0.15141189098358154,\n              0.043854519724845886,\n              0.0866377055644989\n            ],\n            [\n              -0.02429567277431488,\n              0.02457781881093979,\n              0.10732681304216385\n            ],\n            [\n              -0.17606911063194275,\n              0.18131668865680695,\n              -0.11436362564563751\n            ]\n          ],\n          [\n            [\n              -0.06993728131055832,\n              -0.07894193381071091,\n              -0.03506617620587349\n            ],\n            [\n              -0.04411335662007332,\n              -0.10679899901151657,\n              0.03913060203194618\n            ],\n            [\n              0.15648718178272247,\n              -0.09655686467885971,\n              0.11565577238798141\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.10102199018001556,\n        \"weights\": [\n          [\n            [\n              -0.18843390047550201,\n              -0.17197029292583466,\n              0.09857185930013657\n            ],\n            [\n              -0.06710131466388702,\n              0.042346883565187454,\n              0.12859860062599182\n            ],\n            [\n              -0.08121371269226074,\n              -0.03670134395360947,\n              0.0045297881588339806\n            ]\n          ],\n          [\n            [\n              -0.14061777293682098,\n              -0.1551135927438736,\n              0.12329231202602386\n            ],\n            [\n              0.12051475793123245,\n              0.20856688916683197,\n              0.11334879696369171\n            ],\n            [\n              -0.12097702920436859,\n              0.13367164134979248,\n              -0.16495303809642792\n            ]\n          ],\n          [\n            [\n              0.12965843081474304,\n              0.18333521485328674,\n              0.08880039304494858\n            ],\n            [\n              0.12541590631008148,\n              0.06118476018309593,\n              -0.06615698337554932\n            ],\n            [\n              0.044610921293497086,\n              -0.002378442557528615,\n              -0.1364840716123581\n            ]\n          ],\n          [\n            [\n              0.07500725239515305,\n              0.15382656455039978,\n              -0.18037930130958557\n            ],\n            [\n              -0.09707397222518921,\n              0.025962140411138535,\n              0.17580506205558777\n            ],\n            [\n              -0.05261111259460449,\n              0.04544889181852341,\n              0.1407015472650528\n            ]\n          ],\n          [\n            [\n              -0.00922468863427639,\n              0.15449784696102142,\n              0.12286806851625443\n            ],\n            [\n              0.26022836565971375,\n              0.29797008633613586,\n              0.12426729500293732\n            ],\n            [\n              0.028880679979920387,\n              -0.10370765626430511,\n              -0.1462513953447342\n            ]\n          ],\n          [\n            [\n              -0.11443484574556351,\n              0.09595445543527603,\n              -0.0535745806992054\n            ],\n            [\n              0.03697243332862854,\n              -0.13069505989551544,\n              -0.2539665400981903\n            ],\n            [\n              -0.2158351093530655,\n              -0.3199607729911804,\n              -0.16416803002357483\n            ]\n          ],\n          [\n            [\n              -0.11691446602344513,\n              0.030756477266550064,\n              -0.09193025529384613\n            ],\n            [\n              -0.07388360798358917,\n              0.046113599091768265,\n              -0.17120300233364105\n            ],\n            [\n              0.1160215511918068,\n              0.034703608602285385,\n              0.18252792954444885\n            ]\n          ],\n          [\n            [\n              -0.07212908565998077,\n              0.09425552934408188,\n              -0.020474182441830635\n            ],\n            [\n              -0.14558999240398407,\n              -0.2292071431875229,\n              -0.09061917662620544\n            ],\n            [\n              -0.13141657412052155,\n              -0.06997330486774445,\n              -0.12621599435806274\n            ]\n          ],\n          [\n            [\n              -0.16734322905540466,\n              -0.1640625,\n              0.17294487357139587\n            ],\n            [\n              -0.03534962981939316,\n              -0.03587022051215172,\n              -0.1575644314289093\n            ],\n            [\n              0.003622462972998619,\n              -0.17602811753749847,\n              -0.06479363888502121\n            ]\n          ],\n          [\n            [\n              0.10049576312303543,\n              -0.13191525638103485,\n              0.17221088707447052\n            ],\n            [\n              0.08992503583431244,\n              0.09434428811073303,\n              0.01953481324017048\n            ],\n            [\n              0.027625583112239838,\n              -0.13099995255470276,\n              0.22338591516017914\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.1839410364627838,\n        \"weights\": [\n          [\n            [\n              0.33975979685783386,\n              -0.0007806850480847061,\n              0.008579856716096401\n            ],\n            [\n              0.3656090497970581,\n              0.2688677906990051,\n              0.016921566799283028\n            ],\n            [\n              0.35365620255470276,\n              0.12918882071971893,\n              -0.05961879342794418\n            ]\n          ],\n          [\n            [\n              -0.14423567056655884,\n              -0.05012332648038864,\n              0.07619207352399826\n            ],\n            [\n              -0.09011676162481308,\n              -0.04360014945268631,\n              -0.07565093785524368\n            ],\n            [\n              0.019539909437298775,\n              0.13974043726921082,\n              -0.14280207455158234\n            ]\n          ],\n          [\n            [\n              0.03198204189538956,\n              0.06887462735176086,\n              -0.1125723198056221\n            ],\n            [\n              0.14379338920116425,\n              -0.07409057766199112,\n              0.14650262892246246\n            ],\n            [\n              -0.17403532564640045,\n              0.07053810358047485,\n              0.03149620443582535\n            ]\n          ],\n          [\n            [\n              -0.02583300694823265,\n              -0.16200023889541626,\n              -0.0300496444106102\n            ],\n            [\n              -0.1711486428976059,\n              -0.07888854295015335,\n              0.07103282958269119\n            ],\n            [\n              0.031202886253595352,\n              0.008295580744743347,\n              -0.07996819913387299\n            ]\n          ],\n          [\n            [\n              0.1111307144165039,\n              0.01955331861972809,\n              0.2893529236316681\n            ],\n            [\n              -0.15778030455112457,\n              0.1852802336215973,\n              0.24673998355865479\n            ],\n            [\n              -0.13873955607414246,\n              -0.1221257820725441,\n              0.020849088206887245\n            ]\n          ],\n          [\n            [\n              0.09436196833848953,\n              -0.11874820291996002,\n              0.13310982286930084\n            ],\n            [\n              -0.18267351388931274,\n              -0.16806723177433014,\n              -0.10300038754940033\n            ],\n            [\n              0.03245091810822487,\n              0.02065262384712696,\n              -0.02199680171906948\n            ]\n          ],\n          [\n            [\n              -0.14088568091392517,\n              -0.07974132150411606,\n              0.07132813334465027\n            ],\n            [\n              0.10476739704608917,\n              -0.1766519844532013,\n              0.054840538650751114\n            ],\n            [\n              -0.16788484156131744,\n              0.03572266548871994,\n              -0.09109844267368317\n            ]\n          ],\n          [\n            [\n              -0.08281754702329636,\n              0.0322098508477211,\n              0.17384164035320282\n            ],\n            [\n              -0.07534745335578918,\n              0.02286914363503456,\n              -0.07090599089860916\n            ],\n            [\n              -0.06827270239591599,\n              -0.008776373229920864,\n              0.06894057989120483\n            ]\n          ],\n          [\n            [\n              -0.16268222033977509,\n              -0.08499103039503098,\n              0.15647783875465393\n            ],\n            [\n              -0.15771383047103882,\n              0.15894384682178497,\n              0.15144884586334229\n            ],\n            [\n              0.07181107997894287,\n              -0.06984170526266098,\n              -0.07709267735481262\n            ]\n          ],\n          [\n            [\n              0.006052928511053324,\n              -0.146978959441185,\n              -0.041797246783971786\n            ],\n            [\n              -0.14079707860946655,\n              -0.22883114218711853,\n              -0.11258888989686966\n            ],\n            [\n              -0.16370557248592377,\n              -0.10180635750293732,\n              -0.016936233267188072\n            ]\n          ]\n        ]\n      }\n    ]\n  },\n  {\n    \"name\": \"relu_2_2\",\n    \"input_shape\": [\n      26,\n      26,\n      10\n    ],\n    \"output_shape\": [\n      26,\n      26,\n      10\n    ],\n    \"num_neurons\": 10\n  },\n  {\n    \"name\": \"max_pool_2\",\n    \"input_shape\": [\n      26,\n      26,\n      10\n    ],\n    \"output_shape\": [\n      13,\n      13,\n      10\n    ],\n    \"num_neurons\": 10\n  },\n  {\n    \"name\": \"conv_3_1\",\n    \"input_shape\": [\n      13,\n      13,\n      10\n    ],\n    \"output_shape\": [\n      11,\n      11,\n      10\n    ],\n    \"num_neurons\": 10,\n    \"weights\": [\n      {\n        \"bias\": 0.0007923436933197081,\n        \"weights\": [\n          [\n            [\n              0.014084209688007832,\n              0.12834112346172333,\n              0.13770976662635803\n            ],\n            [\n              -0.15767118334770203,\n              -0.14443932473659515,\n              0.020143991336226463\n            ],\n            [\n              -0.137760728597641,\n              0.09652291983366013,\n              -0.05444719269871712\n            ]\n          ],\n          [\n            [\n              -0.09999548643827438,\n              0.11078786849975586,\n              -0.022936243563890457\n            ],\n            [\n              -0.1495421975851059,\n              -0.18033678829669952,\n              0.0147013608366251\n            ],\n            [\n              0.11255548149347305,\n              -0.08196849375963211,\n              -0.020989680662751198\n            ]\n          ],\n          [\n            [\n              -0.020475145429372787,\n              0.10974124073982239,\n              0.13217176496982574\n            ],\n            [\n              0.1812802404165268,\n              0.1309806853532791,\n              0.165183424949646\n            ],\n            [\n              0.032678816467523575,\n              0.11972841620445251,\n              0.11467933654785156\n            ]\n          ],\n          [\n            [\n              -0.01285762619227171,\n              -0.04902748763561249,\n              -0.17654164135456085\n            ],\n            [\n              -0.03163326531648636,\n              0.08867398649454117,\n              -0.14534570276737213\n            ],\n            [\n              0.05154038593173027,\n              -0.11657627671957016,\n              -0.1699831187725067\n            ]\n          ],\n          [\n            [\n              -0.05665671080350876,\n              -0.04876944422721863,\n              0.04310169443488121\n            ],\n            [\n              -0.07165641337633133,\n              0.02466914989054203,\n              -0.1543319970369339\n            ],\n            [\n              -0.07406008988618851,\n              0.09902089834213257,\n              -0.0853695422410965\n            ]\n          ],\n          [\n            [\n              -0.09680116176605225,\n              0.0020944171119481325,\n              0.06861642003059387\n            ],\n            [\n              0.06426360458135605,\n              0.0014486954314634204,\n              -0.06736121326684952\n            ],\n            [\n              -0.02081463299691677,\n              0.010254998691380024,\n              -0.1277466118335724\n            ]\n          ],\n          [\n            [\n              0.13085784018039703,\n              0.01865667849779129,\n              0.11534454673528671\n            ],\n            [\n              -0.16589294373989105,\n              0.15188102424144745,\n              -0.02093818411231041\n            ],\n            [\n              -0.1733652651309967,\n              -0.005625993013381958,\n              -0.07835599780082703\n            ]\n          ],\n          [\n            [\n              -0.0005362312076613307,\n              -0.14726924896240234,\n              0.11500425636768341\n            ],\n            [\n              0.04323713108897209,\n              -0.02632644958794117,\n              -0.12503141164779663\n            ],\n            [\n              -0.08531049638986588,\n              0.03978920727968216,\n              0.026031920686364174\n            ]\n          ],\n          [\n            [\n              -0.09447162598371506,\n              0.08095337450504303,\n              -0.0033416757360100746\n            ],\n            [\n              0.07817487418651581,\n              -0.08524847030639648,\n              0.15410764515399933\n            ],\n            [\n              0.07341686636209488,\n              -0.08353333920240402,\n              0.09512470662593842\n            ]\n          ],\n          [\n            [\n              -0.1630215346813202,\n              -0.048441022634506226,\n              -0.13386407494544983\n            ],\n            [\n              0.03796912357211113,\n              -0.12130269408226013,\n              0.10480397939682007\n            ],\n            [\n              -0.09396984428167343,\n              0.14544925093650818,\n              0.13277694582939148\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.03995319455862045,\n        \"weights\": [\n          [\n            [\n              0.10615699738264084,\n              0.1713346689939499,\n              -0.20744158327579498\n            ],\n            [\n              0.12267278879880905,\n              0.11937644332647324,\n              0.00038562045665457845\n            ],\n            [\n              0.03341822326183319,\n              0.11432299017906189,\n              -0.05625610798597336\n            ]\n          ],\n          [\n            [\n              0.09210916608572006,\n              -0.040792591869831085,\n              -0.02658102475106716\n            ],\n            [\n              0.08646120131015778,\n              0.12138230353593826,\n              -0.1291978806257248\n            ],\n            [\n              0.02896762639284134,\n              -0.13562193512916565,\n              -0.21209317445755005\n            ]\n          ],\n          [\n            [\n              0.11792007833719254,\n              -0.08320023119449615,\n              -0.04069831594824791\n            ],\n            [\n              0.15743722021579742,\n              -0.1775541752576828,\n              -0.10747846961021423\n            ],\n            [\n              0.15329301357269287,\n              0.1677314192056656,\n              0.12893836200237274\n            ]\n          ],\n          [\n            [\n              0.12554802000522614,\n              -0.17939364910125732,\n              0.11124496906995773\n            ],\n            [\n              0.09351976215839386,\n              -0.1847659945487976,\n              -0.10156165063381195\n            ],\n            [\n              0.05262088030576706,\n              -0.03287182003259659,\n              -0.1808682680130005\n            ]\n          ],\n          [\n            [\n              0.024454670026898384,\n              -0.012735440395772457,\n              -0.12574359774589539\n            ],\n            [\n              0.07756925374269485,\n              -0.010186216793954372,\n              0.010992603376507759\n            ],\n            [\n              -0.0616394467651844,\n              0.13827480375766754,\n              -0.001830886583775282\n            ]\n          ],\n          [\n            [\n              -0.18649210035800934,\n              -0.06740047037601471,\n              -0.15099391341209412\n            ],\n            [\n              -0.07808662205934525,\n              0.14133435487747192,\n              0.17570528388023376\n            ],\n            [\n              -0.1506563127040863,\n              0.1270168423652649,\n              0.0695977583527565\n            ]\n          ],\n          [\n            [\n              -0.07790328562259674,\n              -0.11900027841329575,\n              -0.14872725307941437\n            ],\n            [\n              0.010531118139624596,\n              -0.06965688616037369,\n              -0.1263745129108429\n            ],\n            [\n              -0.09708639979362488,\n              -0.09562280029058456,\n              -0.049736279994249344\n            ]\n          ],\n          [\n            [\n              0.1256728172302246,\n              -0.11736968904733658,\n              0.12513695657253265\n            ],\n            [\n              -0.023180535063147545,\n              0.1055678203701973,\n              0.0043745096772909164\n            ],\n            [\n              -0.05807867273688316,\n              0.10425814986228943,\n              -0.020190350711345673\n            ]\n          ],\n          [\n            [\n              -0.06526564061641693,\n              -0.07953085005283356,\n              0.06664080917835236\n            ],\n            [\n              -0.14328138530254364,\n              0.0676945149898529,\n              -0.16580939292907715\n            ],\n            [\n              -0.094744972884655,\n              -0.027153242379426956,\n              -0.0967867374420166\n            ]\n          ],\n          [\n            [\n              0.03862609341740608,\n              -0.10124414414167404,\n              0.14332693815231323\n            ],\n            [\n              0.16817833483219147,\n              0.024794770404696465,\n              0.06339366734027863\n            ],\n            [\n              -0.0812276303768158,\n              0.19088798761367798,\n              -0.07698020339012146\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.1186998263001442,\n        \"weights\": [\n          [\n            [\n              -0.1698475033044815,\n              -0.052456241101026535,\n              0.0413820780813694\n            ],\n            [\n              -0.06934557110071182,\n              -0.10775590687990189,\n              0.09477849304676056\n            ],\n            [\n              -0.09385121613740921,\n              -0.0356290377676487,\n              0.04569070041179657\n            ]\n          ],\n          [\n            [\n              0.22217205166816711,\n              0.07174063473939896,\n              -0.11545348912477493\n            ],\n            [\n              0.21276743710041046,\n              0.1291293352842331,\n              0.01896503008902073\n            ],\n            [\n              0.14763914048671722,\n              0.1861116588115692,\n              0.08590038865804672\n            ]\n          ],\n          [\n            [\n              0.026769159361720085,\n              -0.045213937759399414,\n              0.075718954205513\n            ],\n            [\n              -0.14487768709659576,\n              0.0035822070203721523,\n              0.14938460290431976\n            ],\n            [\n              -0.06984766572713852,\n              -0.07488036900758743,\n              0.10114625096321106\n            ]\n          ],\n          [\n            [\n              0.23332081735134125,\n              0.09064438939094543,\n              -0.036316052079200745\n            ],\n            [\n              -0.16097210347652435,\n              0.005021014250814915,\n              0.06244467943906784\n            ],\n            [\n              -0.03754465654492378,\n              0.045506302267313004,\n              -0.042660053819417953\n            ]\n          ],\n          [\n            [\n              -0.21556663513183594,\n              -0.026264134794473648,\n              0.0604860857129097\n            ],\n            [\n              0.2277263104915619,\n              0.19397714734077454,\n              0.0751928836107254\n            ],\n            [\n              0.03162621706724167,\n              -0.10699104517698288,\n              -0.004026363138109446\n            ]\n          ],\n          [\n            [\n              0.08232510834932327,\n              -0.13078849017620087,\n              0.03585424646735191\n            ],\n            [\n              0.21168948709964752,\n              0.07903169840574265,\n              0.05411357805132866\n            ],\n            [\n              0.16992908716201782,\n              0.0022068489342927933,\n              0.10664689540863037\n            ]\n          ],\n          [\n            [\n              0.08053668588399887,\n              -0.030569858849048615,\n              0.051013581454753876\n            ],\n            [\n              -0.1376090943813324,\n              -0.1279177963733673,\n              0.08727388828992844\n            ],\n            [\n              -0.17089048027992249,\n              0.055040109902620316,\n              0.09938357025384903\n            ]\n          ],\n          [\n            [\n              0.049769479781389236,\n              -0.10225320607423782,\n              -0.06922544538974762\n            ],\n            [\n              0.010851606726646423,\n              0.0875324010848999,\n              -0.11066267639398575\n            ],\n            [\n              0.1668866127729416,\n              0.014141902327537537,\n              0.038830194622278214\n            ]\n          ],\n          [\n            [\n              0.06571630388498306,\n              0.15494510531425476,\n              -0.06427671760320663\n            ],\n            [\n              0.11934246122837067,\n              0.10940946638584137,\n              0.2175624519586563\n            ],\n            [\n              0.33665385842323303,\n              0.08218508213758469,\n              0.2736110985279083\n            ]\n          ],\n          [\n            [\n              -0.0974932312965393,\n              -0.19408659636974335,\n              -0.12665219604969025\n            ],\n            [\n              -0.11315779387950897,\n              -0.21975912153720856,\n              -0.1136365607380867\n            ],\n            [\n              -0.05188465490937233,\n              0.02909829467535019,\n              -0.08100558072328568\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.09210537374019623,\n        \"weights\": [\n          [\n            [\n              -0.03278696909546852,\n              -0.017309516668319702,\n              0.08832841366529465\n            ],\n            [\n              0.10297974944114685,\n              0.13024617731571198,\n              -0.020145995542407036\n            ],\n            [\n              0.19648101925849915,\n              0.05248822271823883,\n              -0.07985596358776093\n            ]\n          ],\n          [\n            [\n              -0.054609447717666626,\n              0.2046888768672943,\n              0.06291133165359497\n            ],\n            [\n              -0.16105268895626068,\n              0.2436676025390625,\n              0.08801200240850449\n            ],\n            [\n              0.11915095895528793,\n              0.20166395604610443,\n              0.037480369210243225\n            ]\n          ],\n          [\n            [\n              0.1711929589509964,\n              -0.16010943055152893,\n              -0.12285618484020233\n            ],\n            [\n              -0.17299924790859222,\n              -0.07008962333202362,\n              0.14374801516532898\n            ],\n            [\n              0.09877115488052368,\n              -0.1352241039276123,\n              0.04064025729894638\n            ]\n          ],\n          [\n            [\n              0.08953512459993362,\n              0.16134575009346008,\n              0.19984757900238037\n            ],\n            [\n              -0.004337814636528492,\n              -0.11732278764247894,\n              0.0945245549082756\n            ],\n            [\n              0.20016124844551086,\n              0.20509259402751923,\n              -0.009458988904953003\n            ]\n          ],\n          [\n            [\n              0.011065728031098843,\n              0.11924783140420914,\n              0.2751632630825043\n            ],\n            [\n              -0.1809077262878418,\n              0.04392337054014206,\n              -0.10610227286815643\n            ],\n            [\n              0.11697299778461456,\n              -0.10463783890008926,\n              0.10434195399284363\n            ]\n          ],\n          [\n            [\n              0.13397638499736786,\n              0.23100727796554565,\n              0.2506504952907562\n            ],\n            [\n              0.21284300088882446,\n              -0.09303019195795059,\n              0.04000652953982353\n            ],\n            [\n              0.13550232350826263,\n              -0.05390007048845291,\n              0.15633025765419006\n            ]\n          ],\n          [\n            [\n              -0.035088714212179184,\n              0.011019790545105934,\n              -0.15191912651062012\n            ],\n            [\n              -0.0970035269856453,\n              -0.20510318875312805,\n              -0.2798207104206085\n            ],\n            [\n              0.22112753987312317,\n              0.173477441072464,\n              -0.07703046500682831\n            ]\n          ],\n          [\n            [\n              -0.1316671073436737,\n              0.11739280819892883,\n              -0.12437774240970612\n            ],\n            [\n              0.14049214124679565,\n              0.018660126253962517,\n              -0.008317839354276657\n            ],\n            [\n              0.10293585807085037,\n              0.12097896635532379,\n              -0.18470950424671173\n            ]\n          ],\n          [\n            [\n              0.07217487692832947,\n              0.17033891379833221,\n              -0.05641436204314232\n            ],\n            [\n              0.05039115250110626,\n              -0.09965449571609497,\n              -0.0016430896939709783\n            ],\n            [\n              0.12333089858293533,\n              0.07186176627874374,\n              -0.021624935790896416\n            ]\n          ],\n          [\n            [\n              0.05988701060414314,\n              -0.09962324053049088,\n              -0.047765620052814484\n            ],\n            [\n              0.004566616844385862,\n              0.14760108292102814,\n              -0.049695611000061035\n            ],\n            [\n              0.08817210793495178,\n              0.09744234383106232,\n              -0.1272374391555786\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.08263328671455383,\n        \"weights\": [\n          [\n            [\n              0.05567118898034096,\n              0.06958293914794922,\n              0.29363101720809937\n            ],\n            [\n              -0.15386274456977844,\n              -0.1892932951450348,\n              0.09584794193506241\n            ],\n            [\n              -0.16181093454360962,\n              -0.26432499289512634,\n              0.09839805960655212\n            ]\n          ],\n          [\n            [\n              -0.1616799533367157,\n              -0.011068828403949738,\n              0.09977106750011444\n            ],\n            [\n              0.0686490461230278,\n              -0.03881068900227547,\n              -0.05779111757874489\n            ],\n            [\n              0.12616251409053802,\n              -0.10615657269954681,\n              0.007902081124484539\n            ]\n          ],\n          [\n            [\n              -0.051640547811985016,\n              -0.06663563847541809,\n              0.05050617456436157\n            ],\n            [\n              -0.17704543471336365,\n              -0.07125069200992584,\n              0.07076115161180496\n            ],\n            [\n              -0.08993498235940933,\n              0.12576580047607422,\n              0.15912599861621857\n            ]\n          ],\n          [\n            [\n              -0.16754917800426483,\n              0.07261624932289124,\n              0.04180620238184929\n            ],\n            [\n              0.0661192536354065,\n              -0.04214955493807793,\n              0.034119412302970886\n            ],\n            [\n              -0.08485493063926697,\n              -0.17403626441955566,\n              -0.1516757309436798\n            ]\n          ],\n          [\n            [\n              0.15753023326396942,\n              0.14854496717453003,\n              0.1365014761686325\n            ],\n            [\n              0.07514838874340057,\n              -0.12848225235939026,\n              0.020275237038731575\n            ],\n            [\n              -0.07221581786870956,\n              0.0022716999519616365,\n              -0.19164001941680908\n            ]\n          ],\n          [\n            [\n              0.08286779373884201,\n              -0.013372530229389668,\n              -0.021720657125115395\n            ],\n            [\n              -0.21152161061763763,\n              0.026690226048231125,\n              0.06342209130525589\n            ],\n            [\n              0.21802379190921783,\n              0.08808977156877518,\n              0.0848800539970398\n            ]\n          ],\n          [\n            [\n              0.17877766489982605,\n              0.02883482351899147,\n              0.16922372579574585\n            ],\n            [\n              -0.09378396719694138,\n              -0.006063633132725954,\n              -0.09353656321763992\n            ],\n            [\n              -0.04188361391425133,\n              0.1893092840909958,\n              0.13593453168869019\n            ]\n          ],\n          [\n            [\n              0.019957734271883965,\n              0.17231403291225433,\n              0.11049658805131912\n            ],\n            [\n              -0.07806538790464401,\n              0.0027042622677981853,\n              0.11312343180179596\n            ],\n            [\n              -0.1269344538450241,\n              -0.0074524227529764175,\n              -0.025511115789413452\n            ]\n          ],\n          [\n            [\n              0.07176779955625534,\n              0.044990986585617065,\n              0.1393134891986847\n            ],\n            [\n              0.14584551751613617,\n              -0.11557573080062866,\n              -0.05252663046121597\n            ],\n            [\n              0.33578360080718994,\n              0.16844221949577332,\n              0.19836050271987915\n            ]\n          ],\n          [\n            [\n              0.20327317714691162,\n              0.08193209767341614,\n              0.09176541119813919\n            ],\n            [\n              0.022141672670841217,\n              0.2393805980682373,\n              0.24619236588478088\n            ],\n            [\n              0.2033611685037613,\n              0.2690250277519226,\n              0.07805047184228897\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.0020236384589225054,\n        \"weights\": [\n          [\n            [\n              0.10053439438343048,\n              -0.11898089200258255,\n              -0.02185264602303505\n            ],\n            [\n              0.07545827329158783,\n              -0.004810892511159182,\n              0.06351689249277115\n            ],\n            [\n              0.10038071125745773,\n              0.256062388420105,\n              0.14542889595031738\n            ]\n          ],\n          [\n            [\n              0.11477155238389969,\n              0.08614219725131989,\n              -0.10725229978561401\n            ],\n            [\n              -0.2381533682346344,\n              -0.18247275054454803,\n              0.08615956455469131\n            ],\n            [\n              0.11388798803091049,\n              -0.22101150453090668,\n              -0.06669963151216507\n            ]\n          ],\n          [\n            [\n              -0.04353608563542366,\n              0.1748303472995758,\n              0.00718834949657321\n            ],\n            [\n              -0.15852247178554535,\n              0.025131087750196457,\n              0.07791343331336975\n            ],\n            [\n              0.047718364745378494,\n              0.03452707454562187,\n              -0.13101045787334442\n            ]\n          ],\n          [\n            [\n              -0.13334953784942627,\n              -0.04534310847520828,\n              0.19090056419372559\n            ],\n            [\n              -0.04243757203221321,\n              0.13102532923221588,\n              -0.000876651203725487\n            ],\n            [\n              -0.11044615507125854,\n              -0.010847765021026134,\n              -0.28389787673950195\n            ]\n          ],\n          [\n            [\n              0.09492381662130356,\n              0.09689532965421677,\n              0.07702462375164032\n            ],\n            [\n              0.09333714842796326,\n              0.09936945885419846,\n              0.15126819908618927\n            ],\n            [\n              0.11038269847631454,\n              0.03863901272416115,\n              0.17338909208774567\n            ]\n          ],\n          [\n            [\n              -0.2164311558008194,\n              0.12255308777093887,\n              -0.22648969292640686\n            ],\n            [\n              -0.1568596065044403,\n              -0.14963537454605103,\n              -0.01591780036687851\n            ],\n            [\n              -0.07670484483242035,\n              -0.05227745696902275,\n              -0.10527215898036957\n            ]\n          ],\n          [\n            [\n              0.25787675380706787,\n              0.2774695158004761,\n              0.2681882381439209\n            ],\n            [\n              -0.21463291347026825,\n              -0.17441409826278687,\n              0.17279638350009918\n            ],\n            [\n              0.07751452922821045,\n              0.1427261233329773,\n              0.0029161006677895784\n            ]\n          ],\n          [\n            [\n              -0.023808851838111877,\n              -0.1338464319705963,\n              -0.16531069576740265\n            ],\n            [\n              0.09184430539608002,\n              -0.10058215260505676,\n              0.10079031437635422\n            ],\n            [\n              -0.09520717710256577,\n              -0.12539193034172058,\n              -0.0013041305355727673\n            ]\n          ],\n          [\n            [\n              -0.202018141746521,\n              -0.13908347487449646,\n              0.09860333055257797\n            ],\n            [\n              -0.012914903461933136,\n              0.030028915032744408,\n              -0.05219478905200958\n            ],\n            [\n              -0.06688292324542999,\n              -0.1209234818816185,\n              -0.02122826874256134\n            ]\n          ],\n          [\n            [\n              -0.03583580628037453,\n              0.019045300781726837,\n              -0.09886214882135391\n            ],\n            [\n              0.15042924880981445,\n              0.16495978832244873,\n              -0.050434794276952744\n            ],\n            [\n              0.056220296770334244,\n              -0.06817059218883514,\n              -0.040640488266944885\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.017033971846103668,\n        \"weights\": [\n          [\n            [\n              -0.20809075236320496,\n              -0.13181592524051666,\n              -0.12393893301486969\n            ],\n            [\n              -0.06774646043777466,\n              0.03121250309050083,\n              -0.07943106442689896\n            ],\n            [\n              -0.10797509551048279,\n              0.0999375507235527,\n              0.1254819631576538\n            ]\n          ],\n          [\n            [\n              0.10122480243444443,\n              0.09813398867845535,\n              0.052496425807476044\n            ],\n            [\n              0.16393233835697174,\n              -0.09034610539674759,\n              0.05845034122467041\n            ],\n            [\n              -0.02693263627588749,\n              -0.2084280252456665,\n              0.006234470289200544\n            ]\n          ],\n          [\n            [\n              0.18094633519649506,\n              -0.05955606698989868,\n              0.11186791211366653\n            ],\n            [\n              0.08737774938344955,\n              0.17498096823692322,\n              0.0400092788040638\n            ],\n            [\n              -0.17215411365032196,\n              -0.1591709554195404,\n              0.17329218983650208\n            ]\n          ],\n          [\n            [\n              0.14523540437221527,\n              0.21107657253742218,\n              0.27582937479019165\n            ],\n            [\n              -0.035530272871255875,\n              0.11021815985441208,\n              -0.11280883103609085\n            ],\n            [\n              0.12257345765829086,\n              -0.2404092252254486,\n              -0.09588366001844406\n            ]\n          ],\n          [\n            [\n              -0.17064805328845978,\n              -0.21269570291042328,\n              -0.09245115518569946\n            ],\n            [\n              -0.002536489861086011,\n              -0.04389023408293724,\n              0.11974436789751053\n            ],\n            [\n              0.01879681646823883,\n              -0.05463852360844612,\n              0.0689098984003067\n            ]\n          ],\n          [\n            [\n              -0.09446363896131516,\n              -0.2626676857471466,\n              0.037944354116916656\n            ],\n            [\n              -0.028313560411334038,\n              0.08721524477005005,\n              0.12055739760398865\n            ],\n            [\n              0.07355979830026627,\n              0.18201249837875366,\n              0.10116560012102127\n            ]\n          ],\n          [\n            [\n              0.3293829560279846,\n              0.32655951380729675,\n              0.24378590285778046\n            ],\n            [\n              -0.07051360607147217,\n              -0.10129305720329285,\n              -0.0892932265996933\n            ],\n            [\n              -0.23557034134864807,\n              -0.31763985753059387,\n              -0.3160083293914795\n            ]\n          ],\n          [\n            [\n              -0.03236345574259758,\n              -0.10293465107679367,\n              -0.1326933354139328\n            ],\n            [\n              -0.03448130562901497,\n              -0.035801827907562256,\n              0.14836330711841583\n            ],\n            [\n              -0.06528409570455551,\n              0.012504453770816326,\n              0.009019790217280388\n            ]\n          ],\n          [\n            [\n              0.07146022468805313,\n              0.17739997804164886,\n              -0.14866097271442413\n            ],\n            [\n              -0.01479280460625887,\n              0.001622064271941781,\n              0.1060650423169136\n            ],\n            [\n              -0.0778941810131073,\n              -0.12459131330251694,\n              0.1424666792154312\n            ]\n          ],\n          [\n            [\n              -0.15287674963474274,\n              -0.057680219411849976,\n              -0.11161118000745773\n            ],\n            [\n              0.020092297345399857,\n              0.056244343519210815,\n              0.0034972724970430136\n            ],\n            [\n              -0.01507959607988596,\n              -0.10725513100624084,\n              -0.005411537829786539\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.0487380176782608,\n        \"weights\": [\n          [\n            [\n              0.013004053384065628,\n              0.1438901126384735,\n              0.2067205160856247\n            ],\n            [\n              0.2778065502643585,\n              0.035558704286813736,\n              0.15905006229877472\n            ],\n            [\n              0.1556456834077835,\n              -0.055424440652132034,\n              -0.196225106716156\n            ]\n          ],\n          [\n            [\n              -0.06706154346466064,\n              -0.06031229719519615,\n              0.1522347778081894\n            ],\n            [\n              -0.1272343546152115,\n              -0.024298088625073433,\n              -0.17963898181915283\n            ],\n            [\n              -0.296866238117218,\n              0.05329625681042671,\n              -0.08038515597581863\n            ]\n          ],\n          [\n            [\n              0.08246057480573654,\n              0.11964347958564758,\n              -0.15256546437740326\n            ],\n            [\n              0.04718828201293945,\n              -0.036873333156108856,\n              0.11580844223499298\n            ],\n            [\n              -0.02689993940293789,\n              0.14799445867538452,\n              0.0425935722887516\n            ]\n          ],\n          [\n            [\n              -0.2321205884218216,\n              0.07651403546333313,\n              0.06711816787719727\n            ],\n            [\n              -0.24553300440311432,\n              -0.19699813425540924,\n              -0.10530014336109161\n            ],\n            [\n              -0.3381549119949341,\n              -0.23991091549396515,\n              0.08259432017803192\n            ]\n          ],\n          [\n            [\n              0.1878867745399475,\n              0.0027300918009132147,\n              0.13993220031261444\n            ],\n            [\n              -0.006729230750352144,\n              0.04196777939796448,\n              0.12541988492012024\n            ],\n            [\n              -0.18064618110656738,\n              -0.02939745783805847,\n              -0.12110479176044464\n            ]\n          ],\n          [\n            [\n              -0.02033473551273346,\n              0.1171392872929573,\n              0.1683845967054367\n            ],\n            [\n              0.04123875871300697,\n              0.12270105630159378,\n              0.15763184428215027\n            ],\n            [\n              0.010796451941132545,\n              0.1692696511745453,\n              -0.012512930668890476\n            ]\n          ],\n          [\n            [\n              0.01538323238492012,\n              0.14960414171218872,\n              0.2853028476238251\n            ],\n            [\n              -0.029109112918376923,\n              -0.11643902212381363,\n              -0.055375710129737854\n            ],\n            [\n              -0.004981658887118101,\n              -0.09975367784500122,\n              0.01719915121793747\n            ]\n          ],\n          [\n            [\n              -0.08597826957702637,\n              -0.1395689994096756,\n              -0.0871158018708229\n            ],\n            [\n              0.07820675522089005,\n              0.06476256996393204,\n              0.08447449654340744\n            ],\n            [\n              0.04280698299407959,\n              0.13454754650592804,\n              -0.14602670073509216\n            ]\n          ],\n          [\n            [\n              -0.2120448648929596,\n              -0.10349173098802567,\n              -0.01867772452533245\n            ],\n            [\n              -0.13450013101100922,\n              0.03246336057782173,\n              0.008800385519862175\n            ],\n            [\n              -0.06651842594146729,\n              0.0027631367556750774,\n              0.1084757074713707\n            ]\n          ],\n          [\n            [\n              0.024234263226389885,\n              0.035659898072481155,\n              0.07447188347578049\n            ],\n            [\n              0.14663217961788177,\n              0.12224237620830536,\n              0.11024618148803711\n            ],\n            [\n              -0.017633860930800438,\n              0.11866504698991776,\n              0.031190453097224236\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.06205393746495247,\n        \"weights\": [\n          [\n            [\n              0.10403458029031754,\n              -0.04516236484050751,\n              0.007944069802761078\n            ],\n            [\n              0.027522817254066467,\n              -0.06080198660492897,\n              0.17122933268547058\n            ],\n            [\n              0.031839076429605484,\n              -0.13542407751083374,\n              0.147073432803154\n            ]\n          ],\n          [\n            [\n              -0.07265862077474594,\n              0.07584702968597412,\n              0.0826362743973732\n            ],\n            [\n              -0.12504354119300842,\n              0.10610494017601013,\n              -0.05252160131931305\n            ],\n            [\n              -0.1802300661802292,\n              0.13104572892189026,\n              0.1587398797273636\n            ]\n          ],\n          [\n            [\n              -0.01467291358858347,\n              -0.1736292839050293,\n              -0.06778385490179062\n            ],\n            [\n              -0.08540039509534836,\n              0.1718093454837799,\n              0.037517379969358444\n            ],\n            [\n              -0.11343583464622498,\n              -0.09185007214546204,\n              0.04145399108529091\n            ]\n          ],\n          [\n            [\n              -0.0252825990319252,\n              0.13854925334453583,\n              0.1342930644750595\n            ],\n            [\n              -0.18445101380348206,\n              -0.06430703401565552,\n              -0.1338326334953308\n            ],\n            [\n              0.044733159244060516,\n              0.1775427758693695,\n              0.18956460058689117\n            ]\n          ],\n          [\n            [\n              0.05329892411828041,\n              0.14391566812992096,\n              0.17773744463920593\n            ],\n            [\n              -0.13861612975597382,\n              0.05440625175833702,\n              0.18248164653778076\n            ],\n            [\n              0.054199911653995514,\n              0.10067229717969894,\n              0.03818938881158829\n            ]\n          ],\n          [\n            [\n              0.15098167955875397,\n              -0.10688918083906174,\n              0.10139582306146622\n            ],\n            [\n              -0.030306799337267876,\n              -0.08467961102724075,\n              -0.024463560432195663\n            ],\n            [\n              -0.004675365053117275,\n              0.06831468641757965,\n              0.10519678145647049\n            ]\n          ],\n          [\n            [\n              0.005631213542073965,\n              0.024092094972729683,\n              -0.2629964053630829\n            ],\n            [\n              0.02316136285662651,\n              -0.1372058093547821,\n              -0.0011137331603094935\n            ],\n            [\n              0.08635002374649048,\n              0.12336578965187073,\n              -0.20182885229587555\n            ]\n          ],\n          [\n            [\n              -0.12374411523342133,\n              0.06976250559091568,\n              0.0033627478405833244\n            ],\n            [\n              -0.06103638559579849,\n              -0.01258084923028946,\n              -0.16220147907733917\n            ],\n            [\n              0.036776743829250336,\n              -0.043156035244464874,\n              -0.002188880927860737\n            ]\n          ],\n          [\n            [\n              0.008888236247003078,\n              -0.20282362401485443,\n              -0.00037311940104700625\n            ],\n            [\n              -0.06386574357748032,\n              0.1379087269306183,\n              -0.1674935668706894\n            ],\n            [\n              -0.06518663465976715,\n              0.11533026397228241,\n              0.04993900656700134\n            ]\n          ],\n          [\n            [\n              0.13230127096176147,\n              0.14546886086463928,\n              0.025895265862345695\n            ],\n            [\n              0.2527938485145569,\n              0.23856063187122345,\n              0.2568085789680481\n            ],\n            [\n              -0.045192964375019073,\n              0.02460247091948986,\n              -0.09280352294445038\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.0025097853504121304,\n        \"weights\": [\n          [\n            [\n              -0.22498294711112976,\n              0.09176423400640488,\n              -0.2085607796907425\n            ],\n            [\n              -0.1700950711965561,\n              -0.1335902363061905,\n              -0.10471974313259125\n            ],\n            [\n              -0.01865343004465103,\n              0.05609619617462158,\n              0.061930976808071136\n            ]\n          ],\n          [\n            [\n              0.1914878934621811,\n              0.1001431941986084,\n              -0.1320115625858307\n            ],\n            [\n              -0.03730002045631409,\n              -0.023550791665911674,\n              0.1155635267496109\n            ],\n            [\n              -0.11461998522281647,\n              -0.16998285055160522,\n              -0.0012773670023307204\n            ]\n          ],\n          [\n            [\n              -0.17897191643714905,\n              0.17310252785682678,\n              -0.08422792702913284\n            ],\n            [\n              -0.07960566878318787,\n              0.0966135635972023,\n              -0.017904765903949738\n            ],\n            [\n              0.09933783113956451,\n              -0.024894703179597855,\n              -0.07360128313302994\n            ]\n          ],\n          [\n            [\n              0.15306206047534943,\n              0.038106270134449005,\n              -0.01117273885756731\n            ],\n            [\n              -0.04237715154886246,\n              -0.14435125887393951,\n              0.13693749904632568\n            ],\n            [\n              0.0670156180858612,\n              0.08106747269630432,\n              0.17583781480789185\n            ]\n          ],\n          [\n            [\n              -0.0670420229434967,\n              -0.08985667675733566,\n              -0.21924220025539398\n            ],\n            [\n              -0.19975325465202332,\n              -0.02605069987475872,\n              -0.15807080268859863\n            ],\n            [\n              -0.26512908935546875,\n              -0.11320961266756058,\n              -0.020481813699007034\n            ]\n          ],\n          [\n            [\n              -0.126532182097435,\n              0.0574677474796772,\n              0.13956378400325775\n            ],\n            [\n              -0.2515821158885956,\n              0.1006961390376091,\n              -0.014027233235538006\n            ],\n            [\n              -0.22177976369857788,\n              0.057052042335271835,\n              -0.15844808518886566\n            ]\n          ],\n          [\n            [\n              0.14358428120613098,\n              0.18655778467655182,\n              -0.06682707369327545\n            ],\n            [\n              0.09867151081562042,\n              0.20803463459014893,\n              -0.07671579718589783\n            ],\n            [\n              0.26640254259109497,\n              0.18211060762405396,\n              0.21034330129623413\n            ]\n          ],\n          [\n            [\n              0.08930578082799911,\n              0.13615724444389343,\n              -0.13849537074565887\n            ],\n            [\n              -0.1625012904405594,\n              0.11744222044944763,\n              -0.1308753341436386\n            ],\n            [\n              0.17427915334701538,\n              -0.11424800753593445,\n              -0.0840780958533287\n            ]\n          ],\n          [\n            [\n              -0.103568896651268,\n              -0.03933466225862503,\n              0.1908055990934372\n            ],\n            [\n              -0.040733352303504944,\n              0.1285048872232437,\n              -0.13059766590595245\n            ],\n            [\n              -0.12315960228443146,\n              -0.15618792176246643,\n              0.14209264516830444\n            ]\n          ],\n          [\n            [\n              0.05338658019900322,\n              -0.06080073490738869,\n              -0.05633510276675224\n            ],\n            [\n              -0.17862318456172943,\n              -0.11959339678287506,\n              0.04116475209593773\n            ],\n            [\n              0.16246318817138672,\n              -0.030260881409049034,\n              -0.16533328592777252\n            ]\n          ]\n        ]\n      }\n    ]\n  },\n  {\n    \"name\": \"relu_3_1\",\n    \"input_shape\": [\n      11,\n      11,\n      10\n    ],\n    \"output_shape\": [\n      11,\n      11,\n      10\n    ],\n    \"num_neurons\": 10\n  },\n  {\n    \"name\": \"conv_3_2\",\n    \"input_shape\": [\n      11,\n      11,\n      10\n    ],\n    \"output_shape\": [\n      9,\n      9,\n      10\n    ],\n    \"num_neurons\": 10,\n    \"weights\": [\n      {\n        \"bias\": -0.10277897119522095,\n        \"weights\": [\n          [\n            [\n              0.09267803281545639,\n              -0.14078417420387268,\n              -0.07605091482400894\n            ],\n            [\n              0.14614148437976837,\n              0.08831778913736343,\n              -0.12347656488418579\n            ],\n            [\n              -0.13764387369155884,\n              0.17568658292293549,\n              0.0465690940618515\n            ]\n          ],\n          [\n            [\n              -0.07284963130950928,\n              0.10043230652809143,\n              -0.08929219841957092\n            ],\n            [\n              0.14805518090724945,\n              0.0652356818318367,\n              0.10943868011236191\n            ],\n            [\n              0.018983790650963783,\n              0.183913916349411,\n              0.1163867861032486\n            ]\n          ],\n          [\n            [\n              0.08620022237300873,\n              0.13983801007270813,\n              0.10129179805517197\n            ],\n            [\n              0.0601688027381897,\n              0.01619243435561657,\n              -0.1599501520395279\n            ],\n            [\n              0.0444798469543457,\n              -0.15351161360740662,\n              -0.0690305083990097\n            ]\n          ],\n          [\n            [\n              -0.03898674622178078,\n              -0.020412325859069824,\n              -0.165174663066864\n            ],\n            [\n              0.2007649689912796,\n              0.08091966062784195,\n              0.08275861293077469\n            ],\n            [\n              -0.3116636276245117,\n              -0.040311604738235474,\n              -0.25993087887763977\n            ]\n          ],\n          [\n            [\n              0.24938461184501648,\n              0.004057968035340309,\n              0.22963069379329681\n            ],\n            [\n              0.15706834197044373,\n              0.1954222023487091,\n              -0.07087729126214981\n            ],\n            [\n              -0.20342469215393066,\n              0.03749361261725426,\n              -0.09815824776887894\n            ]\n          ],\n          [\n            [\n              0.29192686080932617,\n              -0.058532387018203735,\n              0.03931589797139168\n            ],\n            [\n              -0.21860815584659576,\n              0.15099529922008514,\n              -0.15310262143611908\n            ],\n            [\n              -0.1616145670413971,\n              -0.2389601618051529,\n              -0.04281521961092949\n            ]\n          ],\n          [\n            [\n              0.12723246216773987,\n              0.1764863282442093,\n              -0.13758128881454468\n            ],\n            [\n              0.07308806478977203,\n              -0.08522924035787582,\n              0.08005966991186142\n            ],\n            [\n              0.05577504634857178,\n              0.24023334681987762,\n              0.21399162709712982\n            ]\n          ],\n          [\n            [\n              0.2390051931142807,\n              0.0032715487759560347,\n              0.030026260763406754\n            ],\n            [\n              0.2931196987628937,\n              0.23040874302387238,\n              0.02019393816590309\n            ],\n            [\n              0.26748859882354736,\n              0.043368130922317505,\n              0.18693090975284576\n            ]\n          ],\n          [\n            [\n              0.11633669584989548,\n              -0.060911625623703,\n              -0.1735755205154419\n            ],\n            [\n              0.14914847910404205,\n              0.04549966752529144,\n              0.10831913352012634\n            ],\n            [\n              -0.16951464116573334,\n              -0.19501762092113495,\n              -0.19189445674419403\n            ]\n          ],\n          [\n            [\n              -0.04856371879577637,\n              -0.13896895945072174,\n              -0.15048551559448242\n            ],\n            [\n              0.04664619266986847,\n              0.20016220211982727,\n              0.20906642079353333\n            ],\n            [\n              0.15420468151569366,\n              0.14150410890579224,\n              0.1315082162618637\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.19701802730560303,\n        \"weights\": [\n          [\n            [\n              -0.143484964966774,\n              0.04895861819386482,\n              -0.1301470696926117\n            ],\n            [\n              0.009600421413779259,\n              0.13975095748901367,\n              0.03641190379858017\n            ],\n            [\n              0.1698870062828064,\n              0.15469446778297424,\n              -0.012752104550600052\n            ]\n          ],\n          [\n            [\n              -0.1372455209493637,\n              0.11356484889984131,\n              0.14451460540294647\n            ],\n            [\n              -0.07270649820566177,\n              0.16737598180770874,\n              -0.1741221398115158\n            ],\n            [\n              0.0332883819937706,\n              -0.08568745106458664,\n              0.16088034212589264\n            ]\n          ],\n          [\n            [\n              -0.008876948617398739,\n              -0.0006492537213489413,\n              -0.2033199816942215\n            ],\n            [\n              0.14547814428806305,\n              0.09163498133420944,\n              0.005591763183474541\n            ],\n            [\n              -0.20547902584075928,\n              -0.15030084550380707,\n              0.17562271654605865\n            ]\n          ],\n          [\n            [\n              0.26435554027557373,\n              -0.02550823986530304,\n              -0.0867445170879364\n            ],\n            [\n              -0.07698290050029755,\n              0.19865825772285461,\n              0.1312965452671051\n            ],\n            [\n              -0.1653180867433548,\n              0.003725706599652767,\n              -0.08473141491413116\n            ]\n          ],\n          [\n            [\n              0.2102108597755432,\n              0.16618561744689941,\n              0.21938714385032654\n            ],\n            [\n              -0.07313138246536255,\n              0.03749938681721687,\n              0.036075253039598465\n            ],\n            [\n              -0.08825419843196869,\n              -0.06802782416343689,\n              0.27731508016586304\n            ]\n          ],\n          [\n            [\n              0.07379036396741867,\n              -0.048663150519132614,\n              0.06394065916538239\n            ],\n            [\n              0.1504632532596588,\n              -0.15331095457077026,\n              0.039276234805583954\n            ],\n            [\n              0.029437465593218803,\n              -0.1090429499745369,\n              0.1631459891796112\n            ]\n          ],\n          [\n            [\n              -0.07403174042701721,\n              -0.20716427266597748,\n              -0.2277587205171585\n            ],\n            [\n              -0.07353825122117996,\n              0.10423948615789413,\n              -0.04673963785171509\n            ],\n            [\n              -0.12316188961267471,\n              0.04170292615890503,\n              -0.09625712037086487\n            ]\n          ],\n          [\n            [\n              0.15695101022720337,\n              0.03164668008685112,\n              -0.07265935093164444\n            ],\n            [\n              0.038059987127780914,\n              0.19235441088676453,\n              -0.17337316274642944\n            ],\n            [\n              0.10923129320144653,\n              0.12453075498342514,\n              0.10390300303697586\n            ]\n          ],\n          [\n            [\n              0.24312619864940643,\n              -0.07914382964372635,\n              -0.07341685146093369\n            ],\n            [\n              -0.002172311767935753,\n              0.013023238629102707,\n              -0.01515197940170765\n            ],\n            [\n              0.16983307898044586,\n              0.15501026809215546,\n              -0.019847286865115166\n            ]\n          ],\n          [\n            [\n              -0.0009645269601605833,\n              -0.13425539433956146,\n              -0.1456792801618576\n            ],\n            [\n              -0.043353136628866196,\n              -0.08142224699258804,\n              -0.19224217534065247\n            ],\n            [\n              0.039527688175439835,\n              -0.03303470090031624,\n              -0.25063997507095337\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.03129541128873825,\n        \"weights\": [\n          [\n            [\n              -0.04401397705078125,\n              -0.1026557981967926,\n              -0.15873579680919647\n            ],\n            [\n              0.14723625779151917,\n              -0.02761595882475376,\n              -0.11629337817430496\n            ],\n            [\n              -0.10660669207572937,\n              0.06266634911298752,\n              -0.12126072496175766\n            ]\n          ],\n          [\n            [\n              -0.051345374435186386,\n              -0.12373384833335876,\n              0.01398497074842453\n            ],\n            [\n              0.13865694403648376,\n              -0.16774369776248932,\n              -0.1940239816904068\n            ],\n            [\n              -0.11738843470811844,\n              -0.16516463458538055,\n              -0.032126326113939285\n            ]\n          ],\n          [\n            [\n              0.19980014860630035,\n              -0.012881172820925713,\n              0.02587236277759075\n            ],\n            [\n              -0.0648706927895546,\n              -0.03064793534576893,\n              0.17219239473342896\n            ],\n            [\n              0.2922405004501343,\n              0.11596475541591644,\n              -0.05196569487452507\n            ]\n          ],\n          [\n            [\n              -0.08409712463617325,\n              -0.2780201733112335,\n              -0.1252819001674652\n            ],\n            [\n              -0.11839822679758072,\n              0.07058580219745636,\n              -0.10253388434648514\n            ],\n            [\n              0.0600438117980957,\n              0.3053727149963379,\n              0.28478389978408813\n            ]\n          ],\n          [\n            [\n              0.1946101039648056,\n              0.03956221789121628,\n              -0.13357213139533997\n            ],\n            [\n              -0.17230957746505737,\n              -0.21701259911060333,\n              0.027316274121403694\n            ],\n            [\n              -0.3401566445827484,\n              -0.11001702398061752,\n              -0.15215662121772766\n            ]\n          ],\n          [\n            [\n              0.16042929887771606,\n              0.1576676219701767,\n              -0.04330100119113922\n            ],\n            [\n              -0.13132819533348083,\n              0.12381011247634888,\n              0.19731402397155762\n            ],\n            [\n              0.0245810654014349,\n              0.09865681827068329,\n              -0.15396825969219208\n            ]\n          ],\n          [\n            [\n              0.004712874535471201,\n              -0.06420975923538208,\n              0.007638312876224518\n            ],\n            [\n              0.10823718458414078,\n              0.044939979910850525,\n              -0.12299510091543198\n            ],\n            [\n              -0.07070112228393555,\n              0.19507981836795807,\n              0.12318171560764313\n            ]\n          ],\n          [\n            [\n              -0.1818331927061081,\n              0.15946221351623535,\n              0.04939134046435356\n            ],\n            [\n              -0.13904240727424622,\n              0.049535155296325684,\n              -0.13799390196800232\n            ],\n            [\n              0.006305249873548746,\n              0.0588289350271225,\n              0.12431934475898743\n            ]\n          ],\n          [\n            [\n              -0.07872401922941208,\n              0.047131527215242386,\n              -0.17851923406124115\n            ],\n            [\n              0.10127964615821838,\n              0.09443499892950058,\n              0.007255954202264547\n            ],\n            [\n              0.00549765769392252,\n              0.1199440211057663,\n              0.1588352471590042\n            ]\n          ],\n          [\n            [\n              0.3437877297401428,\n              0.07055503875017166,\n              0.010348151437938213\n            ],\n            [\n              0.10796968638896942,\n              -0.13699431717395782,\n              -0.24103495478630066\n            ],\n            [\n              -0.12173858284950256,\n              -0.11904724687337875,\n              -0.11231179535388947\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.17929309606552124,\n        \"weights\": [\n          [\n            [\n              0.0019429587991908193,\n              -0.09248022735118866,\n              0.007708424236625433\n            ],\n            [\n              0.1692398488521576,\n              0.16336318850517273,\n              0.11352494359016418\n            ],\n            [\n              -0.040179528295993805,\n              0.07438023388385773,\n              0.13233979046344757\n            ]\n          ],\n          [\n            [\n              -0.17890037596225739,\n              -0.08385209739208221,\n              -0.15496759116649628\n            ],\n            [\n              0.10870101302862167,\n              -0.04719959944486618,\n              0.06616398692131042\n            ],\n            [\n              0.1379544734954834,\n              0.08445236086845398,\n              0.14320217072963715\n            ]\n          ],\n          [\n            [\n              0.03291373327374458,\n              0.039768971502780914,\n              -0.10941271483898163\n            ],\n            [\n              0.09658505767583847,\n              -0.25947511196136475,\n              0.1890028566122055\n            ],\n            [\n              -0.03597365692257881,\n              0.02223622426390648,\n              -0.03105202130973339\n            ]\n          ],\n          [\n            [\n              -0.09644536674022675,\n              0.13919652998447418,\n              0.18691404163837433\n            ],\n            [\n              -0.06257228553295135,\n              -0.12506872415542603,\n              0.2528545558452606\n            ],\n            [\n              -0.273127943277359,\n              -0.21213173866271973,\n              0.23635731637477875\n            ]\n          ],\n          [\n            [\n              0.19669513404369354,\n              0.06016920506954193,\n              0.21170251071453094\n            ],\n            [\n              -0.126479834318161,\n              0.038581881672143936,\n              -0.02009897492825985\n            ],\n            [\n              0.17705003917217255,\n              0.2454189658164978,\n              0.12400642782449722\n            ]\n          ],\n          [\n            [\n              0.06749136745929718,\n              0.07600580900907516,\n              0.22043104469776154\n            ],\n            [\n              -0.10949134826660156,\n              -0.04233156889677048,\n              0.029990488663315773\n            ],\n            [\n              -0.3844650685787201,\n              -0.23563167452812195,\n              0.05225079134106636\n            ]\n          ],\n          [\n            [\n              0.13738933205604553,\n              0.04688725993037224,\n              0.015981517732143402\n            ],\n            [\n              -0.06326860189437866,\n              0.16815994679927826,\n              0.05785881355404854\n            ],\n            [\n              0.14164455235004425,\n              -0.032385967671871185,\n              0.06069803610444069\n            ]\n          ],\n          [\n            [\n              -0.0930677205324173,\n              -0.2240021824836731,\n              -0.08354826271533966\n            ],\n            [\n              -0.28700146079063416,\n              -0.16311541199684143,\n              -0.08450518548488617\n            ],\n            [\n              -0.32040783762931824,\n              0.06595692038536072,\n              -0.042691562324762344\n            ]\n          ],\n          [\n            [\n              -0.12971054017543793,\n              -0.06245476379990578,\n              0.27206432819366455\n            ],\n            [\n              -0.11758383363485336,\n              -0.16736234724521637,\n              0.19137832522392273\n            ],\n            [\n              0.0216483436524868,\n              -0.06711260974407196,\n              0.13713283836841583\n            ]\n          ],\n          [\n            [\n              -0.07027512788772583,\n              0.08385137468576431,\n              -0.15525342524051666\n            ],\n            [\n              -0.018946722149848938,\n              0.13567037880420685,\n              0.0418560765683651\n            ],\n            [\n              -0.03601307421922684,\n              -0.015821611508727074,\n              -0.08597856014966965\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.03919683396816254,\n        \"weights\": [\n          [\n            [\n              -0.12174763530492783,\n              0.13658182322978973,\n              -0.1651463508605957\n            ],\n            [\n              -0.028811035677790642,\n              -0.015335196629166603,\n              -0.02954046055674553\n            ],\n            [\n              0.11986204236745834,\n              -0.15042239427566528,\n              0.0074767074547708035\n            ]\n          ],\n          [\n            [\n              -0.1640665978193283,\n              -0.04325765371322632,\n              -0.05339731648564339\n            ],\n            [\n              -0.016034750267863274,\n              -0.022160634398460388,\n              -0.13991817831993103\n            ],\n            [\n              0.025412749499082565,\n              -0.16299155354499817,\n              0.010081378743052483\n            ]\n          ],\n          [\n            [\n              -0.20143799483776093,\n              -0.11384277045726776,\n              -0.10117499530315399\n            ],\n            [\n              0.06904461979866028,\n              0.050809185951948166,\n              0.08019406348466873\n            ],\n            [\n              0.22358885407447815,\n              -0.010466795414686203,\n              0.21184107661247253\n            ]\n          ],\n          [\n            [\n              -0.10086043179035187,\n              -0.15338215231895447,\n              -0.10359030216932297\n            ],\n            [\n              -0.061026688665151596,\n              -0.09536128491163254,\n              0.11404836177825928\n            ],\n            [\n              -0.019127026200294495,\n              0.14049454033374786,\n              -0.12202276289463043\n            ]\n          ],\n          [\n            [\n              -0.07664071768522263,\n              0.015995649620890617,\n              0.15433888137340546\n            ],\n            [\n              0.06485911458730698,\n              0.09001573175191879,\n              0.06948885321617126\n            ],\n            [\n              0.04386594146490097,\n              0.038411740213632584,\n              0.11713247001171112\n            ]\n          ],\n          [\n            [\n              0.09215138107538223,\n              0.12106510251760483,\n              0.23122583329677582\n            ],\n            [\n              0.14584136009216309,\n              0.16980870068073273,\n              0.16628189384937286\n            ],\n            [\n              -0.04187764227390289,\n              -0.10224214196205139,\n              0.019382622092962265\n            ]\n          ],\n          [\n            [\n              -0.05033596232533455,\n              0.12056105583906174,\n              0.22138570249080658\n            ],\n            [\n              0.10856883227825165,\n              0.1487428843975067,\n              0.0575917586684227\n            ],\n            [\n              -0.00046111567644402385,\n              0.042668137699365616,\n              0.05538313090801239\n            ]\n          ],\n          [\n            [\n              0.13978596031665802,\n              0.02549193613231182,\n              0.1446741670370102\n            ],\n            [\n              0.08918158710002899,\n              -0.043298862874507904,\n              0.03623894974589348\n            ],\n            [\n              0.17429907619953156,\n              0.03104049526154995,\n              -0.11564658582210541\n            ]\n          ],\n          [\n            [\n              0.16242437064647675,\n              -0.05185018479824066,\n              -0.0121151739731431\n            ],\n            [\n              -0.10923708975315094,\n              -0.1006595641374588,\n              -0.08410155028104782\n            ],\n            [\n              0.042500089854002,\n              -0.17756427824497223,\n              -0.15757030248641968\n            ]\n          ],\n          [\n            [\n              0.1477101892232895,\n              0.13517220318317413,\n              0.16390906274318695\n            ],\n            [\n              -0.09088374674320221,\n              -0.03356750309467316,\n              -0.11755392700433731\n            ],\n            [\n              0.029441041871905327,\n              0.08585233241319656,\n              0.0896700844168663\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.01446094922721386,\n        \"weights\": [\n          [\n            [\n              -0.16259777545928955,\n              -0.07919269800186157,\n              -0.10109077394008636\n            ],\n            [\n              0.07295500487089157,\n              -0.13824179768562317,\n              -0.055377405136823654\n            ],\n            [\n              0.043624382466077805,\n              -0.1642560511827469,\n              0.035963453352451324\n            ]\n          ],\n          [\n            [\n              0.12694010138511658,\n              0.1857878863811493,\n              0.02139131911098957\n            ],\n            [\n              -0.1294437199831009,\n              -0.1224006712436676,\n              0.04927557706832886\n            ],\n            [\n              0.1591484695672989,\n              0.007865636609494686,\n              -0.04981435835361481\n            ]\n          ],\n          [\n            [\n              0.025919122621417046,\n              -0.12693646550178528,\n              0.1762840300798416\n            ],\n            [\n              0.13712717592716217,\n              -0.07527372986078262,\n              0.13072159886360168\n            ],\n            [\n              0.04369989410042763,\n              -0.15994201600551605,\n              -0.07772780954837799\n            ]\n          ],\n          [\n            [\n              0.08389417082071304,\n              0.02161312662065029,\n              0.11244728416204453\n            ],\n            [\n              0.04562941938638687,\n              -0.1850823312997818,\n              -0.19985713064670563\n            ],\n            [\n              0.013847727328538895,\n              0.07941315323114395,\n              -0.07868022471666336\n            ]\n          ],\n          [\n            [\n              -0.1346278041601181,\n              -0.09600866585969925,\n              -0.24615386128425598\n            ],\n            [\n              -0.13599228858947754,\n              -0.027469327673316002,\n              -0.13686475157737732\n            ],\n            [\n              0.11155768483877182,\n              0.20709258317947388,\n              0.055043429136276245\n            ]\n          ],\n          [\n            [\n              -0.09350954741239548,\n              -0.2341221421957016,\n              -0.06315312534570694\n            ],\n            [\n              0.15690560638904572,\n              0.05876573547720909,\n              0.2008129358291626\n            ],\n            [\n              0.09133657813072205,\n              0.07357358932495117,\n              0.04054557904601097\n            ]\n          ],\n          [\n            [\n              0.02289535477757454,\n              0.05094471201300621,\n              0.022777413949370384\n            ],\n            [\n              0.1911495476961136,\n              0.1095840260386467,\n              0.1875132918357849\n            ],\n            [\n              0.10030882805585861,\n              0.0299226064234972,\n              0.2583174705505371\n            ]\n          ],\n          [\n            [\n              -0.04467569664120674,\n              -0.15514443814754486,\n              0.012017147615551949\n            ],\n            [\n              -0.1360175758600235,\n              0.04701819643378258,\n              0.01685551553964615\n            ],\n            [\n              -0.015009362250566483,\n              0.15808776021003723,\n              0.1380142867565155\n            ]\n          ],\n          [\n            [\n              0.0014395368052646518,\n              0.10229988396167755,\n              -0.08579538762569427\n            ],\n            [\n              -0.04380276799201965,\n              -0.003118126653134823,\n              0.07287978380918503\n            ],\n            [\n              -0.050696540623903275,\n              -0.010711082257330418,\n              -0.05901684984564781\n            ]\n          ],\n          [\n            [\n              0.12797345221042633,\n              -0.041508764028549194,\n              0.05089915543794632\n            ],\n            [\n              0.08462511003017426,\n              0.15349045395851135,\n              0.06033384054899216\n            ],\n            [\n              -0.17805145680904388,\n              -0.09504542499780655,\n              -0.11060364544391632\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.18813537061214447,\n        \"weights\": [\n          [\n            [\n              -0.1358455866575241,\n              0.020784197375178337,\n              0.012531528249382973\n            ],\n            [\n              -0.17718467116355896,\n              0.1581783890724182,\n              -0.10088316351175308\n            ],\n            [\n              -0.03216350078582764,\n              -0.04750040918588638,\n              0.046315405517816544\n            ]\n          ],\n          [\n            [\n              -0.10508772730827332,\n              -0.07013152539730072,\n              -0.0639897957444191\n            ],\n            [\n              0.12097233533859253,\n              -0.013156263157725334,\n              -0.08829545974731445\n            ],\n            [\n              0.11504996567964554,\n              0.10036181658506393,\n              -0.048247672617435455\n            ]\n          ],\n          [\n            [\n              0.22100888192653656,\n              0.16265073418617249,\n              0.09039779752492905\n            ],\n            [\n              0.20307500660419464,\n              0.07338210940361023,\n              0.10677074640989304\n            ],\n            [\n              0.12762710452079773,\n              0.12233605980873108,\n              0.16212224960327148\n            ]\n          ],\n          [\n            [\n              0.2026071697473526,\n              0.03163035959005356,\n              0.05191440507769585\n            ],\n            [\n              0.05101798474788666,\n              -0.08335137367248535,\n              0.12224773317575455\n            ],\n            [\n              -0.026638077571988106,\n              -0.009244870394468307,\n              0.08258619904518127\n            ]\n          ],\n          [\n            [\n              0.1353917270898819,\n              0.11299196630716324,\n              0.14517857134342194\n            ],\n            [\n              0.013255714438855648,\n              0.0006891486700624228,\n              0.0024036001414060593\n            ],\n            [\n              0.011934444308280945,\n              -0.04438449814915657,\n              -0.07786228507757187\n            ]\n          ],\n          [\n            [\n              0.12286064773797989,\n              0.1729215383529663,\n              0.03957889601588249\n            ],\n            [\n              0.13248325884342194,\n              -0.14979279041290283,\n              0.0712703987956047\n            ],\n            [\n              -0.03144574910402298,\n              0.07466572523117065,\n              -0.15872295200824738\n            ]\n          ],\n          [\n            [\n              0.0500401109457016,\n              0.11128253489732742,\n              -0.0670473501086235\n            ],\n            [\n              0.053425371646881104,\n              -0.0975981280207634,\n              -0.1515394151210785\n            ],\n            [\n              -0.018341612070798874,\n              0.08227913826704025,\n              -0.07649536430835724\n            ]\n          ],\n          [\n            [\n              -0.013060234487056732,\n              0.10545924305915833,\n              -0.04617242142558098\n            ],\n            [\n              -0.17272469401359558,\n              -0.12658539414405823,\n              -0.15309768915176392\n            ],\n            [\n              -0.059895988553762436,\n              0.010617214255034924,\n              -0.20278626680374146\n            ]\n          ],\n          [\n            [\n              -0.0945475772023201,\n              0.048694733530282974,\n              -0.12032745778560638\n            ],\n            [\n              -0.04117373004555702,\n              -0.0721011757850647,\n              0.2760360538959503\n            ],\n            [\n              -0.23647838830947876,\n              0.09139523655176163,\n              0.07709531486034393\n            ]\n          ],\n          [\n            [\n              0.08168414980173111,\n              -0.049900736659765244,\n              -0.017324190586805344\n            ],\n            [\n              -0.12146569043397903,\n              0.18098480999469757,\n              0.13150836527347565\n            ],\n            [\n              -0.19960907101631165,\n              -0.06940629333257675,\n              -0.12759949266910553\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.032049767673015594,\n        \"weights\": [\n          [\n            [\n              0.07276657968759537,\n              0.17987872660160065,\n              0.015256171114742756\n            ],\n            [\n              -0.12216264009475708,\n              -0.06664621829986572,\n              -0.16023167967796326\n            ],\n            [\n              0.06514883786439896,\n              -0.16295680403709412,\n              0.042405229061841965\n            ]\n          ],\n          [\n            [\n              -0.14318525791168213,\n              0.13009771704673767,\n              0.0518876276910305\n            ],\n            [\n              0.1567896604537964,\n              -0.051100775599479675,\n              0.14236561954021454\n            ],\n            [\n              0.0707058385014534,\n              -0.029033901169896126,\n              0.2330576330423355\n            ]\n          ],\n          [\n            [\n              0.06189902126789093,\n              -0.25021445751190186,\n              -0.1533758044242859\n            ],\n            [\n              -0.05761691555380821,\n              0.11707664281129837,\n              0.10397264361381531\n            ],\n            [\n              -0.10958359390497208,\n              0.19651801884174347,\n              0.13396427035331726\n            ]\n          ],\n          [\n            [\n              -0.06290310621261597,\n              0.008595671504735947,\n              -0.19669604301452637\n            ],\n            [\n              -0.21231496334075928,\n              -0.0584346279501915,\n              0.10230404138565063\n            ],\n            [\n              0.013176584616303444,\n              0.1945866346359253,\n              0.20213302969932556\n            ]\n          ],\n          [\n            [\n              -0.15669439733028412,\n              -0.1461036652326584,\n              0.031452495604753494\n            ],\n            [\n              -0.1582546830177307,\n              0.11258459836244583,\n              0.2544539272785187\n            ],\n            [\n              -0.053227197378873825,\n              0.25587764382362366,\n              0.1207578256726265\n            ]\n          ],\n          [\n            [\n              -0.09621194005012512,\n              -0.013983515091240406,\n              -0.042230118066072464\n            ],\n            [\n              -0.13541610538959503,\n              -0.21901044249534607,\n              0.03681187331676483\n            ],\n            [\n              0.015613908879458904,\n              -0.018824201077222824,\n              0.014227106235921383\n            ]\n          ],\n          [\n            [\n              0.29758620262145996,\n              0.30296194553375244,\n              0.08188604563474655\n            ],\n            [\n              0.2363891303539276,\n              0.14506536722183228,\n              -0.0023157803807407618\n            ],\n            [\n              0.12809130549430847,\n              -0.042837560176849365,\n              0.09612684696912766\n            ]\n          ],\n          [\n            [\n              0.01854560151696205,\n              -0.061136938631534576,\n              0.0714552104473114\n            ],\n            [\n              0.10207103937864304,\n              -0.01072518341243267,\n              0.12656676769256592\n            ],\n            [\n              -0.040705688297748566,\n              0.09779739379882812,\n              0.023327942937612534\n            ]\n          ],\n          [\n            [\n              -0.022386737167835236,\n              0.12966154515743256,\n              0.04666091501712799\n            ],\n            [\n              0.01978493109345436,\n              -0.11666550487279892,\n              -0.06739896535873413\n            ],\n            [\n              -0.06784651428461075,\n              0.059927359223365784,\n              0.2724410891532898\n            ]\n          ],\n          [\n            [\n              -0.2819039225578308,\n              -0.17425957322120667,\n              -0.0023961211554706097\n            ],\n            [\n              0.06943542510271072,\n              0.010751917958259583,\n              0.20004703104496002\n            ],\n            [\n              -0.04761410504579544,\n              0.0035902871750295162,\n              0.2276211827993393\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": -0.1071610152721405,\n        \"weights\": [\n          [\n            [\n              0.16179943084716797,\n              -0.15223224461078644,\n              0.05603885278105736\n            ],\n            [\n              0.03607629984617233,\n              0.04757857695221901,\n              -0.13878557085990906\n            ],\n            [\n              -0.1390153467655182,\n              -0.09328515827655792,\n              0.15812215209007263\n            ]\n          ],\n          [\n            [\n              0.1543763130903244,\n              -0.03176015987992287,\n              0.08444511145353317\n            ],\n            [\n              0.06713499128818512,\n              0.1576194316148758,\n              -0.03991593047976494\n            ],\n            [\n              -0.0672399029135704,\n              0.052136920392513275,\n              0.06228874623775482\n            ]\n          ],\n          [\n            [\n              0.05517008900642395,\n              0.032260771840810776,\n              0.32103991508483887\n            ],\n            [\n              -0.15375973284244537,\n              -0.0356624498963356,\n              -0.09302377700805664\n            ],\n            [\n              -0.2430153340101242,\n              -0.0408388189971447,\n              0.009174891747534275\n            ]\n          ],\n          [\n            [\n              0.24025054275989532,\n              0.14598426222801208,\n              0.1818782240152359\n            ],\n            [\n              0.20681455731391907,\n              -0.019322190433740616,\n              0.22610841691493988\n            ],\n            [\n              0.003642961150035262,\n              -0.08128095418214798,\n              -0.1269708275794983\n            ]\n          ],\n          [\n            [\n              0.048292554914951324,\n              -0.2193034142255783,\n              -0.003415939398109913\n            ],\n            [\n              0.07603807002305984,\n              -0.1078244149684906,\n              0.0187393631786108\n            ],\n            [\n              -0.08292911946773529,\n              -0.25053516030311584,\n              0.07760937511920929\n            ]\n          ],\n          [\n            [\n              0.06579913944005966,\n              -0.1253804713487625,\n              0.15515655279159546\n            ],\n            [\n              -0.09566143155097961,\n              -0.19516333937644958,\n              -0.19508473575115204\n            ],\n            [\n              0.01030671875923872,\n              -0.177727609872818,\n              0.015511158853769302\n            ]\n          ],\n          [\n            [\n              0.21972207725048065,\n              0.09551124274730682,\n              0.05152615159749985\n            ],\n            [\n              0.08212753385305405,\n              0.26710039377212524,\n              0.17063234746456146\n            ],\n            [\n              0.18379761278629303,\n              -0.008331249468028545,\n              0.032313983887434006\n            ]\n          ],\n          [\n            [\n              0.21882523596286774,\n              0.04904114082455635,\n              0.03894847258925438\n            ],\n            [\n              -0.06760572642087936,\n              -0.12773454189300537,\n              0.2312447726726532\n            ],\n            [\n              -0.07193315774202347,\n              -0.014704928733408451,\n              0.11695306748151779\n            ]\n          ],\n          [\n            [\n              0.22869613766670227,\n              0.11457665264606476,\n              -0.13291311264038086\n            ],\n            [\n              0.08686745911836624,\n              -0.12870098650455475,\n              -0.05958646908402443\n            ],\n            [\n              -0.006725900340825319,\n              0.04420801252126694,\n              -0.05865180864930153\n            ]\n          ],\n          [\n            [\n              -0.014937112107872963,\n              -0.2035684734582901,\n              -0.1690637618303299\n            ],\n            [\n              -0.028141602873802185,\n              0.02778678573668003,\n              -0.2195882499217987\n            ],\n            [\n              0.215241476893425,\n              0.09540508687496185,\n              0.11833357065916061\n            ]\n          ]\n        ]\n      },\n      {\n        \"bias\": 0.061665862798690796,\n        \"weights\": [\n          [\n            [\n              -0.0025511737912893295,\n              0.020746171474456787,\n              -0.06292499601840973\n            ],\n            [\n              -0.13648031651973724,\n              0.05221004784107208,\n              0.11034466326236725\n            ],\n            [\n              0.04746418446302414,\n              0.043327465653419495,\n              -0.12029217183589935\n            ]\n          ],\n          [\n            [\n              -0.12141206860542297,\n              -0.021659456193447113,\n              -0.1135789230465889\n            ],\n            [\n              -0.19066378474235535,\n              -0.20452120900154114,\n              0.0035310890525579453\n            ],\n            [\n              -0.017933499068021774,\n              0.04559336602687836,\n              0.014429604634642601\n            ]\n          ],\n          [\n            [\n              0.10998199135065079,\n              0.15145368874073029,\n              -0.10743904858827591\n            ],\n            [\n              0.00623478926718235,\n              -0.05017784237861633,\n              0.011177558451890945\n            ],\n            [\n              -0.06357812881469727,\n              -0.16046376526355743,\n              0.13748908042907715\n            ]\n          ],\n          [\n            [\n              0.12345323711633682,\n              -0.03187406808137894,\n              0.11310245841741562\n            ],\n            [\n              0.16261525452136993,\n              0.14970175921916962,\n              -0.06283792853355408\n            ],\n            [\n              0.17747862637043,\n              0.1026889979839325,\n              -0.15795530378818512\n            ]\n          ],\n          [\n            [\n              -0.031117647886276245,\n              0.05445241183042526,\n              0.1802845150232315\n            ],\n            [\n              0.2290922999382019,\n              -0.007870027795433998,\n              0.012697769328951836\n            ],\n            [\n              0.1058914065361023,\n              -0.07278214395046234,\n              -0.0018795863725245\n            ]\n          ],\n          [\n            [\n              0.15368755161762238,\n              0.2867761552333832,\n              -0.09185421466827393\n            ],\n            [\n              0.0020685032941401005,\n              0.20404541492462158,\n              0.19803866744041443\n            ],\n            [\n              0.2220652550458908,\n              0.13969124853610992,\n              0.04406684264540672\n            ]\n          ],\n          [\n            [\n              -0.1418893188238144,\n              0.028295258060097694,\n              -0.10037285834550858\n            ],\n            [\n              -0.07259444892406464,\n              -0.03481004014611244,\n              0.003599332645535469\n            ],\n            [\n              0.05741039291024208,\n              -0.13806051015853882,\n              0.012858081609010696\n            ]\n          ],\n          [\n            [\n              0.06935367733240128,\n              -0.20636332035064697,\n              0.03540106117725372\n            ],\n            [\n              0.07383903115987778,\n              0.03785941004753113,\n              -0.09802525490522385\n            ],\n            [\n              -0.058929651975631714,\n              -0.13806124031543732,\n              0.006437752395868301\n            ]\n          ],\n          [\n            [\n              -0.03544848412275314,\n              0.041394755244255066,\n              0.13945014774799347\n            ],\n            [\n              0.1043098196387291,\n              0.10708555579185486,\n              -0.08619974553585052\n            ],\n            [\n              -0.16758239269256592,\n              0.06004820391535759,\n              0.12389624863862991\n            ]\n          ],\n          [\n            [\n              -0.043459489941596985,\n              0.0891413688659668,\n              -0.029467688873410225\n            ],\n            [\n              0.1968507170677185,\n              0.01814853958785534,\n              -0.0600057914853096\n            ],\n            [\n              -0.005719579290598631,\n              -0.048423200845718384,\n              0.17595772445201874\n            ]\n          ]\n        ]\n      }\n    ]\n  },\n  {\n    \"name\": \"relu_3_2\",\n    \"input_shape\": [\n      9,\n      9,\n      10\n    ],\n    \"output_shape\": [\n      9,\n      9,\n      10\n    ],\n    \"num_neurons\": 10\n  },\n  {\n    \"name\": \"max_pool_3\",\n    \"input_shape\": [\n      9,\n      9,\n      10\n    ],\n    \"output_shape\": [\n      4,\n      4,\n      10\n    ],\n    \"num_neurons\": 10\n  },\n  {\n    \"name\": \"flatten\",\n    \"input_shape\": [\n      4,\n      4,\n      10\n    ],\n    \"output_shape\": [\n      160\n    ],\n    \"num_neurons\": 160\n  },\n  {\n    \"name\": \"output\",\n    \"input_shape\": [\n      160\n    ],\n    \"output_shape\": [\n      10\n    ],\n    \"num_neurons\": 10,\n    \"weights\": [\n      {\n        \"bias\": -0.2513352632522583,\n        \"weights\": [\n          0.037237223237752914,\n          -0.2085798680782318,\n          0.22239898145198822,\n          -0.018747668713331223,\n          0.19035179913043976,\n          -0.18876996636390686,\n          -0.25303611159324646,\n          -0.030314432457089424,\n          0.15639150142669678,\n          -0.008488400839269161,\n          0.1312105506658554,\n          -0.1418263167142868,\n          0.1837759017944336,\n          0.0009167467942461371,\n          0.19680120050907135,\n          0.01619894616305828,\n          0.13799647986888885,\n          -0.17226719856262207,\n          0.2117895931005478,\n          0.05933043360710144,\n          0.12771391868591309,\n          0.01596875675022602,\n          0.0722590759396553,\n          -0.04783660173416138,\n          -0.05229617655277252,\n          -0.07667139917612076,\n          0.08286578208208084,\n          -0.22329497337341309,\n          0.1616649478673935,\n          -0.04890842363238335,\n          0.16079849004745483,\n          -0.24089208245277405,\n          0.04068567976355553,\n          -0.15328344702720642,\n          0.24964410066604614,\n          -0.045488741248846054,\n          -0.06377352029085159,\n          -0.28701427578926086,\n          0.033565498888492584,\n          -0.18892920017242432,\n          -0.03639240562915802,\n          -0.2350505292415619,\n          0.11429664492607117,\n          0.02341393753886223,\n          -0.11650383472442627,\n          -0.06349196285009384,\n          -0.11841116100549698,\n          0.0019236041698604822,\n          0.0964500904083252,\n          -0.14897628128528595,\n          0.11839055269956589,\n          -0.015549846924841404,\n          0.13918085396289825,\n          0.014651562087237835,\n          0.031121404841542244,\n          0.12348775565624237,\n          -0.01968524605035782,\n          -0.14411517977714539,\n          -0.03137340396642685,\n          0.15658320486545563,\n          0.0929691344499588,\n          -0.07419539988040924,\n          0.0623166449368,\n          0.14762182533740997,\n          0.01012086495757103,\n          0.11676504462957382,\n          0.0023846919648349285,\n          0.04194874316453934,\n          0.2167632281780243,\n          0.107383131980896,\n          -0.02139628306031227,\n          -0.1818522959947586,\n          0.0939592644572258,\n          0.06153164803981781,\n          0.02344508282840252,\n          0.05982291325926781,\n          0.03424457833170891,\n          -0.03409535437822342,\n          0.06347865611314774,\n          0.1078614890575409,\n          0.17487934231758118,\n          -0.19930392503738403,\n          0.2562271058559418,\n          0.03345225751399994,\n          -0.06433529406785965,\n          0.06729006767272949,\n          -0.03861946240067482,\n          -0.15650668740272522,\n          0.028838133439421654,\n          0.006899332627654076,\n          0.18134081363677979,\n          0.008289948105812073,\n          0.2117832452058792,\n          -0.11967208236455917,\n          -0.1511869728565216,\n          0.03577937185764313,\n          0.13548101484775543,\n          -0.12569761276245117,\n          0.1621827483177185,\n          0.040863316506147385,\n          0.0857875868678093,\n          0.09568747133016586,\n          0.24893949925899506,\n          0.10296972095966339,\n          0.07670018821954727,\n          -0.16938653588294983,\n          -0.05782659351825714,\n          -0.1479375809431076,\n          0.004151251167058945,\n          -0.036444827914237976,\n          0.13446637988090515,\n          -0.08143768459558487,\n          0.18580955266952515,\n          -0.13741815090179443,\n          -0.0870201364159584,\n          0.061781659722328186,\n          0.13522882759571075,\n          0.06917691230773926,\n          -0.08724942058324814,\n          -0.13882677257061005,\n          0.04797711968421936,\n          0.004900653380900621,\n          0.037656333297491074,\n          0.1827707439661026,\n          0.14588098227977753,\n          0.10867556929588318,\n          -0.07463699579238892,\n          0.026831157505512238,\n          0.0734119638800621,\n          -0.3187274634838104,\n          0.14199024438858032,\n          -0.15632282197475433,\n          -0.04299023002386093,\n          0.004703454673290253,\n          0.05190644413232803,\n          -0.0728243887424469,\n          0.04847163334488869,\n          -0.05418686196208,\n          0.09738653153181076,\n          -0.10706260800361633,\n          0.13013815879821777,\n          -0.15482038259506226,\n          0.018822895362973213,\n          0.050359707325696945,\n          0.22491790354251862,\n          0.003010277636349201,\n          -0.04754158854484558,\n          -0.0860431045293808,\n          0.021679630503058434,\n          -0.14216278493404388,\n          0.1486811339855194,\n          -0.23144711554050446,\n          -0.09288418292999268,\n          0.031953901052474976,\n          -0.03763307258486748,\n          0.053416907787323,\n          0.015163575299084187,\n          0.01830022782087326,\n          0.019117359071969986,\n          0.03853989765048027\n        ]\n      },\n      {\n        \"bias\": 0.06519734114408493,\n        \"weights\": [\n          0.02079402282834053,\n          -0.11754846572875977,\n          -0.19642002880573273,\n          -0.24779118597507477,\n          0.03460664302110672,\n          0.04089893400669098,\n          -0.36112111806869507,\n          0.20083148777484894,\n          0.033368732780218124,\n          -0.12880755960941315,\n          -0.15078726410865784,\n          -0.2041657567024231,\n          0.04551428556442261,\n          -0.24495230615139008,\n          -0.055047597736120224,\n          -0.011546147055923939,\n          -0.11893712729215622,\n          -0.1647050678730011,\n          -0.029689233750104904,\n          0.010487670078873634,\n          0.07262550294399261,\n          0.12508803606033325,\n          -0.020908445119857788,\n          -0.23473933339118958,\n          -0.1036846935749054,\n          -0.08660558611154556,\n          -0.04750441759824753,\n          0.10566245019435883,\n          -0.09703733772039413,\n          -0.003489075228571892,\n          -0.03992157801985741,\n          -0.05630800500512123,\n          -0.20488663017749786,\n          -0.058865614235401154,\n          -0.0873987227678299,\n          0.014982353895902634,\n          -0.2316000908613205,\n          0.07593020051717758,\n          0.04896589368581772,\n          -0.19822505116462708,\n          -0.05083863064646721,\n          0.15500585734844208,\n          -0.10749870538711548,\n          0.08721087872982025,\n          -0.05278895050287247,\n          -0.015682891011238098,\n          -0.07422029972076416,\n          0.17600804567337036,\n          0.0158672071993351,\n          0.22179925441741943,\n          -0.08887603133916855,\n          0.02084948681294918,\n          0.008970390073955059,\n          0.11157546937465668,\n          0.007794892881065607,\n          0.09842189401388168,\n          0.1446964293718338,\n          0.14059989154338837,\n          0.23860600590705872,\n          0.12240061163902283,\n          0.1566430777311325,\n          0.06698709726333618,\n          0.14156758785247803,\n          0.20906579494476318,\n          0.04928354173898697,\n          -0.1434832662343979,\n          0.12041249871253967,\n          -0.07896735519170761,\n          -0.029586967080831528,\n          0.062060169875621796,\n          0.01035227533429861,\n          0.08735382556915283,\n          -0.05076536163687706,\n          -0.044588249176740646,\n          -0.035106439143419266,\n          -0.1356794834136963,\n          0.07217061519622803,\n          -0.024540981277823448,\n          -0.06691071391105652,\n          -0.05676655098795891,\n          0.10740158706903458,\n          0.04756231978535652,\n          -0.11898987740278244,\n          -0.030191099271178246,\n          0.09835253655910492,\n          -0.09163330495357513,\n          0.1027570515871048,\n          -0.02512558549642563,\n          -0.016787171363830566,\n          -0.12739400565624237,\n          -0.13635672628879547,\n          -0.08522175252437592,\n          0.10673031210899353,\n          0.016609013080596924,\n          0.07704536616802216,\n          -0.0576607845723629,\n          0.2912350594997406,\n          0.14694605767726898,\n          0.0024474398232996464,\n          0.10420805215835571,\n          0.15038296580314636,\n          0.1361939162015915,\n          -0.03876202553510666,\n          -0.007628224324434996,\n          0.24061846733093262,\n          -0.18753314018249512,\n          0.15079623460769653,\n          -0.030442936345934868,\n          -0.14139853417873383,\n          0.21154966950416565,\n          0.0838024914264679,\n          0.05419695004820824,\n          -0.02998300828039646,\n          -0.011226293630897999,\n          -0.04138803854584694,\n          0.04061892628669739,\n          0.1276482343673706,\n          0.1606159657239914,\n          0.037188563495874405,\n          -0.1970548778772354,\n          0.09592647105455399,\n          0.10023986548185349,\n          -0.1526363492012024,\n          -0.2351713627576828,\n          0.10576551407575607,\n          -0.06816257536411285,\n          -0.12890216708183289,\n          0.07298823446035385,\n          -0.14376012980937958,\n          0.03043556958436966,\n          -0.04120851680636406,\n          -0.00672142906114459,\n          0.09243026375770569,\n          -0.12206718325614929,\n          0.1518348604440689,\n          0.09600169211626053,\n          -0.035856325179338455,\n          -0.09588852524757385,\n          -0.05772422254085541,\n          0.08547937124967575,\n          0.0068990495055913925,\n          0.12570695579051971,\n          -0.19614481925964355,\n          -0.10259822010993958,\n          0.01009683683514595,\n          -0.13089512288570404,\n          0.10345528274774551,\n          0.05156470835208893,\n          -0.15156997740268707,\n          -0.07541146874427795,\n          -0.11222236603498459,\n          0.21292605996131897,\n          -0.13383132219314575,\n          -0.2018393576145172,\n          0.03841444477438927,\n          -0.005980853457003832,\n          -0.20552890002727509,\n          -0.023261312395334244,\n          -0.038892123848199844,\n          -0.01693912036716938\n        ]\n      },\n      {\n        \"bias\": 0.04120241478085518,\n        \"weights\": [\n          0.08600084483623505,\n          -0.15282900631427765,\n          -0.027479447424411774,\n          -0.04215899482369423,\n          -0.2327398955821991,\n          -0.00358211575075984,\n          0.08998187631368637,\n          0.13590587675571442,\n          0.040852587670087814,\n          0.07764560729265213,\n          0.18494489789009094,\n          0.013980111107230186,\n          0.03266307711601257,\n          0.07039047032594681,\n          0.03717689961194992,\n          0.20998527109622955,\n          0.03674859181046486,\n          0.03126056492328644,\n          0.027937866747379303,\n          -0.0662318766117096,\n          0.14224347472190857,\n          -0.011425723321735859,\n          -0.03436950221657753,\n          -0.17810063064098358,\n          -0.0452599823474884,\n          0.024546818807721138,\n          -0.06436756253242493,\n          -0.09777086228132248,\n          -0.1455928385257721,\n          0.02671065181493759,\n          0.11764450371265411,\n          -0.08235208690166473,\n          0.1531396210193634,\n          0.12281898409128189,\n          -0.24636049568653107,\n          -0.02294488251209259,\n          0.052015628665685654,\n          -0.12787996232509613,\n          -0.10134577006101608,\n          0.22447580099105835,\n          -0.08638007938861847,\n          -0.11863862723112106,\n          0.011148069985210896,\n          0.016892261803150177,\n          -0.0375971756875515,\n          0.00924800056964159,\n          0.05850135162472725,\n          -0.0026676931884139776,\n          -0.1703430712223053,\n          0.07496099919080734,\n          -0.18020468950271606,\n          -0.03123406320810318,\n          -0.035994257777929306,\n          -0.03662952780723572,\n          0.05994078144431114,\n          0.006410139612853527,\n          -0.11289823055267334,\n          0.07897420972585678,\n          0.03905794396996498,\n          0.04750785604119301,\n          -0.1678929328918457,\n          -0.01354603935033083,\n          -0.06797380745410919,\n          -0.12825189530849457,\n          -0.191240593791008,\n          0.0038869220297783613,\n          -0.08653810620307922,\n          0.04066885635256767,\n          -0.12487009912729263,\n          0.06414137780666351,\n          -0.037770166993141174,\n          0.09392068535089493,\n          -0.05836348980665207,\n          0.047921646386384964,\n          -0.05376650393009186,\n          0.05610073357820511,\n          0.13865968585014343,\n          0.0442340262234211,\n          -0.02996636927127838,\n          0.09017479419708252,\n          -0.14603812992572784,\n          -0.15702925622463226,\n          0.09836402535438538,\n          -0.034204814583063126,\n          -0.21223345398902893,\n          0.08547372370958328,\n          0.03568708151578903,\n          -0.18350523710250854,\n          -0.04710116609930992,\n          -0.006407922599464655,\n          -0.039264336228370667,\n          -0.026584159582853317,\n          -0.19885477423667908,\n          -0.026866992935538292,\n          0.02649182453751564,\n          -0.07081405818462372,\n          -0.12021728605031967,\n          -0.045769963413476944,\n          -0.045715611428022385,\n          0.010395940393209457,\n          0.14889976382255554,\n          0.08393600583076477,\n          -0.013625028543174267,\n          -0.184380441904068,\n          -0.22028972208499908,\n          -0.05215242877602577,\n          -0.07085873931646347,\n          -0.2045580893754959,\n          -0.07469934970140457,\n          -0.04117636755108833,\n          -0.1794842928647995,\n          -0.05560043454170227,\n          0.03548799827694893,\n          -0.08768782764673233,\n          -0.15210869908332825,\n          -0.03236064314842224,\n          0.11876364797353745,\n          -0.1098104789853096,\n          -0.005126004572957754,\n          -0.042576082050800323,\n          0.1425704061985016,\n          0.1461399495601654,\n          0.01607448048889637,\n          -0.2623697817325592,\n          -0.04019223526120186,\n          0.07856371253728867,\n          0.15838487446308136,\n          -0.27415820956230164,\n          -0.16817432641983032,\n          0.09030286967754364,\n          -0.0840967521071434,\n          0.17270471155643463,\n          0.18276546895503998,\n          -0.1929769366979599,\n          -0.2359301745891571,\n          0.0933491438627243,\n          0.11974931508302689,\n          0.07347185164690018,\n          0.03694111481308937,\n          0.04725728556513786,\n          -0.05635693669319153,\n          -0.11006646603345871,\n          0.006177674047648907,\n          0.01241665706038475,\n          -0.123158298432827,\n          -0.14310431480407715,\n          -0.1627674251794815,\n          -0.2512933611869812,\n          0.06060843914747238,\n          0.18860188126564026,\n          0.15036679804325104,\n          -0.06769870221614838,\n          0.07621799409389496,\n          -0.08008167892694473,\n          0.0892903134226799,\n          -0.18899571895599365,\n          0.07670537382364273,\n          -0.09369657188653946,\n          0.049252886325120926,\n          0.04767722636461258\n        ]\n      },\n      {\n        \"bias\": 0.03328557312488556,\n        \"weights\": [\n          -0.08469369262456894,\n          -0.07368701696395874,\n          -0.1512986570596695,\n          0.2565789222717285,\n          -0.013534227386116982,\n          0.011298569850623608,\n          0.05071094632148743,\n          -0.1242319792509079,\n          0.07724617421627045,\n          0.11585883051156998,\n          -0.08613696694374084,\n          0.01709127426147461,\n          0.14706312119960785,\n          0.03544112294912338,\n          -0.03719817101955414,\n          0.09248726814985275,\n          -0.20743463933467865,\n          0.10114835202693939,\n          -0.03204192593693733,\n          0.04098070040345192,\n          0.010841119103133678,\n          -0.1099933311343193,\n          -0.12631045281887054,\n          -0.048705197870731354,\n          0.06112825870513916,\n          -0.0438520610332489,\n          -0.10077061504125595,\n          -0.019756561145186424,\n          0.05961857736110687,\n          -0.09528839588165283,\n          -0.009388601407408714,\n          0.20330873131752014,\n          0.0077501204796135426,\n          0.13931621611118317,\n          0.1991031914949417,\n          -0.06700847297906876,\n          0.08266443014144897,\n          -0.07973328232765198,\n          0.038616426289081573,\n          0.0636761337518692,\n          0.1014070212841034,\n          -0.11712313443422318,\n          0.03464946523308754,\n          0.06275692582130432,\n          -0.03704669326543808,\n          -0.14066182076931,\n          -0.13146544992923737,\n          0.05711666867136955,\n          0.23695756494998932,\n          -0.09217248857021332,\n          0.21224084496498108,\n          0.06584629416465759,\n          0.021800076588988304,\n          0.09231077879667282,\n          0.07490959763526917,\n          0.15343786776065826,\n          0.16477355360984802,\n          -0.12703096866607666,\n          0.03442982956767082,\n          -0.11825532466173172,\n          0.032010845839977264,\n          -0.04941524937748909,\n          -0.03632350638508797,\n          -0.1250848025083542,\n          0.09015306085348129,\n          -0.16828550398349762,\n          0.13186994194984436,\n          0.007492118515074253,\n          -0.020117156207561493,\n          -0.1650894582271576,\n          0.12328720837831497,\n          0.018113315105438232,\n          -0.04145171865820885,\n          -0.00039698652108199894,\n          0.07897568494081497,\n          0.14171190559864044,\n          -0.005400299560278654,\n          -0.007740440778434277,\n          -0.052659858018159866,\n          -0.0036274211015552282,\n          0.15316137671470642,\n          0.13744430243968964,\n          -0.02076825313270092,\n          -0.06474146246910095,\n          0.004920308478176594,\n          -0.008472095243632793,\n          0.08498229086399078,\n          -0.08411326259374619,\n          0.034755319356918335,\n          0.145986407995224,\n          -0.03708430007100105,\n          0.13377876579761505,\n          0.05018776282668114,\n          0.04652559012174606,\n          0.023020414635539055,\n          -0.020148739218711853,\n          -0.08832806348800659,\n          -0.08817742019891739,\n          -0.13774582743644714,\n          0.14065656065940857,\n          0.11330980062484741,\n          0.23775307834148407,\n          -0.13410240411758423,\n          0.041749853640794754,\n          -0.049154892563819885,\n          0.13007943332195282,\n          -0.12735864520072937,\n          -0.04375014081597328,\n          -0.14050902426242828,\n          0.0044339741580188274,\n          0.14104455709457397,\n          -0.04526012763381004,\n          0.029577406123280525,\n          0.03591854125261307,\n          -0.18595625460147858,\n          0.16793856024742126,\n          -0.1735418438911438,\n          0.11090452969074249,\n          0.08248969167470932,\n          0.031469836831092834,\n          -0.20477627217769623,\n          0.08334281295537949,\n          0.03731105849146843,\n          -0.018131623044610023,\n          0.10176900774240494,\n          0.04530928283929825,\n          -0.20170095562934875,\n          0.07753785699605942,\n          0.005386087577790022,\n          0.0879441350698471,\n          0.11917395144701004,\n          0.10809807479381561,\n          -0.03257959708571434,\n          -0.08768340945243835,\n          -0.15129457414150238,\n          0.13132545351982117,\n          -0.05518629401922226,\n          0.1352892816066742,\n          -0.1272444874048233,\n          -0.1279493123292923,\n          0.11846945434808731,\n          0.13123778998851776,\n          0.02546725608408451,\n          -0.18402616679668427,\n          -0.25343838334083557,\n          0.1608027219772339,\n          0.14363862574100494,\n          0.095777228474617,\n          0.12361801415681839,\n          -0.14778845012187958,\n          -0.0717211589217186,\n          0.17189612984657288,\n          -0.03806455805897713,\n          0.06086942180991173,\n          -0.19953981041908264,\n          -0.17191222310066223,\n          -0.18540118634700775,\n          0.1942734569311142,\n          0.03480803221464157,\n          0.13359607756137848\n        ]\n      },\n      {\n        \"bias\": -0.049977295100688934,\n        \"weights\": [\n          -0.05572054535150528,\n          -0.1551874876022339,\n          0.020352210849523544,\n          0.09161262959241867,\n          0.10101374983787537,\n          -0.022689171135425568,\n          0.1960146725177765,\n          0.022763028740882874,\n          0.12757505476474762,\n          0.17756937444210052,\n          0.022784404456615448,\n          0.18289858102798462,\n          0.08029237389564514,\n          0.10200183838605881,\n          0.20415280759334564,\n          -0.21716825664043427,\n          -0.017852002754807472,\n          -0.06801117956638336,\n          0.2604348063468933,\n          0.19233711063861847,\n          0.07736406475305557,\n          -0.08600379526615143,\n          -0.000648561748676002,\n          0.08696174621582031,\n          0.24168306589126587,\n          -0.17914535105228424,\n          -0.019220460206270218,\n          0.010958154685795307,\n          0.18479633331298828,\n          0.1812601238489151,\n          0.12014281004667282,\n          0.07065637409687042,\n          0.13548587262630463,\n          0.21402156352996826,\n          0.16186054050922394,\n          0.028483539819717407,\n          0.1447264850139618,\n          0.11166881769895554,\n          0.03180659934878349,\n          -0.06799910217523575,\n          0.1424344778060913,\n          -0.16308742761611938,\n          0.12037429213523865,\n          0.08241201937198639,\n          -0.006381646264344454,\n          -0.1439223289489746,\n          0.018708644434809685,\n          0.17588558793067932,\n          0.10204339027404785,\n          0.03225938603281975,\n          -0.16779983043670654,\n          -0.1852608621120453,\n          0.12777948379516602,\n          0.14605174958705902,\n          0.10819468647241592,\n          0.18260523676872253,\n          0.039845060557127,\n          0.1529584378004074,\n          0.04988490045070648,\n          0.0747872069478035,\n          0.16076454520225525,\n          -0.24388034641742706,\n          0.1855817288160324,\n          -0.05500513315200806,\n          0.05364350229501724,\n          -0.04310567304491997,\n          -0.12908554077148438,\n          0.04129517823457718,\n          -0.09934262931346893,\n          -0.12845230102539062,\n          -0.06754323840141296,\n          -0.21861182153224945,\n          0.00018565059872344136,\n          -0.07219569385051727,\n          0.26546522974967957,\n          0.03840245306491852,\n          0.14276957511901855,\n          0.14317616820335388,\n          0.02138613536953926,\n          -0.010423548519611359,\n          -0.12960036098957062,\n          0.028418630361557007,\n          0.17652270197868347,\n          0.1768396496772766,\n          -0.03356723487377167,\n          -0.09414089471101761,\n          -0.06974460929632187,\n          0.24123646318912506,\n          0.08451832830905914,\n          -0.1976855844259262,\n          -0.1876990795135498,\n          0.04215606674551964,\n          0.1136370375752449,\n          -0.12485310435295105,\n          -0.052624695003032684,\n          -0.004095508251339197,\n          0.10575198382139206,\n          0.0005804583197459579,\n          -0.05689302831888199,\n          -0.1730596274137497,\n          -0.10368581116199493,\n          -0.14030338823795319,\n          0.038566138595342636,\n          -0.0693182647228241,\n          -0.088661789894104,\n          0.07880612462759018,\n          -0.02643081173300743,\n          0.20525892078876495,\n          0.14734841883182526,\n          -0.04572084918618202,\n          -0.16984106600284576,\n          0.05686834454536438,\n          -0.04002545028924942,\n          0.19997598230838776,\n          0.13044387102127075,\n          0.10726533830165863,\n          0.022250255569815636,\n          0.0871301144361496,\n          0.07369503378868103,\n          -0.2776543200016022,\n          -0.009098000824451447,\n          -0.149514839053154,\n          0.0821806862950325,\n          0.13884590566158295,\n          0.13742420077323914,\n          0.0424516536295414,\n          0.007404979784041643,\n          0.3190549612045288,\n          0.07036988437175751,\n          -0.1615973263978958,\n          -0.06823543459177017,\n          -0.07634284347295761,\n          -0.030366120859980583,\n          0.09335874766111374,\n          -0.0037275233771651983,\n          -0.12423108518123627,\n          -0.11606099456548691,\n          0.2158622443675995,\n          -0.10634196549654007,\n          -0.22440743446350098,\n          -0.09882140159606934,\n          -0.20353126525878906,\n          0.039445336908102036,\n          -0.04115420952439308,\n          0.2158142477273941,\n          -0.14689353108406067,\n          -0.18669050931930542,\n          0.004791576880961657,\n          -0.03494725376367569,\n          -0.16598285734653473,\n          -0.024321993812918663,\n          0.007548720110207796,\n          0.04897141456604004,\n          0.151950404047966,\n          -0.12424694746732712,\n          0.11363803595304489,\n          -0.029262833297252655,\n          -0.22700084745883942,\n          0.18970008194446564,\n          -0.27406176924705505\n        ]\n      },\n      {\n        \"bias\": 0.10740397870540619,\n        \"weights\": [\n          -0.09817632287740707,\n          0.09360858052968979,\n          -0.2206110954284668,\n          -0.03642928972840309,\n          0.019186800345778465,\n          0.009339817799627781,\n          0.0678006112575531,\n          -0.12545500695705414,\n          -0.17405526340007782,\n          0.1787732094526291,\n          0.016165463253855705,\n          -0.1535927951335907,\n          -0.23425143957138062,\n          0.10757003724575043,\n          -0.06270799785852432,\n          -0.04213930293917656,\n          -0.00785924680531025,\n          -0.0915057510137558,\n          -0.1977023184299469,\n          0.18485864996910095,\n          -0.08709600567817688,\n          -0.07833657413721085,\n          -0.23701629042625427,\n          -0.02431904524564743,\n          -0.05959422141313553,\n          -0.12913313508033752,\n          0.29110148549079895,\n          -0.07331027090549469,\n          -0.0945194661617279,\n          0.014398790895938873,\n          -0.20398154854774475,\n          -0.0639834851026535,\n          -0.28068962693214417,\n          -0.07411859184503555,\n          0.17561130225658417,\n          0.03084406815469265,\n          0.13568758964538574,\n          -0.17196929454803467,\n          -0.17531254887580872,\n          0.24685105681419373,\n          -0.07949556410312653,\n          -0.06590619683265686,\n          -0.08142048120498657,\n          0.06871413439512253,\n          0.10955297201871872,\n          -0.09832993894815445,\n          0.2631336450576782,\n          -0.12286641448736191,\n          -0.2420239895582199,\n          0.17391496896743774,\n          -0.10369700938463211,\n          0.08312420547008514,\n          0.028955940157175064,\n          -0.05447767302393913,\n          -0.10312772542238235,\n          0.008984916843473911,\n          -0.10846628993749619,\n          0.011468200944364071,\n          -0.04736282303929329,\n          0.019867198541760445,\n          -0.24239042401313782,\n          -0.13549824059009552,\n          -0.2936073839664459,\n          -0.22102601826190948,\n          0.06334040313959122,\n          -0.13097910583019257,\n          0.06423677504062653,\n          -0.2156268060207367,\n          -0.06296639889478683,\n          0.0018850264605134726,\n          -0.19911891222000122,\n          -0.1331825703382492,\n          -0.17773933708667755,\n          -0.07899640500545502,\n          -0.07640063017606735,\n          -0.17165009677410126,\n          -0.0670141652226448,\n          0.1168694868683815,\n          -0.08239220082759857,\n          0.10499954223632812,\n          0.13124549388885498,\n          0.16127580404281616,\n          -0.27503979206085205,\n          -0.10864947736263275,\n          0.046090465039014816,\n          -0.021556736901402473,\n          -0.027808692306280136,\n          0.013691266998648643,\n          -0.09099084138870239,\n          0.11974895745515823,\n          -0.1061704084277153,\n          0.1164446473121643,\n          -0.02484109252691269,\n          -0.12037941068410873,\n          -0.02865842543542385,\n          0.014138509519398212,\n          -0.08974955230951309,\n          -0.10594083368778229,\n          -0.15397924184799194,\n          -0.017448142170906067,\n          -0.3773985803127289,\n          -0.13716748356819153,\n          -0.1757728010416031,\n          0.08123417943716049,\n          -0.04952746257185936,\n          -0.10134467482566833,\n          -0.1725357621908188,\n          -0.009008901193737984,\n          -0.19613268971443176,\n          -0.1252610832452774,\n          -0.18627676367759705,\n          0.11160551756620407,\n          -0.05294667184352875,\n          -0.1735457181930542,\n          -0.048068828880786896,\n          0.03661644086241722,\n          0.10665728151798248,\n          0.05310671776533127,\n          -0.05112740397453308,\n          -0.04014366492629051,\n          -0.2204791009426117,\n          -0.1124662235379219,\n          -0.14420171082019806,\n          -0.12129344791173935,\n          -0.045645374804735184,\n          0.04447237029671669,\n          -0.005466880742460489,\n          0.05493749678134918,\n          -0.2751377522945404,\n          0.35105863213539124,\n          -0.19648021459579468,\n          -0.040038101375103,\n          -0.05119453743100166,\n          -0.057337936013936996,\n          -0.09457419067621231,\n          -0.25313904881477356,\n          0.06937748193740845,\n          -0.18291690945625305,\n          -0.2244023233652115,\n          -0.14007000625133514,\n          -0.2888190746307373,\n          0.010960889980196953,\n          -0.25302475690841675,\n          -0.08481965959072113,\n          -0.05915164574980736,\n          0.030427807942032814,\n          0.04273337498307228,\n          -0.09530351310968399,\n          -0.16611160337924957,\n          0.11970489472150803,\n          -0.3337106704711914,\n          -0.1330523043870926,\n          -0.08704158663749695,\n          0.10914306342601776,\n          -0.0006350909825414419,\n          -0.3267476260662079,\n          0.014995367266237736,\n          0.10203836113214493,\n          -0.20047084987163544,\n          0.04865623638033867\n        ]\n      },\n      {\n        \"bias\": 0.1981860101222992,\n        \"weights\": [\n          -0.27284112572669983,\n          0.0540563128888607,\n          0.0998094454407692,\n          0.00199695467017591,\n          -0.01917155645787716,\n          0.036235369741916656,\n          -0.21463792026042938,\n          0.03745388612151146,\n          0.004020120482891798,\n          -0.03348729759454727,\n          -0.18775486946105957,\n          0.04625977948307991,\n          -0.08220256119966507,\n          -0.02491600625216961,\n          -0.14655394852161407,\n          0.04158813878893852,\n          -0.023304522037506104,\n          -0.10217802226543427,\n          0.029488958418369293,\n          0.07126743346452713,\n          -0.19182774424552917,\n          -0.20424877107143402,\n          -0.1188196912407875,\n          0.017517048865556717,\n          -0.1786332130432129,\n          0.2456461787223816,\n          -0.10822447389364243,\n          -0.029559070244431496,\n          0.0666329637169838,\n          -0.13657335937023163,\n          0.054573904722929,\n          0.11644081771373749,\n          0.020247742533683777,\n          -0.001992663834244013,\n          0.15066377818584442,\n          0.20816725492477417,\n          0.15670248866081238,\n          0.014836751855909824,\n          -0.050185926258563995,\n          0.09624837338924408,\n          -0.07556010782718658,\n          0.20634761452674866,\n          0.02330104447901249,\n          0.1292237490415573,\n          0.0666479766368866,\n          0.12132132798433304,\n          -0.0023121866397559643,\n          -0.007688293233513832,\n          -0.07181555032730103,\n          0.23851683735847473,\n          0.03026459738612175,\n          -0.12228409945964813,\n          0.1331338882446289,\n          0.044146109372377396,\n          0.08712542057037354,\n          0.23654964566230774,\n          -0.08287416398525238,\n          0.05321338772773743,\n          0.14882305264472961,\n          -0.017454281449317932,\n          0.09899231046438217,\n          -0.06365566700696945,\n          -0.05781932175159454,\n          -0.029267551377415657,\n          -0.05585131794214249,\n          -0.051691435277462006,\n          0.03675556182861328,\n          -0.008843853138387203,\n          0.07112447917461395,\n          -0.12925799190998077,\n          -0.03820851817727089,\n          0.22050751745700836,\n          0.19380871951580048,\n          -0.14906930923461914,\n          0.16994433104991913,\n          -0.0308974739164114,\n          -0.0971590057015419,\n          0.04362250119447708,\n          -0.03274126723408699,\n          0.08698645979166031,\n          -0.09849371761083603,\n          0.04764360189437866,\n          -0.017240118235349655,\n          0.08262528479099274,\n          0.0040047550573945045,\n          0.1820852905511856,\n          -0.24041011929512024,\n          -0.05690561234951019,\n          -0.07320531457662582,\n          0.07184021919965744,\n          -0.11587467044591904,\n          0.06611733138561249,\n          0.04691815376281738,\n          -0.09590533375740051,\n          -0.07177133858203888,\n          0.2021479606628418,\n          -0.029307160526514053,\n          0.0022750445641577244,\n          -0.13753801584243774,\n          0.07947014272212982,\n          -0.018867958337068558,\n          0.02580343745648861,\n          -0.11479823291301727,\n          0.08818550407886505,\n          -0.05761965364217758,\n          0.29467448592185974,\n          -0.020573871210217476,\n          0.10719917714595795,\n          0.0674787238240242,\n          -0.21910299360752106,\n          -0.10424784570932388,\n          -0.05166369676589966,\n          0.07482614368200302,\n          0.09548874944448471,\n          0.16565583646297455,\n          0.06946326047182083,\n          -0.15518254041671753,\n          0.15225088596343994,\n          -0.059569139033555984,\n          0.17331576347351074,\n          -0.15469063818454742,\n          -0.011736918240785599,\n          -0.0743703693151474,\n          -0.02521207369863987,\n          0.21097147464752197,\n          0.015317046083509922,\n          -0.10350048542022705,\n          -0.20174823701381683,\n          -0.027751153334975243,\n          0.34808358550071716,\n          -0.1496248096227646,\n          -0.09757106006145477,\n          -0.07120630890130997,\n          -0.04872015118598938,\n          -0.1251097172498703,\n          0.19881290197372437,\n          -0.12300252169370651,\n          -0.1502315253019333,\n          -0.0363832451403141,\n          -0.08398815989494324,\n          0.012706249952316284,\n          0.060364462435245514,\n          0.06915893405675888,\n          0.03780388832092285,\n          -0.01439569890499115,\n          0.08950001001358032,\n          -0.1820613294839859,\n          0.10769841074943542,\n          0.13988932967185974,\n          -0.01807638816535473,\n          -0.06515968590974808,\n          -0.2280558943748474,\n          -0.10001692920923233,\n          -0.09029679745435715,\n          0.16249671578407288,\n          0.06676555424928665,\n          -0.053001731634140015,\n          0.02137565053999424,\n          -0.06049026548862457,\n          0.162782222032547\n        ]\n      },\n      {\n        \"bias\": 0.0370665043592453,\n        \"weights\": [\n          -0.26121050119400024,\n          0.10680586099624634,\n          0.0461772084236145,\n          -0.16212515532970428,\n          0.003976386971771717,\n          0.07709202915430069,\n          -0.12171993404626846,\n          -0.038415055721998215,\n          -0.17564398050308228,\n          0.01424784492701292,\n          0.09240981191396713,\n          0.05835915356874466,\n          0.2542452812194824,\n          -0.0135171664878726,\n          -0.3632211983203888,\n          0.05839724838733673,\n          0.02638164721429348,\n          -0.05304727330803871,\n          -0.00911666452884674,\n          0.18669220805168152,\n          -0.2187773585319519,\n          0.12940119206905365,\n          0.15737488865852356,\n          0.01647847890853882,\n          -0.06713542342185974,\n          -0.07137651741504669,\n          -0.13015934824943542,\n          -0.2209329456090927,\n          -0.0966019555926323,\n          0.25791457295417786,\n          -0.21341100335121155,\n          -0.035787537693977356,\n          -0.14084142446517944,\n          0.0818888247013092,\n          -0.18205320835113525,\n          0.1392735093832016,\n          0.007235927972942591,\n          -0.03819909319281578,\n          -0.12506785988807678,\n          0.008972321636974812,\n          0.024128643795847893,\n          0.07486371695995331,\n          0.0830928161740303,\n          0.10497559607028961,\n          -0.09628425538539886,\n          0.1274452954530716,\n          0.09825670719146729,\n          -0.0626559853553772,\n          -0.11526541411876678,\n          0.026937123388051987,\n          -0.18119166791439056,\n          0.0732833594083786,\n          0.07145573943853378,\n          -0.11325812339782715,\n          0.11196794360876083,\n          0.13995517790317535,\n          -0.04731713607907295,\n          -0.01690071076154709,\n          -0.3084547221660614,\n          -0.010559958405792713,\n          0.02768581360578537,\n          -0.15354549884796143,\n          -0.05657998472452164,\n          -0.0037228295113891363,\n          -0.16291484236717224,\n          -0.1428159922361374,\n          -0.048418737947940826,\n          -0.008436597883701324,\n          0.017381368204951286,\n          0.17255446314811707,\n          -0.0776229053735733,\n          -0.03611063212156296,\n          -0.12281385809183121,\n          -0.07541469484567642,\n          -0.1406850516796112,\n          -0.055661462247371674,\n          -0.16544950008392334,\n          -0.014977896586060524,\n          -0.30811694264411926,\n          0.13425123691558838,\n          -0.10105883330106735,\n          -0.011093179695308208,\n          0.02761003002524376,\n          0.1308542788028717,\n          0.14720095694065094,\n          0.06768316775560379,\n          -0.12713392078876495,\n          0.08260264992713928,\n          -0.12877966463565826,\n          0.04395975172519684,\n          -0.0896310806274414,\n          -0.09982939809560776,\n          -0.04898013547062874,\n          0.1607513129711151,\n          0.07599145919084549,\n          0.04804789274930954,\n          -0.09473568201065063,\n          -0.042790886014699936,\n          -0.05863460153341293,\n          -0.02368701808154583,\n          -0.10920858383178711,\n          0.02913752570748329,\n          0.10707156360149384,\n          0.2899341881275177,\n          -0.0159510038793087,\n          -0.09972861409187317,\n          -0.025558440014719963,\n          0.008900151588022709,\n          -0.20323482155799866,\n          0.13385596871376038,\n          -0.03748917579650879,\n          -0.03837504982948303,\n          -0.17934364080429077,\n          0.20230770111083984,\n          -0.196036696434021,\n          -0.00694623775780201,\n          -0.1302284449338913,\n          -0.08840484172105789,\n          -0.10602930933237076,\n          0.12468640506267548,\n          -0.11644363403320312,\n          0.025978771969676018,\n          -0.005548249464482069,\n          0.2190469205379486,\n          0.023829400539398193,\n          0.08599808812141418,\n          0.05084935575723648,\n          0.2142515927553177,\n          -0.22909900546073914,\n          0.006318274885416031,\n          -0.21812517940998077,\n          0.004667122382670641,\n          -0.11009600013494492,\n          0.10025832056999207,\n          0.07672252506017685,\n          0.05106695368885994,\n          0.05383049324154854,\n          0.045482657849788666,\n          -0.2300644963979721,\n          0.0935501977801323,\n          -0.06428369134664536,\n          -0.1584600806236267,\n          -0.19616837799549103,\n          0.30590179562568665,\n          0.06762436032295227,\n          -0.03887428715825081,\n          -0.11756851524114609,\n          0.22885584831237793,\n          -0.10290771722793579,\n          0.04584900289773941,\n          0.014222455210983753,\n          -0.0825687050819397,\n          -0.2224753350019455,\n          -0.03995548188686371,\n          -0.07024715095758438,\n          -0.031246084719896317,\n          0.12813539803028107,\n          0.12576213479042053,\n          -0.1577892154455185,\n          -0.10511001199483871\n        ]\n      },\n      {\n        \"bias\": 0.03854462504386902,\n        \"weights\": [\n          -0.09666208177804947,\n          0.029394706711173058,\n          -0.1254337877035141,\n          0.11446832120418549,\n          0.08943864703178406,\n          0.04882677271962166,\n          0.0020381093490868807,\n          -0.09975072741508484,\n          0.09895923733711243,\n          -0.13372060656547546,\n          -0.1110915094614029,\n          -0.07699407637119293,\n          -0.08014658838510513,\n          -0.0859091728925705,\n          0.03857537731528282,\n          0.10735710710287094,\n          -0.0371607281267643,\n          -0.12063384801149368,\n          0.07624576985836029,\n          0.038483791053295135,\n          -0.10902794450521469,\n          0.022159377112984657,\n          0.13537147641181946,\n          -0.06897123157978058,\n          0.18731948733329773,\n          0.06551744788885117,\n          -0.033693280071020126,\n          -0.1659877449274063,\n          0.00724980840459466,\n          -0.00790323968976736,\n          0.15806816518306732,\n          -0.003911122214049101,\n          -0.11398297548294067,\n          0.08504553884267807,\n          -0.011943445540964603,\n          -0.0021410456392914057,\n          -0.25380584597587585,\n          0.09608065336942673,\n          0.16827677190303802,\n          -0.18283317983150482,\n          -0.16612882912158966,\n          0.018003389239311218,\n          0.08439077436923981,\n          0.03611861914396286,\n          -0.04194595664739609,\n          -0.14725127816200256,\n          -0.1306677907705307,\n          0.0406867153942585,\n          0.0784306675195694,\n          -0.13322554528713226,\n          -0.05561885982751846,\n          0.0038844668306410313,\n          0.021333331242203712,\n          -0.0033197312150150537,\n          0.12906287610530853,\n          -0.10337760299444199,\n          0.07833029329776764,\n          0.08704245835542679,\n          0.17515510320663452,\n          0.09370747208595276,\n          -0.12477362900972366,\n          0.16433316469192505,\n          -0.11719950288534164,\n          0.2011999934911728,\n          -0.02053927257657051,\n          0.10993778705596924,\n          -0.07146348804235458,\n          0.2645721137523651,\n          0.1467093527317047,\n          0.1399621069431305,\n          -0.1954391449689865,\n          -0.025107722729444504,\n          0.14887191355228424,\n          0.10378609597682953,\n          0.001424496527761221,\n          -0.11814416944980621,\n          -0.06833596527576447,\n          -0.07151030749082565,\n          0.07245617359876633,\n          0.15705761313438416,\n          0.042368583381175995,\n          0.030194761231541634,\n          -0.06286198645830154,\n          0.004694900941103697,\n          -0.018568556755781174,\n          -0.10706563293933868,\n          0.11104834079742432,\n          -0.12686151266098022,\n          -0.04825436323881149,\n          0.2427487075328827,\n          -0.028469813987612724,\n          0.041383400559425354,\n          0.22761604189872742,\n          0.11543919891119003,\n          0.07864727824926376,\n          -0.02435583993792534,\n          -0.144240140914917,\n          -0.08225312829017639,\n          0.12744276225566864,\n          -0.09187740832567215,\n          0.06431471556425095,\n          -0.07465823739767075,\n          0.05263656750321388,\n          0.09415699541568756,\n          0.021273881196975708,\n          0.13154473900794983,\n          -0.17318099737167358,\n          -0.10128828883171082,\n          0.181057408452034,\n          -0.010079347528517246,\n          -0.08375677466392517,\n          0.026337042450904846,\n          -0.026335477828979492,\n          0.11287073791027069,\n          0.04385574162006378,\n          0.07973909378051758,\n          -0.025874832645058632,\n          -0.023699169978499413,\n          -0.01767638884484768,\n          -0.05727190524339676,\n          -0.14186827838420868,\n          -0.04676585644483566,\n          0.14679542183876038,\n          0.11656296253204346,\n          -0.08118315786123276,\n          -0.09438435733318329,\n          -0.27734941244125366,\n          -0.05376912280917168,\n          0.15657515823841095,\n          -0.004638702142983675,\n          0.13610883057117462,\n          -0.04935608431696892,\n          0.12849493324756622,\n          0.03019319288432598,\n          0.08441486209630966,\n          -0.07159248739480972,\n          -0.1660023182630539,\n          -0.05452330410480499,\n          0.004930651281028986,\n          0.017215538769960403,\n          -0.043742891401052475,\n          0.23605124652385712,\n          0.028741635382175446,\n          -0.14261862635612488,\n          -0.15088991820812225,\n          0.01460717711597681,\n          -0.06834860891103745,\n          -0.08679278194904327,\n          -0.21159447729587555,\n          0.1392872929573059,\n          -0.03119533881545067,\n          0.20758967101573944,\n          0.019692832604050636,\n          0.02798999845981598,\n          0.023634497076272964,\n          -0.07321225851774216,\n          -0.10218268632888794,\n          0.09177958220243454,\n          0.1011536568403244,\n          -0.02782781608402729\n        ]\n      },\n      {\n        \"bias\": -0.21957211196422577,\n        \"weights\": [\n          -0.08631733804941177,\n          -0.09169624745845795,\n          -0.0868954285979271,\n          -0.2362481951713562,\n          -0.01671820506453514,\n          0.19936442375183105,\n          0.025327913463115692,\n          0.18400217592716217,\n          -0.050545986741781235,\n          0.024359596893191338,\n          0.09668876975774765,\n          -0.2641465663909912,\n          0.0683712363243103,\n          -0.06481257826089859,\n          -0.1372736543416977,\n          -0.06165653467178345,\n          0.09512709826231003,\n          -0.007064086385071278,\n          0.02610013075172901,\n          -0.11298362910747528,\n          0.13367030024528503,\n          -0.09786675125360489,\n          0.030519912019371986,\n          0.09724839776754379,\n          0.12115022540092468,\n          0.2557755410671234,\n          -0.03782111778855324,\n          0.16955135762691498,\n          0.01413385197520256,\n          0.030685709789395332,\n          -0.10797213017940521,\n          -0.15391862392425537,\n          -0.00028278533136472106,\n          0.053137414157390594,\n          0.006760092917829752,\n          0.16912689805030823,\n          0.10216201096773148,\n          0.017426462844014168,\n          0.044181615114212036,\n          -0.2847808003425598,\n          0.09711595624685287,\n          0.012986400164663792,\n          -0.12308140099048615,\n          -0.1203189417719841,\n          -0.084618479013443,\n          0.16808536648750305,\n          -0.003849987406283617,\n          0.1495172381401062,\n          -0.07037550210952759,\n          -0.12532570958137512,\n          0.1818188577890396,\n          -0.09584223479032516,\n          -0.15509819984436035,\n          -0.07505150884389877,\n          0.005048006772994995,\n          0.19233135879039764,\n          0.018545635044574738,\n          0.18574918806552887,\n          -0.21432122588157654,\n          -0.20721225440502167,\n          0.18027199804782867,\n          -0.08862737566232681,\n          -0.11541688442230225,\n          -0.32148972153663635,\n          0.15248200297355652,\n          0.17131578922271729,\n          0.11279366165399551,\n          -0.020592020824551582,\n          0.09154266864061356,\n          -0.12984587252140045,\n          0.07451387494802475,\n          -0.12150340527296066,\n          0.1821117401123047,\n          -0.06719622015953064,\n          -0.06629617512226105,\n          0.05715804919600487,\n          -0.12847685813903809,\n          0.22482816874980927,\n          0.13104964792728424,\n          0.04575655981898308,\n          -0.061889708042144775,\n          0.07622509449720383,\n          -0.0012407713802531362,\n          -0.24739132821559906,\n          0.1261616200208664,\n          0.21985629200935364,\n          -0.02537592127919197,\n          0.07340868562459946,\n          0.11279113590717316,\n          0.20221973955631256,\n          0.11219894140958786,\n          0.168039932847023,\n          -0.12270182371139526,\n          -0.261880099773407,\n          -8.49431671667844e-06,\n          -0.07801557332277298,\n          -0.07929310947656631,\n          0.1982761174440384,\n          0.13327869772911072,\n          -0.0644545927643776,\n          0.018913015723228455,\n          0.06036343425512314,\n          0.12980662286281586,\n          -0.01493810210376978,\n          0.16211330890655518,\n          0.04009845852851868,\n          -0.1935233473777771,\n          0.27241766452789307,\n          0.05454820767045021,\n          0.06039433181285858,\n          0.013994371518492699,\n          -0.06063270568847656,\n          -0.11903529614210129,\n          0.07067041844129562,\n          0.11669784784317017,\n          0.16885024309158325,\n          0.00928397849202156,\n          0.1473366767168045,\n          0.13088810443878174,\n          0.17934522032737732,\n          -0.004325420595705509,\n          -0.027260515838861465,\n          -0.15746904909610748,\n          0.2920587956905365,\n          0.10150379687547684,\n          0.27921855449676514,\n          0.3468317985534668,\n          0.2332412302494049,\n          0.016965227201581,\n          -0.02529432810842991,\n          0.09169992059469223,\n          -0.03665655851364136,\n          -0.12456502765417099,\n          0.15592020750045776,\n          0.06345918029546738,\n          -0.03025202266871929,\n          0.011918516829609871,\n          0.14057765901088715,\n          -0.0870533436536789,\n          0.03265158459544182,\n          -0.004262031055986881,\n          0.10487637668848038,\n          -0.031096341088414192,\n          -0.03632301092147827,\n          -0.007921137847006321,\n          0.09567331522703171,\n          0.09852716326713562,\n          0.028825316578149796,\n          -0.14790326356887817,\n          -0.09557581692934036,\n          -0.03719637170433998,\n          -0.059132400900125504,\n          -0.15941011905670166,\n          0.058650240302085876,\n          0.05947893485426903,\n          0.30133211612701416,\n          0.020923253148794174,\n          0.030720166862010956,\n          0.16891704499721527,\n          -0.04065362364053726\n        ]\n      }\n    ]\n  }\n]"
  },
  {
    "path": "public/global.css",
    "content": ":root {\n\t--outer-shadow: 0 .5rem 1rem rgba(0,0,0,.15);\n\t--outer-shadow-sm: 0 .125rem .25rem rgba(0,0,0,.075);\n\t--outer-shadow-lg: 0 1rem 3rem rgba(0,0,0,.175);\n\t--red: rgb(255, 59, 48);\n\t--blue: rgb(0, 122, 255);\n\t--green: rgb(52, 199, 89);\n\t--teal: rgb(90, 200, 250);\n\t--light-gray: rgb(250, 250, 250);\n\t--middle-gray: rgb(190, 190, 190);\n\t--dark-gray: rgb(155, 155, 155);\n\t--deep-gray: rgb(100, 100, 100);\n}\n\nhtml, body {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n\tscroll-behavior: smooth;\n}\n\nbody {\n\tcolor: #333;\n\tmargin: 0;\n\tpadding: 0;\n\tbox-sizing: border-box;\n\tfont-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n}\n\n"
  },
  {
    "path": "public/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width\" />\n\n    <!-- Primary Meta Tags -->\n    <meta name=\"title\" content=\"CNN Explainer\" />\n    <meta\n      name=\"description\"\n      content=\"An interactive visualization system designed to help non-experts learn about Convolutional Neural Networks (CNNs).\"\n    />\n\n    <!-- Open Graph / Facebook -->\n    <meta property=\"og:type\" content=\"website\" />\n    <meta\n      property=\"og:url\"\n      content=\"https://poloclub.github.io/cnn-explainer/\"\n    />\n    <meta property=\"og:title\" content=\"CNN Explainer\" />\n    <meta\n      property=\"og:description\"\n      content=\"An interactive visualization system designed to help non-experts learn about Convolutional Neural Networks (CNNs).\"\n    />\n    <meta\n      property=\"og:image\"\n      content=\"https://poloclub.github.io/cnn-explainer/assets/figures/preview.png\"\n    />\n\n    <!-- Twitter -->\n    <meta property=\"twitter:card\" content=\"summary_large_image\" />\n    <meta\n      property=\"twitter:url\"\n      content=\"https://poloclub.github.io/cnn-explainer/\"\n    />\n    <meta property=\"twitter:title\" content=\"CNN Explainer\" />\n    <meta\n      property=\"twitter:description\"\n      content=\"An interactive visualization system designed to help non-experts learn about Convolutional Neural Networks (CNNs).\"\n    />\n    <meta\n      property=\"twitter:image\"\n      content=\"https://poloclub.github.io/cnn-explainer/assets/figures/preview.png\"\n    />\n    <meta property=\"twitter:site\" content=\"@jay4w\" />\n    <meta property=\"twitter:creator\" content=\"@jay4w\" />\n\n    <title>CNN Explainer</title>\n\n    <link rel=\"icon\" type=\"image/png\" href=\"/assets/img/favicon.png\" />\n    <link rel=\"stylesheet\" href=\"/global.css\" />\n    <link rel=\"stylesheet\" href=\"/bundle.css\" />\n\n    <link\n      rel=\"stylesheet\"\n      href=\"https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/css?family=Neucha&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <script src=\"https://d3js.org/d3.v5.js\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0/dist/tf.min.js\"></script>\n    <script\n      defer\n      src=\"https://use.fontawesome.com/releases/v5.3.1/js/all.js\"\n    ></script>\n    <script src=\"https://polyfill.io/v3/polyfill.min.js?features=es6\"></script>\n    <script src=\"https://cdn.jsdelivr.net/gh/cferdinandi/smooth-scroll@15/dist/smooth-scroll.polyfills.min.js\"></script>\n    <script\n      id=\"MathJax-script\"\n      src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js\"\n    ></script>\n\n    <script\n      type=\"module\"\n      src=\"https://unpkg.com/recommender-overlay/dist/recommender-overlay.es.js\"\n    ></script>\n\n    <script defer src=\"/bundle.js\"></script>\n\n    <style>\n      @font-face {\n        font-family: 'A Love of Thunder';\n        src: URL('/assets/font/A Love of Thunder.ttf') format('truetype');\n      }\n    </style>\n  </head>\n\n  <body></body>\n</html>\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import svelte from 'rollup-plugin-svelte';\nimport resolve from 'rollup-plugin-node-resolve';\nimport commonjs from 'rollup-plugin-commonjs';\nimport livereload from 'rollup-plugin-livereload';\nimport { terser } from 'rollup-plugin-terser';\nimport rollup_start_dev from './rollup_start_dev';\nimport replace from '@rollup/plugin-replace';\n\nconst production = !process.env.ROLLUP_WATCH;\n\nexport default {\n\tinput: 'src/main.js',\n\toutput: {\n\t\tsourcemap: true,\n\t\tformat: 'iife',\n\t\tname: 'app',\n\t\tfile: 'public/bundle.js'\n\t},\n\tplugins: [\n\t\tsvelte({\n\t\t\t// enable run-time checks when not in production\n\t\t\tdev: !production,\n\t\t\t// we'll extract any component CSS out into\n\t\t\t// a separate file — better for performance\n\t\t\tcss: css => {\n\t\t\t\tcss.write('bundle.css');\n\t\t\t}\n\t\t}),\n\n        replace({PUBLIC_URL: production ? '/cnn-explainer' : ''}),\n\n\t\t// If you have external dependencies installed from\n\t\t// npm, you'll most likely need these plugins. In\n\t\t// some cases you'll need additional configuration —\n\t\t// consult the documentation for details:\n\t\t// https://github.com/rollup/rollup-plugin-commonjs\n\t\tresolve({\n\t\t\tbrowser: true,\n\t\t\tdedupe: importee => importee === 'svelte' || importee.startsWith('svelte/')\n\t\t}),\n\t\tcommonjs(),\n\n\t\t// In dev mode, call `npm run start:dev` once\n\t\t// the bundle has been generated\n\t\t!production && rollup_start_dev,\n\n\t\t// Watch the `public` directory and refresh the\n\t\t// browser on changes when not in production\n\t\t!production && livereload('public'),\n\n\t\t// If we're building for production (npm run build\n\t\t// instead of npm run dev), minify\n\t\tproduction && terser()\n\t],\n\twatch: {\n\t\tclearScreen: false\n\t}\n};\n"
  },
  {
    "path": "rollup_start_dev.js",
    "content": "import * as child_process from 'child_process';\n\nlet running_dev_server = false;\n\nexport default {\n\twriteBundle() {\n\t\tif (!running_dev_server) {\n\t\t\trunning_dev_server = true;\n\t\t\tchild_process.spawn('npm', ['run', 'start:dev'], { stdio: ['ignore', 'inherit', 'inherit'], shell: true });\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "src/App.svelte",
    "content": "<script>\n  import Explainer from './Explainer.svelte';\n  import Header from './Header.svelte';\n</script>\n\n<style>\n</style>\n\n<div id=\"app-page\">\n  <recommender-overlay\n    my-brand=\"CNN Explainer\"\n    brands-to-ignore=\"CNN 101\"\n    recommendation-count=\"10\"\n    similar-candidate-count=\"15\"\n    position-left=\"30\"\n    display-delay=\"30000\"\n    homepage-url=\"https://poloclub.github.io/\"\n  />\n  <Header />\n  <Explainer />\n</div>\n"
  },
  {
    "path": "src/Explainer.svelte",
    "content": "<script>\n  import Overview from './overview/Overview.svelte';\n  import { cnnStore } from './stores.js';\n\n  // Enum to control the displaying view\n  const View = {\n    OVERVIEW: 'overview',\n    LAYERVIEW: 'layerview',\n    DETAILVIEW: 'detailview'\n  };\n\n  let mainView = View.OVERVIEW;\n\n  /* Example to read loaded cnn in other components:\n  $: if ( $cnnStore.length != 0) {\n    console.log($cnnStore);\n  }\n  */\n\n</script>\n\n<style>\n#explainer {\n  width: 100%;\n  padding: 0;\n  margin: auto;\n  /* outline: 1px solid var(--g-dark-gray); */\n}\n</style>\n\n<div id='explainer'>\n    <Overview />\n</div>"
  },
  {
    "path": "src/Header.svelte",
    "content": "<script>\n</script>\n\n<style>\n\t#header {\n\t\theight: 50px;\n\t\tdisplay: flex;\n\t\tpadding: 0 20px;\n\t\talign-items: center;\n\t\tbackground: rgb(30, 30, 30);\n    justify-content: space-between;\n\t}\n\n\t#logo {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t}\n\n\t#logo-text {\n\t\tfont-size: 30px;\n\t\tcolor: var(--light-gray);\n\t\tfont-family: 'A Love of Thunder';\n\t\tmargin-right: 10px;\n\t}\n\n\t#svg-logo-tagline {\n\t\tfont-size: 23px;\n\t\tfill: rgb(255, 255, 255);\n\t\tdominant-baseline: baseline;\n\t\tfont-family: 'Neucha';\n\t}\n\n\t.icons {\n\t\tdisplay: flex;\n\t\tjustify-content: flex-start;\n\t\talign-items: center;\n\t}\n\n\t.icon {\n\t\twidth: 27px;\n\t\theight: 27px;\n\t\tmargin-left: 15px;\n\t}\n\n\t.icon a{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.icon img {\n    width: 100%;\n    height: 100%;\n    object-fit: contain;\n\t}\n</style>\n\n<div id=\"header\">\n\n  <div id=\"logo\">\n    <div id=\"logo-text\">\n      CNN Explainer\n    </div>\n\n\t\t<svg width=\"510px\" height=\"50px\">\n\t\t\t<defs>\n\t\t\t\t<filter x=\"0%\" y=\"0%\" width=\"100%\" height=\"100%\" filterUnits=\"objectBoundingBox\" id=\"chalk-texture\">\n\t\t\t\t\t<feTurbulence type=\"fractalNoise\" baseFrequency=\"2\" numOctaves=\"5\" stitchTiles=\"stitch\" result=\"f1\">\n\t\t\t\t\t</feTurbulence>\n\t\t\t\t\t<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0, 0 0 0 0 0, 0 0 0 0 0, 0 0 0 -1.5 1.5\" result=\"f2\">\n\t\t\t\t\t</feColorMatrix>\n\t\t\t\t\t<feComposite operator=\"in\" in2=\"f2\" in=\"SourceGraphic\" result=\"f3\">\n\t\t\t\t\t</feComposite>\n\t\t\t\t</filter>\n\t\t\t</defs>\n\n\t\t\t<g filter=\"url(#chalk-texture)\" transform=\"translate(0, 35)\">\n\t\t\t\t<text id=\"svg-logo-tagline\">\n\t\t\t\t\tLearn Convolutional Neural Network (CNN) in your browser! \n\t\t\t\t</text>\n\t\t\t</g>\n\t\t</svg>\n  </div>\n\n\t<div class=\"icons\">\n\t\n\t\t<div class=\"icon\" title=\"Research paper\">\n\t\t\t<a target=\"_blank\" href=\"https://arxiv.org/abs/2004.15004\">\n\t\t\t\t<img src=\"PUBLIC_URL/assets/img/pdf.png\" alt=\"pdf icon\"/>\n\t\t\t</a>\n\t\t</div>\n\n\t\t<div class=\"icon\" title=\"Demo video\">\n\t\t\t<a target=\"_blank\" href=\"https://youtu.be/HnWIHWFbuUQ\">\n\t\t\t\t<img src=\"PUBLIC_URL/assets/img/youtube.png\" alt=\"youtube icon\"/>\n\t\t\t</a>\n\t\t</div>\n\n\t\t<div class=\"icon\" title=\"Open-source code\">\n\t\t\t<a target=\"_blank\" href=\"https://github.com/poloclub/cnn-explainer\">\n\t\t\t\t<img src=\"PUBLIC_URL/assets/img/github.png\" alt=\"github icon\"/>\n\t\t\t</a>\n\t\t</div>\n\n\t</div>\n</div>"
  },
  {
    "path": "src/article/Article.svelte",
    "content": "<script>\n\timport HyperparameterView from '../detail-view/Hyperparameterview.svelte';\n  import Youtube from './Youtube.svelte';\n\n\tlet softmaxEquation = `$$\\\\text{Softmax}(x_{i}) = \\\\frac{\\\\exp(x_i)}{\\\\sum_j \\\\exp(x_j)}$$`;\n\tlet reluEquation = `$$\\\\text{ReLU}(x) = \\\\max(0,x)$$`;\n\n  let currentPlayer;\n</script>\n\n<style>\n\t#description {\n    margin-bottom: 60px;\n    margin-left: auto;\n    margin-right: auto;\n    max-width: 78ch;\n  }\n\n  #description h2 {\n    color: #444;\n    font-size: 40px;\n    font-weight: 450;\n    margin-bottom: 12px;\n    margin-top: 60px;\n  }\n\n  #description h4 {\n    color: #444;\n    font-size: 32px;\n    font-weight: 450;\n    margin-bottom: 8px;\n    margin-top: 44px;\n  }\n\n  #description h6 {\n    color: #444;\n    font-size: 24px;\n    font-weight: 450;\n    margin-bottom: 8px;\n    margin-top: 44px;\n  }\n\n  #description p {\n    margin: 16px 0;\n  }\n\n  #description p img {\n    vertical-align: middle;\n  }\n\n  #description .figure-caption {\n    font-size: 13px;\n    margin-top: 5px;\n  }\n\n  #description ol {\n    margin-left: 40px;\n  }\n\n  #description p, \n  #description div,\n  #description li {\n    color: #555;\n    font-size: 17px;\n    line-height: 1.6;\n  }\n\n  #description small {\n    font-size: 12px;\n  }\n\n  #description ol li img {\n    vertical-align: middle;\n  }\n\n  #description .video-link {\n    color: #3273DC;\n    cursor: pointer;\n    font-weight: normal;\n    text-decoration: none;\n  }\n\n  #description ul {\n      list-style-type: disc;\n      margin-top: -10px;\n      margin-left: 40px;\n      margin-bottom: 15px;\n  }\n    \n  #description a:hover, \n  #description .video-link:hover {\n    text-decoration: underline;\n  }\n\n  .figure, .video {\n    width: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n  }\n</style>\n\n<body>\n  <div id=\"description\">\n    <h2>What is a Convolutional Neural Network?</h2>\n    <p>\n\t\tIn machine learning, a classifier assigns a class label to a data point.  For example, an <em>image classifier</em> produces a class label (e.g, bird, plane) for what objects exist within an image.  A <em>convolutional neural network</em>, or CNN for short, is a type of classifier, which excels at solving this problem!\n\t </p>\n  \t<p>\n  \t\tA CNN is a neural network: an algorithm used to recognize patterns in data. Neural Networks in general are composed of a collection of neurons that are organized in layers, each with their own learnable weights and biases.  Let’s break down a CNN into its basic building blocks.\n  \t</p>\n  \t<ol>\n  \t\t<li>A <strong>tensor</strong> can be thought of as an n-dimensional matrix.  In the CNN above, tensors will be 3-dimensional with the exception of the output layer.</li>\n  \t\t<li>A <strong>neuron</strong> can be thought of as a function that takes in multiple inputs and yields a single output.  The outputs of neurons are represented above as the <span style=\"color:#FF7577;\">red</span> &rarr; <span style=\"color:#60A7D7;\">blue</span> <strong>activation maps</strong>.</li>\n  \t\t<li>A <strong>layer</strong> is simply a collection of neurons with the same operation, including the same hyperparameters.</li>\n  \t\t<li><strong>Kernel weights and biases</strong>, while unique to each neuron, are tuned during the training phase, and allow the classifier to adapt to the problem and dataset provided.  They are encoded in the visualization with a <span style=\"color:#BC8435;\">yellow</span> &rarr; <span style=\"color:#39988F;\">green</span> diverging colorscale.  The specific values can be viewed in the <em>Interactive Formula View</em> by clicking a neuron or by hovering over the kernel/bias in the <em>Convolutional Elastic Explanation View</em>.</li>\n  \t\t<li>A CNN conveys a <strong>differentiable score function</strong>, which is represented as <strong>class scores</strong> in the visualization on the output layer.</li>\n  \t</ol> \n  \t<p>\n  \t\tIf you have studied neural networks before, these terms may sound familiar to you.  So what makes a CNN different? CNNs utilize a special type of layer, aptly named a convolutional layer, that makes them well-positioned to learn from image and image-like data.  Regarding image data, CNNs can be used for many different computer vision tasks, such as <a href=\"http://ijcsit.com/docs/Volume%207/vol7issue5/ijcsit20160705014.pdf\" title=\"CNN Applications\">image processing, classification, segmentation, and object detection</a>.\n  \t</p>  \n  \t<p>\n  \t\tIn CNN Explainer, you can see how a simple CNN can be used for image classification.  Because of the network’s simplicity, its performance isn’t perfect, but that’s okay! The network architecture, <a href=\"http://cs231n.stanford.edu/\" title=\"Tiny VGG Net presented by Stanford's CS231n\">Tiny VGG</a>, used in CNN Explainer contains many of the same layers and operations used in state-of-the-art CNNs today, but on a smaller scale.  This way, it will be easier to understand getting started.\n      </p>     \n\n      <h2>What does each layer of the network do?</h2>\n      <p>\n  \t\tLet’s walk through each layer in the network.  Feel free to interact with the visualization above by clicking and hovering over various parts of it as you read. \n      </p>\n      <h4 id='article-input'>Input Layer</h4>\n      <p>\n      \tThe input layer (leftmost layer) represents the input image into the CNN.  Because we use RGB images as input, the input layer has three channels, corresponding to the red, green, and blue channels, respectively, which are shown in this layer. Use the color scale when you click on the <img class=\"is-rounded\" width=\"12%\" height=\"12%\" src=\"PUBLIC_URL/assets/figures/network_details.png\" alt=\"network details icon\"/> icon above to display detailed information (on this layer, and others).\n      </p>\n      <h4 id='article-convolution'>Convolutional Layers</h4>\n      <p>\n  \t\tThe convolutional layers are the foundation of CNN, as they contain the learned kernels (weights), which extract features that distinguish different images from one another&mdash;this is what we want for classification!  As you interact with the convolutional layer, you will notice links between the previous layers and the convolutional layers.  Each link represents a unique kernel, which is used for the convolution operation to produce the current convolutional neuron’s output or activation map.  \n  \t</p>\n  \t<p>\n  \t\tThe convolutional neuron performs an elementwise dot product with a unique kernel and the output of the previous layer’s corresponding neuron.  This will yield as many intermediate results as there are unique kernels.  The convolutional neuron is the result of all of the intermediate results summed together with the learned bias.\n  \t</p>\n  \t<p>\n  \t\tFor example, let’s look at the first convolutional layer in the Tiny VGG architecture above.  Notice that there are 10 neurons in this layer, but only 3 neurons in the previous layer.  In the Tiny VGG architecture, convolutional layers are fully-connected, meaning each neuron is connected to every other neuron in the previous layer.  Focusing on the output of the topmost convolutional neuron from the first convolutional layer, we see that there are 3 unique kernels when we hover over the activation map.  \n  \t</p>\n    <div class=\"figure\">\n      <img src=\"PUBLIC_URL/assets/figures/convlayer_overview_demo.gif\" alt=\"clicking on topmost first conv. layer activation map\" width=60% height=60% align=\"middle\"/>\n      <div class=\"figure-caption\">\n  \t\t  Figure 1.  As you hover over the activation map of the topmost node from the first convolutional layer, you can see that 3 kernels were applied to yield this activation map.  After clicking this activation map, you can see the convolution operation occuring with each unique kernel.\n  \t  </div>\n    </div>\n\n  \t<p>\n  \t\tThe size of these kernels is a hyper-parameter specified by the designers of the network architecture.  In order to produce the output of the convolutional neuron (activation map), we must perform an elementwise dot product with the output of the previous layer and the unique kernel learned by the network.  In TinyVGG, the dot product operation uses a stride of 1, which means that the kernel is shifted over 1 pixel per dot product, but this is a hyperparameter that the network architecture designer can adjust to better fit their dataset.  We must do this for all 3 kernels, which will yield 3 intermediate results.  \n  \t</p>\n    <div class=\"figure\">\n      <img src=\"PUBLIC_URL/assets/figures/convlayer_detailedview_demo.gif\" alt=\"clicking on topmost first conv. layer activation map\" />\n      <div class=\"figure-caption\">\n        Figure 2. The kernel being applied to yield the topmost intermediate result for the discussed activation map.\n      </div>\n    </div>\n  \t<p>\n  \t\tThen, an elementwise sum is performed containing all 3 intermediate results along with the bias the network has learned.  After this, the resulting 2-dimensional tensor will be the activation map viewable on the interface above for the topmost neuron in the first convolutional layer.  This same operation must be applied to produce each neuron’s activation map.\n  \t</p>\n  \t<p>\n  \t\tWith some simple math, we are able to deduce that there are 3 x 10 = 30 unique kernels, each of size 3x3, applied in the first convolutional layer.  The connectivity between the convolutional layer and the previous layer is a design decision when building a network architecture, which will affect the number of kernels per convolutional layer.  Click around the visualization to better understand the operations behind the convolutional layer.  See if you can follow the example above!\n    </p>\n    <h6>Understanding Hyperparameters</h6>\n    <p>\n    \t<HyperparameterView/>\n    </p>\n    <ol>\n    \t<li><strong>Padding</strong> is often necessary when the kernel extends beyond the activation map.  Padding conserves data at the borders of activation maps, which leads to better performance, and it can help <a href=\"https://arxiv.org/pdf/1603.07285.pdf\" title=\"See page 13\">preserve the input's spatial size</a>, which allows an architecture designer to build deeper, higher performing networks.  There exist <a href=\"https://arxiv.org/pdf/1811.11718.pdf\" title=\"Outlines major padding techniques\">many padding techniques</a>, but the most commonly used approach is zero-padding because of its performance, simplicity, and computational efficiency.  The technique involves adding zeros symmetrically around the edges of an input.  This approach is adopted by many high-performing CNNs such as <a href=\"https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf\" title=\"AlexNet\">AlexNet</a>.</li>\n    \t<li><strong>Kernel size</strong>, often also referred to as filter size, refers to the dimensions of the sliding window over the input.  Choosing this hyperparameter has a massive impact on the image classification task.  For example, small kernel sizes are able to extract a much larger amount of information containing highly local features from the input.  As you can see on the visualization above, a smaller kernel size also leads to a smaller reduction in layer dimensions, which allows for a deeper architecture.  Conversely, a large kernel size extracts less information, which leads to a faster reduction in layer dimensions, often leading to worse performance.  Large kernels are better suited to extract features that are larger.  At the end of the day, choosing an appropriate kernel size will be dependent on your task and dataset, but generally, smaller kernel sizes lead to better performance for the image classification task because an architecture designer is able to stack <a href=\"https://arxiv.org/pdf/1409.1556.pdf\" title=\"Learn why deeper networks perform better!\">more and more layers together to learn more and more complex features</a>!</li>\n    \t<li><strong>Stride</strong> indicates how many pixels the kernel should be shifted over at a time.  For example, as described in the convolutional layer example above, Tiny VGG uses a stride of 1 for its convolutional layers, which means that the dot product is performed on a 3x3 window of the input to yield an output value, then is shifted to the right by one pixel for every subsequent operation.  The impact stride has on a CNN is similar to kernel size.  As stride is decreased, more features are learned because more data is extracted, which also leads to larger output layers.  On the contrary, as stride is increased, this leads to more limited feature extraction and smaller output layer dimensions.  One responsibility of the architecture designer is to ensure that the kernel slides across the input symmetrically when implementing a CNN.  Use the hyperparameter visualization above to alter stride on various input/kernel dimensions to understand this constraint!</li>\n    </ol>\n    <h4>Activation Functions</h4>\n    <h6 id='article-relu'>ReLU</h6>\n    <p>\n    \tNeural networks are extremely prevalent in modern technology&mdash;because they are so accurate!  The highest performing CNNs today consist of an absurd amount of layers, which are able to learn more and more features.  Part of the reason these groundbreaking CNNs are able to achieve such <a href=\"https://arxiv.org/pdf/1512.03385.pdf\" title=\"ResNet\">tremendous accuracies</a> is because of their non-linearity.  ReLU applies much-needed non-linearity into the model.  Non-linearity is necessary to produce non-linear decision boundaries, so that the output cannot be written as a linear combination of the inputs.  If a non-linear activation function was not present, deep CNN architectures would devolve into a single, equivalent convolutional layer, which would not perform nearly as well.  The ReLU activation function is specifically used as a non-linear activation function, as opposed to other non-linear functions such as <em>Sigmoid</em> because it has been <a href=\"https://arxiv.org/pdf/1906.01975.pdf\" title=\"See page 29\">empirically observed</a> that CNNs using ReLU are faster to train than their counterparts.\n    </p>\n    <p>\n  \tThe ReLU activation function is an elementwise mathematical operation: {reluEquation}\n    </p>\n    <div class=\"figure\">\n    <img src=\"PUBLIC_URL/assets/figures/relu_graph.png\" alt=\"relu graph\" width=\"30%\" height=\"30%\"/>\n      <div class=\"figure-caption\">\n        Figure 3. The ReLU activation function graphed, which disregards all negative data.\n      </div>\n    </div>\n    <p>\n  \tThis activation function is applied elementwise on every value from the input tensor.  For example, if applied ReLU on the value 2.24, the result would be 2.24, since 2.24 is larger than 0.  You can observe how this activation function is applied by clicking a ReLU neuron in the network above.  The Rectified Linear Activation function (ReLU) is performed after every convolutional layer in the network architecture outlined above.  Notice the impact this layer has on the activation map of various neurons throughout the network!\n    </p>\n    <h6 id='article-softmax'>Softmax</h6>\n    <p>\n    \t{softmaxEquation}\n    \tA softmax operation serves a key purpose: making sure the CNN outputs sum to 1.  Because of this, softmax operations are useful to scale model outputs into probabilities.  Clicking on the last layer reveals the softmax operation in the network. Notice how the logits after flatten aren’t scaled between zero to one.  For a visual indication of the impact of each logit (unscaled scalar value), they are encoded using a <span style=\"color:#FFC385;\">light orange</span> &rarr; <span style=\"color:#C44103;\">dark orange</span> color scale.  After passing through the softmax function, each class now corresponds to an appropriate probability! \n    </p>\n    <p>\n    \tYou might be thinking what the difference between standard normalization and softmax is&mdash;after all, both rescale the logits between 0 and 1.  Remember that backpropagation is a key aspect of training neural networks&mdash;we want the correct answer to have the largest “signal.” By using softmax, we are effectively “approximating” argmax while gaining differentiability.  Rescaling doesn’t weigh the max significantly higher than other logits, whereas softmax does.  Simply put, softmax is a “softer” argmax&mdash;see what we did there?\n    </p>\n    <div class=\"figure\">\n    <img src=\"PUBLIC_URL/assets/figures/softmax_animation.gif\" alt=\"softmax interactive formula view\"/>\n      <div class=\"figure-caption\">\n        Figure 4. The <em>Softmax Interactive Formula View</em> allows a user to interact with both the color encoded logits and formula to understand how the prediction scores after the flatten layer are normalized to yield classification scores.\n      </div>\n    </div>\n    <h4 id='article-pooling'>Pooling Layers</h4>\n    <p>\n    \tThere are many types of pooling layers in different CNN architectures, but they all have the purpose of gradually decreasing the spatial extent of the network, which reduces the parameters and overall computation of the network.  The type of pooling used in the Tiny VGG architecture above is Max-Pooling.\n    </p>\n    <p>\n    \tThe Max-Pooling operation requires selecting a kernel size and a stride length during architecture design.  Once selected, the operation slides the kernel with the specified stride over the input while only selecting the largest value at each kernel slice from the input to yield a value for the output.  This process can be viewed by clicking a pooling neuron in the network above.\n    </p>\n    <p>\n    \tIn the Tiny VGG architecture above, the pooling layers use a 2x2 kernel and a stride of 2.  This operation with these specifications results in the discarding of 75% of activations.  By discarding so many values, Tiny VGG is more computationally efficient and avoids overfitting.\n    </p>\n    <h4 id='article-flatten'>Flatten Layer</h4>\n    <p>      \n      This layer converts a three-dimensional layer in the network into a one-dimensional vector to fit the  input of a fully-connected layer for classification.  For example, a 5x5x2 tensor would be converted into a vector of size 50.  The previous convolutional layers of the network extracted the features from the input image, but now it is time to classify the features.  We use the softmax function to classify these features, which requires a 1-dimensional input.  This is why the flatten layer is necessary.  This layer can be viewed by clicking any output class.  \n    </p>\n\n    <h2>Interactive features</h2>\n    <ol>\n    \t<li><strong>Upload your own image</strong> by selecting <img class=\"icon is-rounded\" src=\"PUBLIC_URL/assets/figures/upload_image_icon.png\" alt=\"upload image icon\"/> to understand how your image is classified into the 10 classes.  By analyzing the neurons throughout the network, you can understand the activations maps and extracted features.</li>\n    \t<li><strong>Change the activation map colorscale</strong> to better understand the impact of activations at different levels of abstraction by adjusting <img class=\"is-rounded\" width=\"12%\" height=\"12%\" src=\"PUBLIC_URL/assets/figures/heatmap_scale.png\" alt=\"heatmap\"/>.</li>\n    \t<li><strong>Understand network details</strong> such as layer dimensions and colorscales by clicking the <img class=\"is-rounded\" width=\"12%\" height=\"12%\" src=\"PUBLIC_URL/assets/figures/network_details.png\" alt=\"network details icon\"/> icon.</li>\n    \t<li><strong>Simulate network operations</strong> by clicking the <img class=\"icon is-rounded\" src=\"PUBLIC_URL/assets/figures/play_button.png\" alt=\"play icon\"/> button or interact with the layer slice in the <em>Interactive Formula View</em> by hovering over portions of the input or output to understand the mappings and underlying operations.</li>\n      <li><strong>Learn layer functions</strong> by clicking <img class=\"icon is-rounded\" src=\"PUBLIC_URL/assets/figures/info_button.png\" alt=\"info icon\"/> from the <em>Interactive Formula View</em> to read layer details from the article.</li>\n    </ol> \n\n    <h2>Video Tutorial</h2>\n    <ul>\n      <li class=\"video-link\" on:click={currentPlayer.play(0)}>\n        CNN Explainer Introduction\n        <small>(0:00-0:22)</small>\n      </li>\n      <li class=\"video-link\" on:click={currentPlayer.play(27)}>\n        <em>Overview</em>\n        <small>(0:27-0:37)</small>\n      </li>\n      <li class=\"video-link\" on:click={currentPlayer.play(37)}>\n        Convolutional <em>Elastic Explanation View</em>\n        <small>(0:37-0:46)</small>\n      </li>\n      <li class=\"video-link\" on:click={currentPlayer.play(46)}>\n        Convolutional, ReLU, and Pooling <em>Interactive Formula Views</em>\n        <small>(0:46-1:21)</small>\n      </li>\n      <li class=\"video-link\" on:click={currentPlayer.play(82)}>\n        Flatten <em>Elastic Explanation View</em>\n        <small>(1:22-1:41)</small>\n      </li>\n      <li class=\"video-link\" on:click={currentPlayer.play(101)}>\n        Softmax <em>Interactive Formula View</em>\n        <small>(1:41-2:02)</small>\n      </li>\n      <li class=\"video-link\" on:click={currentPlayer.play(126)}>\n        Engaging Learning Experience: Understanding Classification\n        <small>(2:06-2:28)</small>\n      </li>\n      <li class=\"video-link\" on:click={currentPlayer.play(149)}>\n        Interactive Tutorial Article\n        <small>(2:29-2:54)</small>\n      </li>\n    </ul>\n    <div class=\"video\">\n      <Youtube videoId=\"HnWIHWFbuUQ\" playerId=\"demo_video\" bind:this={currentPlayer}/>\n    </div>\n\n    <h2>How is CNN Explainer implemented?</h2>\n    <p>\n      CNN Explainer uses <a href=\"https://js.tensorflow.org/\"><em>TensorFlow.js</em></a>, an in-browser GPU-accelerated deep learning library to load the pretrained model for visualization.  The entire interactive system is written in Javascript using <a href=\"https://svelte.dev/\"><em>Svelte</em></a> as a framework and <a href=\"https://d3js.org/\"><em>D3.js</em></a> for visualizations. You only need a web browser to get started learning CNNs today!\n    </p>\n\n    <h2>Who developed CNN Explainer?</h2>\n    <p>\n      CNN Explainer was created by \n      <a href=\"https://zijie.wang/\">Jay Wang</a>,\n      <a href=\"https://www.linkedin.com/in/robert-turko/\">Robert Turko</a>, \n      <a href=\"http://oshaikh.com/\">Omar Shaikh</a>,\n      <a href=\"https://haekyu.com/\">Haekyu Park</a>,\n      <a href=\"http://nilakshdas.com/\">Nilaksh Das</a>,\n      <a href=\"https://fredhohman.com/\">Fred Hohman</a>,\n      <a href=\"http://minsuk.com\">Minsuk Kahng</a>, and\n      <a href=\"https://www.cc.gatech.edu/~dchau/\">Polo Chau</a>,\n      which was the result of a research collaboration between \n      Georgia Tech and Oregon State.  We thank Anmol Chhabria, Kaan Sancak, Kantwon Rogers, and the Georgia Tech Visualization Lab for their support and constructive feedback.  This work was supported in part by NSF grants IIS-1563816, CNS-1704701, NASA NSTRF, DARPA GARD, gifts from Intel, NVIDIA, Google, Amazon.\n    </p>\n  </div>\n</body>\n"
  },
  {
    "path": "src/article/Youtube.svelte",
    "content": "<script context=\"module\">\n\tlet iframeApiReady = false;\n\n\timport { setContext, onMount } from \"svelte\";\n\tvar tag = document.createElement(\"script\");\n\ttag.src = \"https://www.youtube.com/iframe_api\";\n\tvar firstScriptTag = document.getElementsByTagName(\"script\")[0];\n\tfirstScriptTag.parentNode.insertBefore(tag, firstScriptTag);\n\n\twindow.onYouTubeIframeAPIReady = () =>\n\twindow.dispatchEvent(new Event(\"iframeApiReady\"));\n</script>\n\n<script>\n\timport { createEventDispatcher } from \"svelte\";\n\timport { getContext } from \"svelte\";\n\texport let videoId;\n\texport let playerId = \"player\";\n\n\tlet player;\n\texport function play(startSecond = 0){\n\t\tplayer.seekTo(startSecond);\n\t\tplayer.playVideo()\n\t}\n\tconst dispatch = createEventDispatcher();\n\twindow.addEventListener(\"iframeApiReady\", function(e) {\n\t\tplayer = new YT.Player(playerId, {\n\t\t\tvideoId,\n\t\t\tevents: {\n\t\t\t\tonReady: onPlayerReady\n\t\t\t}\n\t\t});\n\t});\n\tfunction onPlayerReady(event) {\n      player.mute()\n    }\n</script>\n\n<div id={playerId} />"
  },
  {
    "path": "src/config.js",
    "content": "/* global d3 */\n\nconst layerColorScales = {\n  input: [d3.interpolateGreys, d3.interpolateGreys, d3.interpolateGreys],\n  conv: d3.interpolateRdBu,\n  relu: d3.interpolateRdBu,\n  pool: d3.interpolateRdBu,\n  fc: d3.interpolateGreys,\n  weight: d3.interpolateBrBG,\n  logit: d3.interpolateOranges\n};\n\nlet nodeLength = 40;\n\nexport const overviewConfig = {\n  nodeLength : nodeLength,\n  plusSymbolRadius : nodeLength / 5,\n  numLayers : 12,\n  edgeOpacity : 0.8,\n  edgeInitColor : 'rgb(230, 230, 230)',\n  edgeHoverColor : 'rgb(130, 130, 130)',\n  edgeHoverOuting : false,\n  edgeStrokeWidth : 0.7,\n  intermediateColor : 'gray',\n  layerColorScales: layerColorScales,\n  svgPaddings: {top: 25, bottom: 25, left: 50, right: 50},\n  kernelRectLength: 8/3,\n  gapRatio: 4,\n  overlayRectOffset: 12,\n  classLists: ['lifeboat', 'ladybug', 'pizza', 'bell pepper', 'school bus',\n    'koala', 'espresso', 'red panda', 'orange', 'sport car']\n};"
  },
  {
    "path": "src/detail-view/ActivationAnimator.svelte",
    "content": "<script>\n  import { createEventDispatcher } from 'svelte';\n  import { array1d, getMatrixSliceFromOutputHighlights,\n    getVisualizationSizeConstraint, getMatrixSliceFromInputHighlights, gridData\n  } from './DetailviewUtils.js';\n  import Dataview from './Dataview.svelte';\n\n  export let image;\n  export let output;\n  export let isPaused;\n  export let dataRange;\n\n  const dispatch = createEventDispatcher();\n  const padding = 0;\n  let padded_input_size = image.length + padding * 2;\n  $: padded_input_size = image.length + padding * 2;\n\n  let gridInputMatrixSlice = gridData([[0]]);\n  let gridOutputMatrixSlice = gridData([[0]]);\n  let inputHighlights = array1d(image.length * image.length, (i) => true);\n  let outputHighlights = array1d(output.length * output.length, (i) => true);\n  let interval;\n  $ : {\n    let inputHighlights = array1d(image.length * image.length, (i) => true);\n    let outputHighlights = array1d(output.length * output.length, (i) => true);\n    let interval;\n  }\n\n  let counter;\n\n  // lots of replication between mouseover and start-relu. TODO: fix this.\n  function startRelu() {\n    counter = 0;\n    if (interval) clearInterval(interval);\n    interval = setInterval(() => {\n      if (isPaused) return;\n      const flat_animated = counter % (output.length * output.length);\n      outputHighlights = array1d(output.length * output.length, (i) => false);\n      inputHighlights = array1d(image.length * image.length, (i) => undefined);\n      const animatedH = Math.floor(flat_animated / output.length);\n      const animatedW = flat_animated % output.length;\n      outputHighlights[animatedH * output.length + animatedW] = true;\n      inputHighlights[animatedH * output.length + animatedW] = true;\n      const inputMatrixSlice = getMatrixSliceFromInputHighlights(image, inputHighlights, 1);\n      gridInputMatrixSlice = gridData(inputMatrixSlice);\n      const outputMatrixSlice = getMatrixSliceFromOutputHighlights(output, outputHighlights);\n      gridOutputMatrixSlice = gridData(outputMatrixSlice);\n      counter++;\n    }, 250)\n  }\n\n  function handleMouseover(event) {\n    outputHighlights = array1d(output.length * output.length, (i) => false);\n    const animatedH = event.detail.hoverH;\n    const animatedW = event.detail.hoverW;\n    outputHighlights[animatedH * output.length + animatedW] = true;\n    inputHighlights = array1d(image.length * image.length, (i) => undefined);\n    inputHighlights[animatedH * output.length + animatedW] = true;\n    const inputMatrixSlice = getMatrixSliceFromInputHighlights(image, inputHighlights, 1);\n    gridInputMatrixSlice = gridData(inputMatrixSlice);\n    const outputMatrixSlice = getMatrixSliceFromOutputHighlights(output, outputHighlights);\n    gridOutputMatrixSlice = gridData(outputMatrixSlice);\n    isPaused = true;\n    dispatch('message', {\n      text: isPaused\n    });\n  }\n\n  startRelu();\n  let gridImage = gridData(image)\n  let gridOutput = gridData(output)\n  $ : {\n    startRelu();\n    gridImage = gridData(image)\n    gridOutput = gridData(output)\n  }\n</script>\n\n<style>\n  .column {\n    padding: 5px;\n  }\n</style>\n\n<div class=\"column has-text-centered\">\n  <div class=\"header-text\">\n    Input ({image.length}, {image[0].length})\n  </div>\n  <Dataview on:message={handleMouseover} data={gridImage} highlights={inputHighlights} outputLength={output.length}\n      isKernelMath={false} constraint={getVisualizationSizeConstraint(image.length)} dataRange={dataRange} stride={1}/>  \n</div>\n<div class=\"column has-text-centered\">\n  <span>\n    max(\n    <Dataview data={gridData([[0]])} highlights={outputHighlights} isKernelMath={true} \n    constraint={20} dataRange={dataRange}/>\n    ,\n    <Dataview data={gridInputMatrixSlice} highlights={outputHighlights} isKernelMath={true} \n    constraint={20} dataRange={dataRange}/>\n    )\n    =\n    <Dataview data={gridOutputMatrixSlice} highlights={outputHighlights} isKernelMath={true} \n      constraint={20} dataRange={dataRange}/>\n  </span> \n</div>\n<div class=\"column has-text-centered\">\n  <div class=\"header-text\">\n    Output ({output.length}, {output[0].length})\n  </div>\n  <Dataview on:message={handleMouseover} data={gridOutput} highlights={outputHighlights} isKernelMath={false} \n      outputLength={output.length} constraint={getVisualizationSizeConstraint(output.length)} dataRange={dataRange} stride={1}/>\n</div>"
  },
  {
    "path": "src/detail-view/Activationview.svelte",
    "content": "<script>\n\timport ActivationAnimator from './ActivationAnimator.svelte';\n  import { createEventDispatcher } from 'svelte';\n\n  export let input;\n  export let output;\n  export let dataRange;\n  export let isExited;\n\n  const dispatch = createEventDispatcher();\n  let isPaused = false;\n  \n  function handleClickPause() {\n    isPaused = !isPaused;\n  }\n\n  function handlePauseFromInteraction(event) {\n    isPaused = event.detail.text;\n  }\n\n  function handleClickX() {\n    dispatch('message', {\n      text: true\n    });\n  }\n\n  function handleScroll() {\n    let svgHeight = Number(d3.select('#cnn-svg').style('height').replace('px', '')) + 150;\n    let scroll = new SmoothScroll('a[href*=\"#\"]', {offset: -svgHeight});\n    let anchor = document.querySelector(`#article-relu`);\n    scroll.animateScroll(anchor);\n  }\n</script>\n\n<style>\n  .control-pannel {\n    display: flex;\n    position: relative;\n    flex-direction: column;\n    align-items: center;\n  }\n\n  .buttons {\n    cursor: pointer;\n    position: absolute;\n    top: 0px;\n    right: 0px;\n  }\n\n  .control-button {\n    color: gray;\n    font-size: 15px;\n    opacity: 0.4;\n    cursor: pointer;\n  }\n\n  .control-button:not(:first-child) {\n    margin-left: 5px;\n  }\n\n  .annotation {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    padding-left : 10px;\n    font-size: 12px;\n  }\n\n  .annotation > img {\n    width: 17px;\n    margin-right: 5px;\n  }\n\n  .control-button:hover {\n    opacity: 0.8;\n  }\n\n  .box {\n    padding: 5px 15px 10px 15px;\n  }\n\n  .container {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n\n  .title-text {\n    font-size: 1.2em;\n    font-weight: 500;\n    color: #4a4a4a;\n  }\n</style>\n\n{#if !isExited}\n  <div class=\"container\">\n    <div class=\"box\">\n\n      <div class=\"control-pannel\">\n\n        <div class=\"title-text\">\n          ReLU Activation\n        </div>\n\n        <div class=\"buttons\">\n\n          <div class=\"control-button\" on:click={handleScroll} title=\"Jump to article section\">\n            <i class=\"fas fa-info-circle\"></i>\n          </div>\n\n          <div class=\"play-button control-button\" on:click={handleClickPause} title=\"Play animation\">\n            {@html isPaused ?\n              '<i class=\"fas fa-play-circle play-icon\"></i>' :\n              '<i class=\"fas fa-pause-circle\"></i>'}\n          </div>\n\n          <div class=\"delete-button control-button\" on:click={handleClickX} title=\"Close\">\n              <i class=\"fas control-icon fa-times-circle\"></i>\n          </div>\n        </div>\n\n      </div>\n\n      <div class=\"container is-centered is-vcentered\">\n        <ActivationAnimator on:message={handlePauseFromInteraction} \n          image={input} output={output} isPaused={isPaused}\n          dataRange={dataRange}/>\n      </div>\n\n      <div class=\"annotation\">\n        <img src='PUBLIC_URL/assets/img/pointer.svg' alt='pointer icon'>\n        <div class=\"annotation-text\">\n          <span style=\"font-weight:600\">Hover over</span> the matrices to change pixel.\n        </div>\n      </div>\n\n    </div>\n  </div>\n{/if}"
  },
  {
    "path": "src/detail-view/ConvolutionAnimator.svelte",
    "content": "<script>\n  import { createEventDispatcher } from 'svelte';\n  import { array1d, getMatrixSliceFromOutputHighlights,\n    compute_input_multiplies_with_weight, getDataRange,\n    getVisualizationSizeConstraint, generateOutputMappings,\n    getMatrixSliceFromInputHighlights, gridData\n  } from './DetailviewUtils.js';\n  import Dataview from './Dataview.svelte';\n  import KernelMathView from './KernelMathView.svelte';\n  // image: nxn array -- prepadded.\n  // kernel: mxm array.\n  // stride: int\n  export let stride;\n  export let dilation\n  export let kernel;\n  export let image;\n  export let output;\n  export let isPaused;\n  export let dataRange;\n  export let colorScale;\n  export let isInputInputLayer = false;\n\n  const dispatch = createEventDispatcher();\n  const padding = 0;\n  let padded_input_size = image.length + padding * 2;\n  $: padded_input_size = image.length + padding * 2;\n\n  // Dummy data for original state of component.\n  let testInputMatrixSlice = [];\n  for (let i = 0; i < kernel.length; i++) {\n    testInputMatrixSlice.push([]);\n    for (let j = 0; j < kernel.length; j++) {\n      testInputMatrixSlice[i].push(0)\n    }\n  }\n  testInputMatrixSlice = gridData(testInputMatrixSlice)\n  let testOutputMatrixSlice = gridData([0]);\n\n  let inputHighlights = [];\n  let outputHighlights = array1d(output.length * output.length, (i) => true);\n  let interval;\n  $ : {\n    let inputHighlights = [];\n    let outputHighlights = array1d(output.length * output.length, (i) => true);\n    let interval;\n  }\n\n  let counter;\n  // lots of replication between mouseover and start-conv. TODO: fix this.\n  function startConvolution(stride) {\n    counter = 0;\n    let outputMappings = generateOutputMappings(stride, output, kernel.length, padded_input_size, dilation);\n    if (stride <= 0) return;\n    if (interval) clearInterval(interval);\n    interval = setInterval(() => {\n      if (isPaused) return;\n      const flat_animated = counter % (output.length * output.length);\n      outputHighlights = array1d(output.length * output.length, (i) => false);\n      const animatedH = Math.floor(flat_animated / output.length);\n      const animatedW = flat_animated % output.length;\n      outputHighlights[animatedH * output.length + animatedW] = true;\n      inputHighlights = compute_input_multiplies_with_weight(animatedH, animatedW, padded_input_size, kernel.length, outputMappings, kernel.length)\n      const inputMatrixSlice = getMatrixSliceFromInputHighlights(image, inputHighlights, kernel.length);\n      testInputMatrixSlice = gridData(inputMatrixSlice);\n      const outputMatrixSlice = getMatrixSliceFromOutputHighlights(output, outputHighlights);\n      testOutputMatrixSlice = gridData(outputMatrixSlice);\n      counter++;\n    }, 250)\n  }\n\n  function handleMouseover(event) {\n    let outputMappings = generateOutputMappings(stride, output, kernel.length, padded_input_size, dilation);\n    outputHighlights = array1d(output.length * output.length, (i) => false);\n    const animatedH = event.detail.hoverH;\n    const animatedW = event.detail.hoverW;\n    outputHighlights[animatedH * output.length + animatedW] = true;\n    inputHighlights = compute_input_multiplies_with_weight(animatedH, animatedW, padded_input_size, kernel.length, outputMappings, kernel.length)\n    const inputMatrixSlice = getMatrixSliceFromInputHighlights(image, inputHighlights, kernel.length);\n    testInputMatrixSlice = gridData(inputMatrixSlice);\n    const outputMatrixSlice = getMatrixSliceFromOutputHighlights(output, outputHighlights);\n    testOutputMatrixSlice = gridData(outputMatrixSlice);\n    isPaused = true;\n    dispatch('message', {\n      text: isPaused\n    });\n  }\n\n  startConvolution(stride);\n  let testImage = gridData(image)\n  let testOutput = gridData(output)\n  let testKernel = gridData(kernel)\n  $ : {\n    startConvolution(stride);\n    testImage = gridData(image)\n    testOutput = gridData(output)\n    testKernel = gridData(kernel)\n  }\n</script>\n\n<style>\n  .column {\n    padding: 5px;\n  }\n</style>\n\n<div class=\"column has-text-centered\">\n  <div class=\"header-text\">\n    Input ({image.length}, {image[0].length})\n  </div>\n  <Dataview on:message={handleMouseover} data={testImage} highlights={inputHighlights} outputLength={output.length}\n      isKernelMath={false} constraint={getVisualizationSizeConstraint(image.length)}\n      dataRange={dataRange} stride={stride} colorScale={colorScale}\n      isInputLayer={isInputInputLayer}/>\n</div>\n<div class=\"column has-text-centered\">\n  <KernelMathView data={testInputMatrixSlice} kernel={testKernel} constraint={getVisualizationSizeConstraint(kernel.length)}\n                  dataRange={dataRange} kernelRange={getDataRange(kernel)} colorScale={colorScale}\n                  isInputLayer={isInputInputLayer}/>\n  <Dataview data={testOutputMatrixSlice} highlights={outputHighlights} isKernelMath={true} \n      constraint={getVisualizationSizeConstraint(kernel.length)} dataRange={dataRange}/>\n</div>\n<div class=\"column has-text-centered\">\n  <div class=\"header-text\">\n    Output ({output.length}, {output[0].length})\n  </div>\n  <Dataview on:message={handleMouseover} data={testOutput} highlights={outputHighlights} isKernelMath={false}\n      outputLength={output.length} constraint={getVisualizationSizeConstraint(output.length)} dataRange={dataRange} stride={stride}/>\n</div>"
  },
  {
    "path": "src/detail-view/Convolutionview.svelte",
    "content": "<script>\n\timport ConvolutionAnimator from './ConvolutionAnimator.svelte';\n  import { singleConv } from '../utils/cnn.js';\n  import { createEventDispatcher } from 'svelte';\n\n  export let input;\n  export let kernel;\n  export let dataRange;\n  export let colorScale = d3.interpolateRdBu;\n  export let isInputInputLayer = false;\n  export let isExited = false;\n  // export let output;\n  \n  const dispatch = createEventDispatcher();\n\tlet stride = 1;\n  const dilation = 1;\n  var isPaused = false;\n  var outputFinal = singleConv(input, kernel, stride);\n  $: if (stride > 0) {\n    try { \n      outputFinal = singleConv(input, kernel, stride);\n    } catch {\n      console.log(\"Cannot handle stride of \" + stride);\n    }\n  }\n  \n  function handleClickPause() {\n    isPaused = !isPaused;\n  }\n\n  function handleScroll() {\n    let svgHeight = Number(d3.select('#cnn-svg').style('height').replace('px', '')) + 150;\n    let scroll = new SmoothScroll('a[href*=\"#\"]', {offset: -svgHeight});\n    let anchor = document.querySelector(`#article-convolution`);\n    scroll.animateScroll(anchor);\n  }\n\n  function handlePauseFromInteraction(event) {\n    isPaused = event.detail.text;\n  }\n\n  function handleClickX() {\n    isExited = true;\n    dispatch('message', {\n      text: isExited\n    });\n  }\n</script>\n\n<style>\n  .control-pannel {\n    display: flex;\n    position: relative;\n    flex-direction: column;\n    align-items: center;\n  }\n\n  .buttons {\n    cursor: pointer;\n    position: absolute;\n    top: 0px;\n    right: 0px;\n  }\n\n  .control-button {\n    color: gray;\n    font-size: 15px;\n    opacity: 0.4;\n    cursor: pointer;\n  }\n\n  .control-button:not(:first-child) {\n    margin-left: 5px;\n  }\n\n  .annotation {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    padding-left : 10px;\n    font-size: 12px;\n  }\n\n  .annotation > img {\n    width: 17px;\n    margin-right: 5px;\n  }\n\n  .control-button:hover {\n    opacity: 0.8;\n  }\n\n  .box {\n    padding: 5px 15px 10px 15px;\n  }\n\n  .container {\n    display: flex;\n    justify-content: space-between;\n    align-items: flex-end;\n  }\n\n  .title-text {\n    font-size: 1.2em;\n    font-weight: 500;\n    color: #4a4a4a;\n  }\n</style>\n\n{#if !isExited}\n  <div class=\"container\" id=\"detailview-container\">\n\n    <!-- old stride input -->\n    <!-- <div class=\"columns is-mobile\">\n      <div class=\"column is-half is-offset-one-quarter\">\n        <div class=\"field is-grouped\">\n          <p class=\"control is-expanded\">\n            <input class=\"input\" type=\"text\" placeholder=\"Stride\" bind:value={stride} />\n          </p>\n          <p class=\"control\">\n            <button class=\"button is-success\" on:click={handleClickPause}>\n              Toggle Movement\n            </button>\n          </p>\n        </div>\n      </div>\n    </div> -->\n\n    <div class=\"box\">\n\n      <div class=\"control-pannel\">\n\n        <div class=\"title-text\">\n          Convolution\n        </div>\n\n        <div class=\"buttons\">\n          <div class=\"control-button\" on:click={handleScroll} title=\"Jump to article section\">\n            <i class=\"fas fa-info-circle\"></i>\n          </div>\n\n          <div class=\"play-button control-button\" on:click={handleClickPause} title=\"Play animation\">\n            {@html isPaused ?\n              '<i class=\"fas fa-play-circle play-icon\"></i>' :\n              '<i class=\"fas fa-pause-circle\"></i>'}\n          </div>\n\n          <div class=\"delete-button control-button\" on:click={handleClickX} title=\"Close\">\n            <i class=\"fas control-icon fa-times-circle\"></i>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"container is-centered\">\n        <ConvolutionAnimator on:message={handlePauseFromInteraction} \n          kernel={kernel} image={input} output={outputFinal} \n          stride={stride} dilation={dilation} isPaused={isPaused}\n          dataRange={dataRange} colorScale={colorScale}\n          isInputInputLayer={isInputInputLayer} />\n      </div>\n\n      <div class=\"annotation\">\n        <img src='PUBLIC_URL/assets/img/pointer.svg' alt='pointer icon'>\n        <div class=\"annotation-text\">\n          <span style=\"font-weight:600\">Hover over</span> the matrices to change kernel position.\n        </div>\n      </div>\n\n    </div>\n  </div>\n{/if}"
  },
  {
    "path": "src/detail-view/Dataview.svelte",
    "content": "<script>\n  export let data;\n  export let highlights;\n  export let isKernelMath;\n  export let constraint;\n  export let dataRange;\n  export let outputLength = undefined;\n  export let stride = undefined;\n  export let colorScale = d3.interpolateRdBu;\n  export let isInputLayer = false;\n\n  import { onMount } from 'svelte';\n  import { onDestroy } from 'svelte';\n  import { beforeUpdate, afterUpdate } from 'svelte';\n  import { createEventDispatcher } from 'svelte';\n\n  let grid_final;\n  const textConstraintDivisor = 2.6;\n  const standardCellColor = \"ddd\";\n  const dispatch = createEventDispatcher();\n\n  let oldHighlight = highlights;\n  let oldData = data;\n\n  const redraw = () => {\n    d3.select(grid_final).selectAll(\"#grid > *\").remove();\n    const constrainedSvgSize = data.length * constraint + 2;\n    var grid = d3.select(grid_final).select(\"#grid\")\n      .attr(\"width\", constrainedSvgSize + \"px\")\n      .attr(\"height\", constrainedSvgSize + \"px\")\n      .append(\"svg\")\n      .attr(\"width\", constrainedSvgSize + \"px\")\n      .attr(\"height\", constrainedSvgSize + \"px\")\n    var row = grid.selectAll(\".row\")\n      .data(data)\n      .enter().append(\"g\")\n      .attr(\"class\", \"row\");\n    var column = row.selectAll(\".square\")\n      .data(function(d) { return d; })\n      .enter().append(\"rect\")\n      .attr(\"class\",\"square\")\n      .attr(\"x\", function(d) { return d.x; })\n      .attr(\"y\", function(d) { return d.y; })\n      .attr(\"width\", function(d) { return d.width; })\n      .attr(\"height\", function(d) { return d.height; })\n      .style(\"opacity\", 0.8)\n      .style(\"fill\", function(d) {\n        let normalizedValue = d.text;\n        if (isInputLayer){\n          normalizedValue = 1 - d.text;\n        } else {\n          normalizedValue = (d.text + dataRange / 2) / dataRange;\n        }\n        return colorScale(normalizedValue);\n      })\n      .on('mouseover', function(d) {\n        if (data.length != outputLength) {\n          dispatch('message', {\n            hoverH: Math.min(Math.floor(d.row / stride), outputLength - 1),\n            hoverW: Math.min(Math.floor(d.col / stride), outputLength - 1)\n          });\n        } else {\n          dispatch('message', {\n            hoverH: Math.min(Math.floor(d.row / 1), outputLength - 1),\n            hoverW: Math.min(Math.floor(d.col / 1), outputLength - 1)\n          });\n        }\n      });\n    if (isKernelMath) {\n      var text = row.selectAll(\".text\")\n        .data(function(d) { return d; })\n        .enter().append(\"text\")\n        .attr(\"class\",\"text\")\n        .style(\"font-size\", Math.floor(constraint / textConstraintDivisor) + \"px\")\n        .attr(\"x\", function(d) { return d.x + d.width / 2; })\n        .attr(\"y\", function(d) { return d.y + d.height / 2; })\n        .style(\"fill\", function(d) {\n        let normalizedValue = d.text;\n          if (isInputLayer){\n            normalizedValue = 1 - d.text;\n          } else {\n            normalizedValue = (d.text + dataRange / 2) / dataRange;\n          }\n          if (normalizedValue < 0.2 || normalizedValue > 0.8) {\n            return 'white';\n          } else {\n            return 'black';\n          }\n        })\n        .style(\"text-anchor\", \"middle\")\n        .style(\"dominant-baseline\", \"middle\")\n        .text(function(d) {\n          return d.text.toString().replace('-', '－');\n        })\n    }\n  }\n\n  afterUpdate(() => {\n    if (data != oldData) {\n      redraw();\n      oldData = data;\n    }\n\n    if (highlights != oldHighlight) {\n      var grid = d3.select(grid_final).select('#grid').select(\"svg\")\n      grid.selectAll(\".square\")\n        .style(\"stroke\", (d) => isKernelMath || (highlights.length && highlights[d.row * data.length + d.col]) ? \"black\" : null )\n      oldHighlight = highlights;\n    }\n\n  });\n\n  onMount(() => {\n    redraw();\n  });\n\n</script>\n\n<div style=\"display: inline-block; vertical-align: middle;\" class=\"grid\"\n  bind:this={grid_final}>\n  <svg id=\"grid\" width=100% height=100%></svg>\n</div>"
  },
  {
    "path": "src/detail-view/DetailviewUtils.js",
    "content": "import { matrixSlice } from '../utils/cnn.js';\n\nexport function array1d(length, f) {\n  return Array.from({length: length}, f ? ((v, i) => f(i)) : undefined);\n}\n\nfunction array2d(height, width, f) {\n  return Array.from({length: height}, (v, i) => Array.from({length: width}, f ? ((w, j) => f(i, j)) : undefined));\n}\n\nexport function generateOutputMappings(stride, output, kernelLength, padded_input_size, dilation) {\n  const outputMapping = array2d(output.length, output.length, (i, j) => array2d(kernelLength, kernelLength));\n  for (let h_out = 0; h_out < output.length; h_out++) {\n    for (let w_out = 0; w_out < output.length; w_out++) {\n      for (let h_kern = 0; h_kern < kernelLength; h_kern++) {\n        for (let w_kern = 0; w_kern < kernelLength; w_kern++) {\n          const h_im = h_out * stride + h_kern * dilation;\n          const w_im = w_out * stride + w_kern * dilation;\n          outputMapping[h_out][w_out][h_kern][w_kern] = h_im * padded_input_size + w_im;\n        }\n      }\n    }\n  }\n  return outputMapping;\n}\n\nexport function compute_input_multiplies_with_weight(hoverH, hoverW, \n                                              padded_input_size, weight_dims, outputMappings, kernelLength) {\n  \n  const [h_weight, w_weight] = weight_dims;\n  const input_multiplies_with_weight = array1d(padded_input_size * padded_input_size);\n  for (let h_weight = 0; h_weight < kernelLength; h_weight++) {\n    for (let w_weight = 0; w_weight < kernelLength; w_weight++) {\n      const flat_input = outputMappings[hoverH][hoverW][h_weight][w_weight];\n      if (typeof flat_input === \"undefined\") continue;\n      input_multiplies_with_weight[flat_input] = [h_weight, w_weight];\n    }\n  }\n  return input_multiplies_with_weight;\n}\n\nexport function getMatrixSliceFromInputHighlights(matrix, highlights, kernelLength) {\n  var indices = highlights.reduce((total, value, index) => {\n  if (value != undefined) total.push(index);\n    return total;\n  }, []);\n  return matrixSlice(matrix, Math.floor(indices[0] / matrix.length), Math.floor(indices[0] / matrix.length) + kernelLength, indices[0] % matrix.length, indices[0] % matrix.length + kernelLength);\n}\n\nexport function getMatrixSliceFromOutputHighlights(matrix, highlights) {\n  var indices = highlights.reduce((total, value, index) => {\n  if (value != false) total.push(index);\n    return total;\n  }, []);\n  return matrixSlice(matrix, Math.floor(indices[0] / matrix.length), Math.floor(indices[0] / matrix.length) + 1, indices[0] % matrix.length, indices[0] % matrix.length + 1);\n}\n\n// Edit these values to change size of low-level conv visualization.\nexport function getVisualizationSizeConstraint(imageLength) {\n  let sizeOfGrid = 150;\n  let maxSizeOfGridCell = 20;\n  return sizeOfGrid / imageLength > maxSizeOfGridCell ? maxSizeOfGridCell : sizeOfGrid / imageLength;\n}\n\nexport function getDataRange(image) {\n  let maxRow = image.map(function(row){ return Math.max.apply(Math, row); });\n  let max = Math.max.apply(null, maxRow);\n  let minRow = image.map(function(row){ return Math.min.apply(Math, row); });\n  let min = Math.min.apply(null, minRow);\n  let range = {\n    range: 2 * Math.max(Math.abs(min), Math.abs(max)),\n    min: min,\n    max: max\n  };\n  return range;\n}\n\nexport function gridData(image, constraint=getVisualizationSizeConstraint(image.length)) {\n  // Constrain grids based on input image size.\n  var data = new Array();\n  var xpos = 1;\n  var ypos = 1;\n  var width = constraint;\n  var height = constraint;\n  for (var row = 0; row < image.length; row++) {\n    data.push( new Array() );\n    for (var column = 0; column < image[0].length; column++) {\n      data[row].push({\n        text: Math.round(image[row][column] * 100) / 100,\n        row: row,\n        col: column,\n        x: xpos,\n        y: ypos,\n        width: width,\n        height: height\n      })\n      xpos += width;\n    }\n    xpos = 1;\n    ypos += height; \n  }\n  return data;\n}"
  },
  {
    "path": "src/detail-view/HyperparameterAnimator.svelte",
    "content": "<script>\n  import { createEventDispatcher } from 'svelte';\n  import { array1d, compute_input_multiplies_with_weight,\n          generateOutputMappings, gridData\n  } from './DetailviewUtils.js';\n  import HyperparameterDataview from './HyperparameterDataview.svelte';\n  import KernelMathView from './KernelMathView.svelte';\n  // image: nxn array -- prepadded.\n  // kernel: mxm array.\n  // stride: int\n  export let stride;\n  export let dilation\n  export let kernel;\n  export let image;\n  export let output;\n  export let isPaused;\n  export let padding;\n  export let isStrideValid;\n\n  const dispatch = createEventDispatcher();\n\n  let inputHighlights = [];\n  let outputHighlights = array1d(output.length * output.length, (i) => true);\n  let interval;\n  $ : {\n    let inputHighlights = [];\n    let outputHighlights = array1d(output.length * output.length, (i) => true);\n    let interval;\n  }\n\n  let counter;\n  // lots of replication between mouseover and start-conv. TODO: fix this.\n  function startConvolution(stride) {\n    counter = 0;\n    isPaused = false;\n    dispatch('message', {\n      text: isPaused\n    });\n    let outputMappings = generateOutputMappings(stride, output, kernel.length, image.length, dilation);\n    if (stride <= 0) return;\n    if (interval) clearInterval(interval);\n    interval = setInterval(() => {\n      if (isPaused || !isStrideValid) return;\n      const flat_animated = counter % (output.length * output.length);\n      outputHighlights = array1d(output.length * output.length, (i) => false);\n      const animatedH = Math.floor(flat_animated / output.length);\n      const animatedW = flat_animated % output.length;\n      outputHighlights[animatedH * output.length + animatedW] = true;\n      inputHighlights = compute_input_multiplies_with_weight(animatedH, animatedW, image.length, kernel.length, outputMappings, kernel.length)\n      counter++;\n    }, 1000)\n  }\n\n  function handleMouseover(event) {\n    let outputMappings = generateOutputMappings(stride, output, kernel.length, image.length, dilation);\n    outputHighlights = array1d(output.length * output.length, (i) => false);\n    const animatedH = event.detail.hoverH;\n    const animatedW = event.detail.hoverW;\n    outputHighlights[animatedH * output.length + animatedW] = true;\n    inputHighlights = compute_input_multiplies_with_weight(animatedH, animatedW, image.length, kernel.length, outputMappings, kernel.length)\n    isPaused = true;\n    dispatch('message', {\n      text: isPaused\n    });\n  }\n\n  // Fix the total grid size to change hyperparameters without changing the grid\n  // size.  This must be two pixels smaller than the HyperparameterDataview svg\n  // size, so that the stroke on the right side of the grid does not get cut off.\n  const gridSize = 198;\n  startConvolution(stride);\n  let testImage = gridData(image, gridSize / image.length);\n  let testOutput = gridData(output, gridSize / output.length);\n  let testKernel = gridData(kernel, gridSize / kernel.length);\n  $ : {\n    startConvolution(stride);\n    testImage = gridData(image, gridSize / image.length);\n    testOutput = gridData(output, gridSize / output.length);\n    testKernel = gridData(kernel, gridSize / kernel.length);\n  }\n</script>\n\n<style>\n  .wrapper {\n    display: flex;\n    flex-direction: row;\n    align-items: flex-end;\n  }\n\n  .column {\n    padding: 5px 10px 10px 10px;\n  }\n\n  .header-text {\n    line-height: 1.1;\n  }\n\n  .header-sub-text {\n    font-size: 13px;\n    color: #9a9a9a;\n    margin-bottom: 2px;\n  }\n</style>\n\n<div class=\"wrapper\">\n  <div class=\"column has-text-centered\">\n    <div class=\"header-text\">\n      Input ({image.length - 2 * padding}, {image.length - 2 * padding}) <br/>\n    </div>\n    <div class=\"header-sub-text\">\n      After-padding ({image.length}, {image.length})\n    </div>\n    <HyperparameterDataview on:message={handleMouseover} data={testImage} highlights={inputHighlights}\n        outputLength={output.length} stride={stride} padding={padding} isStrideValid={isStrideValid}/>\n  </div>\n  <div class=\"column has-text-centered\">\n    <div class=\"header-text\" style=\"padding-top: 27px;\">\n      Output ({output.length}, {output.length})\n    </div>\n    <div class=\"header-sub-text\">\n      &nbsp;\n    </div>\n    <HyperparameterDataview on:message={handleMouseover} data={testOutput} highlights={outputHighlights}\n        outputLength={output.length} stride={stride} padding={padding} isOutput={true} isStrideValid={isStrideValid}/>\n  </div>\n</div>"
  },
  {
    "path": "src/detail-view/HyperparameterDataview.svelte",
    "content": "<script>\n  export let data;\n  export let highlights;\n  export let outputLength;\n  export let stride;\n  export let padding;\n  export let isOutput = false;\n  export let isStrideValid;\n\n  import { onMount } from 'svelte';\n  import { afterUpdate } from 'svelte';\n  import { createEventDispatcher } from 'svelte';\n\n  let grid_final;\n  const standardCellColor = \"#ddd\";\n  const paddingCellColor = \"#aaa\";\n  const dispatch = createEventDispatcher();\n\n  let oldHighlight = highlights;\n  let oldData = data;\n\n  const redraw = () => {\n    d3.select(grid_final).selectAll(\"#grid > *\").remove();\n    var grid = d3.select(grid_final).select(\"#grid\")\n      .attr(\"width\", 200)\n      .attr(\"height\", 200)\n      .append(\"svg\")\n      .attr(\"width\", 200)\n      .attr(\"height\", 200)\n    var row = grid.selectAll(\".row\")\n      .data(data)\n      .enter().append(\"g\")\n      .attr(\"class\", \"row\");\n    var column = row.selectAll(\".square\")\n      .data(function(d) { return d; })\n      .enter().append(\"rect\")\n      .attr(\"class\",\"square\")\n      .attr(\"x\", function(d) { return d.x; })\n      .attr(\"y\", function(d) { return d.y; })\n      .attr(\"width\", function(d) { return d.width; })\n      .attr(\"height\", function(d) { return d.height; })\n      .style(\"opacity\", 0.5)\n      .style(\"stroke\", \"black\")\n      .style(\"fill\", function(d) {\n        // Colors cells appropriately that represent padding.\n        if (!isOutput && (d.row < padding || d.row > data.length - padding - 1\n          || d.col < padding || d.col > data.length - padding - 1)) {\n          return paddingCellColor;\n        } \n        return standardCellColor;\n      })\n      .on('mouseover', function(d) {\n        if (!isStrideValid) return;\n        if (data.length != outputLength) {\n          dispatch('message', {\n            hoverH: Math.min(Math.floor(d.row / stride), outputLength - 1),\n            hoverW: Math.min(Math.floor(d.col / stride), outputLength - 1)\n          });\n        } else {\n          dispatch('message', {\n            hoverH: Math.min(Math.floor(d.row / 1), outputLength - 1),\n            hoverW: Math.min(Math.floor(d.col / 1), outputLength - 1)\n          });\n        }\n      });\n  }\n\n  afterUpdate(() => {\n    if (data != oldData) {\n      redraw();\n      oldData = data;\n    }    \n\n    if (highlights != oldHighlight) {\n      var grid = d3.select(grid_final).select('#grid').select(\"svg\")\n      grid.selectAll(\".square\")\n        .style(\"fill\", function(d) {\n          if (highlights.length && highlights[d.row * data.length + d.col]) {\n            return \"#FF2738\";\n          } else {\n            // Colors cells appropriately that represent padding.\n            if (!isOutput && (d.row < padding || d.row > data.length - padding - 1\n              || d.col < padding || d.col > data.length - padding - 1)) {\n              return paddingCellColor;\n            } \n          return standardCellColor;\n          }\n      })\n      oldHighlight = highlights;\n    }\n  });\n\n  onMount(() => {\n    redraw();\n  });\n\n</script>\n\n<div style=\"display: inline-block; vertical-align: middle;\" class=\"grid\"\n  bind:this={grid_final}>\n  <svg id=\"grid\" width=100% height=100%></svg>\n</div>"
  },
  {
    "path": "src/detail-view/Hyperparameterview.svelte",
    "content": "<script>\n\timport HyperparameterAnimator from './HyperparameterAnimator.svelte';\n  import { singleConv } from '../utils/cnn.js';\n\n  let inputSize = 5;\n  let kernelSize = 2;\n  let padding = 0;\n  let stride = 1;\n  const dilation = 1;\n  let isPaused = false;\n  let isStrideValid = true;\n  $: inputSizeWithPadding = inputSize + 2 * padding;\n\n  function generateSquareArray(arrayDim) {\n    let arr = [];\n    for (let i = 0; i < arrayDim; i++) {\n      arr.push([]);\n      for (let j = 0; j < arrayDim; j++) {\n        arr[i].push(0)\n      }\n    }\n    return arr;\n  }\n\n  function handleClickPause() {\n    isPaused = !isPaused;\n  }\n\n  function handlePauseFromInteraction(event) {\n    isPaused = event.detail.text;\n  }\n\n  // Update input, kernel, and output as user adjusts hyperparameters.\n  let input = generateSquareArray(inputSize + padding * 2);\n  let kernel = generateSquareArray(kernelSize);\n  $: input = generateSquareArray(inputSize + padding * 2);\n  $: kernel = generateSquareArray(kernelSize);\n  let outputFinal = singleConv(input, kernel, stride);\n  $: if (stride > 0) {\n    const stepSize = (inputSizeWithPadding - kernelSize) / stride + 1;\n    let strideNumberInput = document.getElementById(\"strideNumber\");\n    if (Number.isInteger(stepSize)) {\n      outputFinal = singleConv(input, kernel, stride);\n      if (strideNumberInput != null) {\n        strideNumberInput.className = strideNumberInput.className.replace(\"is-danger\", \"\");\n      }\n      isStrideValid = true;\n    } else {\n      if (!strideNumberInput.className.includes(\"is-danger\")) {\n        strideNumberInput.className += \" is-danger\";\n      }\n      isStrideValid = false;\n      console.log(\"Cannot handle stride of \" + stride);\n    }\n  }\n</script>\n\n<style>\n  .control-button {\n    position: absolute;\n    top: 5px;\n    right: 15px;\n    color: gray;\n    font-size: 22px;\n    opacity: 0.4;\n    cursor: pointer;\n  }\n\n  .control-button:hover {\n    opacity: 0.8;\n  }\n\n  .box {\n    padding: 5px 30px 20px 30px;\n    position: relative;\n  }\n\n  .left-part {\n    display: flex;\n    flex-direction: column;\n    margin-top: 30px;\n  }\n\n  .right-part {\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    align-items: center;\n  }\n\n  .content-container {\n    display: flex;\n    justify-content: space-around;\n  }\n\n  .field {\n    padding-top: 5px;\n  }\n\n  .annotation {\n    display: flex;\n    align-items: center;\n    padding-left : 10px;\n  }\n\n  .annotation-text-hyper {\n    font-size: 15px;\n    font-style: italic;\n  }\n\n  .annotation > img {\n    width: 20px;\n    margin-right: 5px;\n  }\n\n  .is-very-small {\n    font-size: 12px; \n  }\n\n  .field {\n    align-items: center;\n  }\n\n  .field-label.is-normal {\n    padding-top: 0;\n  }\n\n  .field:not(:last-child) {\n    margin-bottom: 7px;\n  }\n\n  label {\n    display: inline-block;\n    width: 105px;\n    text-align: right;\n    font-weight: 500;\n    color: #4a4a4a;\n  } \n\n  input[type=number] {\n    width: 50px;\n  }\n\n  input[type=range] {\n    width: 160px;\n  }\n</style>\n\n<div class=\"container has-text-centered\" id=\"detailview-container\">\n  <div class=\"box\">\n\n      <div class=\"control-button\" on:click={handleClickPause}>\n        {@html isPaused ?\n          '<i class=\"fas fa-play-circle play-icon\"></i>' :\n          '<i class=\"fas fa-pause-circle\"></i>'}\n      </div>\n\n    <div class=\"content-container\">\n      <div class=\"left-part\">\n\n        <div class=\"input-row\">\n          <div class=\"field is-horizontal\">\n            <div class=\"field-label is-normal\">\n              <label class=\"label\">Input Size:</label>\n            </div>\n            <input class=\"input is-very-small\" type=\"number\" bind:value={inputSize}\n              min={kernelSize} max={7}>\n          </div>\n\n          <input type=\"range\" bind:value={inputSize}\n            min={kernelSize} max={7}>\n        </div>\n\n        <div class=\"input-row\">\n          <div class=\"field is-horizontal\">\n            <div class=\"field-label is-normal\">\n              <label class=\"label\">Padding:</label>\n            </div>\n            <input class=\"input is-very-small\" type=\"number\" bind:value={padding} min={0}\n              max={kernelSize - 1}>\n          </div>\n\n          <input type=\"range\" bind:value={padding} min={0}\n            max={kernelSize - 1}>\n        </div>\n\n        <div class=\"input-row\">\n          <div class=\"field is-horizontal\">\n            <div class=\"field-label is-normal\">\n              <label class=\"label\">Kernel Size:</label>\n            </div>\n            <input class=\"input is-very-small\" type=\"number\" bind:value={kernelSize} min={padding + 1}\n              max={inputSizeWithPadding}>\n          </div>\n\n          <input type=\"range\" bind:value={kernelSize} min={padding + 1}\n            max={inputSizeWithPadding}>\n        </div>\n\n        <div class=\"input-row\">\n          <div class=\"field is-horizontal\">\n            <div class=\"field-label is-normal\">\n              <label class=\"label\">Stride:</label>\n            </div>\n            <input class=\"input is-very-small\" type=number id=\"strideNumber\" bind:value={stride} min=1\n              max={Math.max(inputSizeWithPadding - kernelSize + 1, 2)}>\n          </div>\n\n          <input type=\"range\" bind:value={stride} min=1\n            max={Math.max(inputSizeWithPadding - kernelSize + 1, 2)}>\n        </div>\n      </div>\n\n        <div class=\"right-part\">\n          <HyperparameterAnimator on:message={handlePauseFromInteraction} \n            kernel={kernel} image={input} output={outputFinal} isStrideValid={isStrideValid}\n            stride={stride} dilation={dilation} padding={padding} isPaused={isPaused}/>\n\n          <div class=\"annotation\">\n            <img src='PUBLIC_URL/assets/img/pointer.svg' alt='pointer icon' width=\"25px\">\n            <div class=\"annotation-text-hyper\">\n              <span style=\"font-weight:600\">Hover over</span> the matrices to change kernel position.\n            </div>\n          </div>\n          \n        </div>\n\n    </div>\n\n\n  </div>\n</div>"
  },
  {
    "path": "src/detail-view/KernelMathView.svelte",
    "content": "<script>\n  export let data;\n  export let kernel;\n  export let constraint;\n  export let dataRange;\n  export let kernelRange;\n  export let colorScale = d3.interpolateRdBu;\n  export let kernelColorScale = d3.interpolateBrBG;\n  export let isInputLayer = false;\n\n  import { onMount } from 'svelte';\n  import { afterUpdate } from 'svelte';\n\n  let gridFinal;\n  let legendFinal;\n  const textConstraintDivisor = 2.6;\n  const multiplicationSymbolPadding = Math.floor(constraint / 3);\n\n  let oldData = data;\n  let oldKernel = kernel;\n\n  // Legend drawn similarly to legends in overview/intermediate-view.\n  const addOverlayGradient = (gradientID, stops, group) => {\n    if (group === undefined) {\n      group = svg;\n    }\n\n    // Create a gradient\n    let defs = group.append(\"defs\")\n      .attr('class', 'overlay-gradient');\n\n    let gradient = defs.append(\"linearGradient\")\n      .attr(\"id\", gradientID)\n      .attr(\"x1\", \"0%\")\n      .attr(\"x2\", \"100%\")\n      .attr(\"y1\", \"100%\")\n      .attr(\"y2\", \"100%\");\n    \n    stops.forEach(s => {\n      gradient.append('stop')\n        .attr('offset', s.offset)\n        .attr('stop-color', s.color)\n        .attr('stop-opacity', s.opacity);\n    })\n  }\n\n  // Draw the legend for intermediate layer\n  const redrawDetailedConvViewLegend = (arg) => {\n    let legendHeight = arg.legendHeight,\n      range = arg.range,\n      minMax = arg.minMax,\n      width = arg.width,\n      colorScale = arg.colorScale,\n      gradientGap = arg.gradientGap;\n\n    d3.select(legendFinal).selectAll(\"#legend > *\").remove();\n    let legend = d3.select(legendFinal).select(\"#legend\")\n      .attr(\"width\", 150 + \"px\")\n      .attr(\"height\", 25 + \"px\")\n      .attr(\"align\",\"center\")\n      .style(\"dominant-baseline\", \"middle\");\n    let detailedViewKernel = legend.append('g')\n      .attr('transform', `translate(10, 0)`);\n    \n    if (colorScale === undefined) { colorScale = layerColorScales.conv; }\n    if (gradientGap === undefined) { gradientGap = 0; }\n    \n    // Add a legend color gradient\n    let gradientName = `url(#detailed-kernel-gradient)`;\n    let normalizedColor = v => colorScale(v * (1 - 2 * gradientGap) + gradientGap);\n\n    let leftValue = (minMax.min + range / 2) / range,\n      zeroValue = (0 + range / 2) / range,\n      rightValue = (minMax.max + range / 2) / range,\n      totalRange = minMax.max - minMax.min,\n      zeroLocation = (0 - minMax.min) / totalRange,\n      leftMidValue = leftValue + (zeroValue - leftValue)/2,\n      rightMidValue = zeroValue + (rightValue - zeroValue)/2;\n\n    let stops = [\n      {offset: 0, color: normalizedColor(leftValue), opacity: 1},\n      {offset: zeroLocation / 2,\n        color: normalizedColor(leftMidValue),\n        opacity: 1},\n      {offset: zeroLocation,\n        color: normalizedColor(zeroValue),\n        opacity: 1},\n      {offset: zeroLocation + (1 - zeroValue) / 2,\n        color: normalizedColor(rightMidValue),\n        opacity: 1},\n      {offset: 1, color: normalizedColor(rightValue), opacity: 1}\n    ];\n\n    addOverlayGradient(`detailed-kernel-gradient`, stops, detailedViewKernel);\n\n    let legendScale = d3.scaleLinear()\n      .range([0, width - 1.2])\n      .domain([minMax.min, minMax.max]);\n\n    let legendAxis = d3.axisBottom()\n      .scale(legendScale)\n      .tickFormat(d3.format('.2f'))\n      .tickValues([minMax.min, 0, minMax.max]);\n    \n    let detailedLegend = detailedViewKernel.append('g')\n      .attr('id', `detailed-legend-0`)\n    \n    let legendGroup = detailedLegend.append('g')\n      .attr('transform', `translate(0, ${legendHeight - 3})`)\n      .call(legendAxis);\n    \n    legendGroup.selectAll('text')\n      .style('font-size', '9px')\n      .style('fill', \"black\");\n    \n    legendGroup.selectAll('path, line')\n      .style('stroke', \"black\");\n\n    detailedLegend.append('rect')\n      .attr('width', width)\n      .attr('height', legendHeight)\n      .style('fill', gradientName);\n  }\n\n  // Draw the elementwise dot-product math.\n  const redraw = () => {\n    d3.select(gridFinal).selectAll(\"#grid > *\").remove();\n    const constrainedSvgSize = kernel ? 2 * (data.length * constraint) + 2 : data.length * constraint + 2;\n    var grid = d3.select(gridFinal).select(\"#grid\")\n      .attr(\"width\", constrainedSvgSize + \"px\")\n      .attr(\"height\", constrainedSvgSize + \"px\")\n      .append(\"svg\")\n      .attr(\"width\", constrainedSvgSize + \"px\")\n      .attr(\"height\", constrainedSvgSize + \"px\")\n    var row = grid.selectAll(\".row\")\n      .data(data)\n      .enter().append(\"g\")\n      .attr(\"class\", \"row\");\n    \n    var columns = row.selectAll(\".square\")\n      .data(function(d) { return d; })\n      .enter();\n    // Draw cells for slice from input matrix.\n    columns.append(\"rect\")\n      .attr(\"class\",\"square\")\n      .attr(\"x\", function(d) { return d.x === 1 ? d.x + multiplicationSymbolPadding : d.x * 2 + multiplicationSymbolPadding})\n      .attr(\"y\", function(d) { return d.y === 1 ? d.y : d.y * 2 })\n      .attr(\"width\", function(d) { return d.width; })\n      .attr(\"height\", function(d) { return d.height; })\n      .style(\"opacity\", 0.5)\n      .style(\"fill\", function(d) { \n        let normalizedValue = d.text;\n        if (isInputLayer){\n          normalizedValue = 1 - d.text;\n        } else {\n          normalizedValue = (d.text + dataRange / 2) / dataRange;\n        }\n        return colorScale(normalizedValue); \n      })\n      .style(\"stroke\", \"black\");\n    // Draw cells for the kernel.\n    columns.append(\"rect\")\n      .attr(\"class\",\"square\")\n      .attr(\"x\", function(d) { return d.x === 1 ? d.x + multiplicationSymbolPadding: d.x * 2 + multiplicationSymbolPadding})\n      .attr(\"y\", function(d) { return d.y === 1 ? d.y + d.height : d.y * 2 + d.height })\n      .attr(\"width\", function(d) { return d.width; })\n      .attr(\"height\", function(d) { return d.height / 2; })\n      .style(\"opacity\", 0.5)\n      // Same colorscale as is used for the flatten layers.\n      .style(\"fill\", function(d) { \n        let normalizedValue = (kernel[d.row][d.col].text + kernelRange.range / 2) / kernelRange.range;\n        const gap = 0.2;\n        let normalizedValueWithGap = normalizedValue * (1 - 2 * gap) + gap;\n        return kernelColorScale(normalizedValueWithGap); \n      })\n\n    var texts = row.selectAll(\".text\")\n      .data(function(d) { return d; })\n      .enter();\n    // Draw numbers from input matrix slice.\n    texts.append(\"text\")\n      .attr(\"class\",\"text\")\n      .style(\"font-size\", Math.floor(constraint / textConstraintDivisor) + \"px\")\n      .attr(\"x\", function(d) { return d.x === 1 ? d.x + d.width / 2 + multiplicationSymbolPadding: d.x * 2 + d.width / 2 + multiplicationSymbolPadding})\n      .attr(\"y\", function(d) { return d.y === 1 ? d.y + d.height / 2 : d.y * 2 + d.height / 2 })\n      .style(\"fill\", function(d) { \n        let normalizedValue = d.text;\n        if (isInputLayer){\n          normalizedValue = 1 - d.text;\n        } else {\n          normalizedValue = (d.text + dataRange / 2) / dataRange;\n        }\n        if (normalizedValue < 0.2 || normalizedValue > 0.8) {\n          if (isInputLayer && normalizedValue < 0.2) {\n            return 'black';\n          } \n          return 'white';\n        } else {\n          return 'black';\n        }\n      })\n      .style(\"text-anchor\", \"middle\")\n      .style(\"dominant-baseline\", \"middle\")\n      .text(function(d) { return d.text; })\n    // Attempted to use FontAwesome icons for the 'x', '+', and '=', but none of these strategies work: https://github.com/FortAwesome/Font-Awesome/issues/12268\n    // Draw 'x' to signify multiplication.\n    texts.append(\"text\")\n      .attr(\"class\",\"text\")\n      .style(\"font-size\", Math.floor(constraint / (textConstraintDivisor)) + \"px\")\n      .attr('font-weight', 600)\n      .attr(\"x\", function(d) { return d.x === 1 ? d.x + multiplicationSymbolPadding / 2: d.x * 2 + multiplicationSymbolPadding / 2})\n      .attr(\"y\", function(d) { return d.y === 1 ? d.y + d.height + (d.height / 4) : d.y * 2 + d.height + (d.height / 4) })\n      .style(\"fill\", \"black\")\n      .style(\"text-anchor\", \"middle\")\n      .style(\"dominant-baseline\", \"middle\")\n      .text(function(d) { return '×' })\n    // Draw kernel values.\n    texts.append(\"text\")\n      .attr(\"class\",\"text\")\n      .style(\"font-size\", Math.floor(constraint / textConstraintDivisor) + \"px\")\n      .attr(\"x\", function(d) { return d.x === 1 ? d.x + d.width / 2 + multiplicationSymbolPadding: d.x * 2 + d.width / 2 + multiplicationSymbolPadding})\n      .attr(\"y\", function(d) { return d.y === 1 ? d.y + d.height + (d.height / 4) : d.y * 2 + d.height + (d.height / 4) })\n      .style(\"fill\", function(d) { \n        let normalizedValue = (kernel[d.row][d.col].text + kernelRange.range / 2) / kernelRange.range;\n        const gap = 0.2;\n        let normalizedValueWithGap = normalizedValue * (1 - 2 * gap) + gap;\n        if (normalizedValueWithGap < 0.2 || normalizedValueWithGap > 0.8) {\n          return 'white';\n        } else {\n          return 'black';\n        }\n      })\n      .style(\"text-anchor\", \"middle\")\n      .style(\"dominant-baseline\", \"middle\")\n      .text(function(d) { return kernel[d.row][d.col].text; })\n    // Draw '+' to signify the summing of products except for the last kernel cell where '=' is drawn.\n    texts.append(\"text\")\n      .attr(\"class\",\"text\")\n      .style(\"font-size\", Math.floor(constraint / (textConstraintDivisor - 1)) + \"px\")\n      .attr(\"x\", function(d) { return d.x === 1 ? d.x + d.width + d.width / 2 + multiplicationSymbolPadding: d.x * 2 + d.width + d.width / 2 + multiplicationSymbolPadding})\n      .attr(\"y\", function(d) { return d.y === 1 ? d.y + d.height / 2 : d.y * 2 + d.height / 2 })\n      .style(\"text-anchor\", \"middle\")\n      .style(\"dominant-baseline\", \"middle\")\n      .text(function(d) { return d.row == kernel.length - 1 && d.col == kernel.length - 1 ? '=' : '+'; })\n    }\n\n  afterUpdate(() => {\n    if (data != oldData) {\n      redraw();\n      oldData = data;\n    }\n    if (kernel != oldKernel) {\n      /*\n      redrawDetailedConvViewLegend({\n          legendHeight: 5,\n          range: kernelRange.range,\n          minMax: {min: kernelRange.min, max: kernelRange.max},\n          width: 130,\n          colorScale: kernelColorScale,\n          gradientGap: 0.35,\n      });\n      */\n      oldKernel = kernel;\n    }\n  });\n\n  onMount(() => {\n    redraw();\n    /*\n    redrawDetailedConvViewLegend({\n          legendHeight: 5,\n          range: kernelRange.range,\n          minMax: {min: kernelRange.min, max: kernelRange.max},\n          width: 130,\n          colorScale: kernelColorScale,\n          gradientGap: 0.35,\n    });\n    */\n  });\n\n</script>\n\n<div class=\"legend\"\n  bind:this={legendFinal}>\n  <!-- <svg id=\"legend\" width=100% height=100%></svg> -->\n</div>\n\n<div class=\"grid\"\n  bind:this={gridFinal}>\n  <svg id=\"grid\" width=100% height=100%></svg>\n</div>"
  },
  {
    "path": "src/detail-view/PoolAnimator.svelte",
    "content": "<script>\n  import { createEventDispatcher } from 'svelte';\n  import { array1d, getMatrixSliceFromOutputHighlights,\n    compute_input_multiplies_with_weight, getVisualizationSizeConstraint,\n    generateOutputMappings, getMatrixSliceFromInputHighlights, gridData\n  } from './DetailviewUtils.js';\n  import Dataview from './Dataview.svelte';\n\n  export let stride;\n  export let dilation\n  export let kernelLength;\n  export let image;\n  export let output;\n  export let isPaused;\n  export let dataRange;\n\n  const dispatch = createEventDispatcher();\n  const padding = 0;\n  let padded_input_size = image.length + padding * 2;\n  $: padded_input_size = image.length + padding * 2;\n\n  // Dummy data for original state of component.\n  let testInputMatrixSlice = [];\n  for (let i = 0; i < kernelLength; i++) {\n    testInputMatrixSlice.push([]);\n    for (let j = 0; j < kernelLength; j++) {\n      testInputMatrixSlice[i].push(0)\n    }\n  }\n  testInputMatrixSlice = gridData(testInputMatrixSlice)\n  let testOutputMatrixSlice = gridData([[0]]);\n\n  let inputHighlights = [];\n  let outputHighlights = array1d(output.length * output.length, (i) => true);\n  let interval;\n  $ : {\n    let inputHighlights = [];\n    let outputHighlights = array1d(output.length * output.length, (i) => true);\n    let interval;\n  }\n  \n  let counter;\n\n  // lots of replication between mouseover and start-pool. TODO: fix this.\n  function startMaxPool(stride) {\n    counter = 0;\n    let outputMappings = generateOutputMappings(stride, output, kernelLength, padded_input_size, dilation);\n    if (stride <= 0) return;\n    if (interval) clearInterval(interval);\n    interval = setInterval(() => {\n      if (isPaused) return;\n      const flat_animated = counter % (output.length * output.length);\n      outputHighlights = array1d(output.length * output.length, (i) => false);\n      const animatedH = Math.floor(flat_animated / output.length);\n      const animatedW = flat_animated % output.length;\n      outputHighlights[animatedH * output.length + animatedW] = true;\n      inputHighlights = compute_input_multiplies_with_weight(animatedH, animatedW, padded_input_size, kernelLength, outputMappings, kernelLength)\n      const inputMatrixSlice = getMatrixSliceFromInputHighlights(image, inputHighlights, kernelLength);\n      testInputMatrixSlice = gridData(inputMatrixSlice);\n      const outputMatrixSlice = getMatrixSliceFromOutputHighlights(output, outputHighlights);\n      testOutputMatrixSlice = gridData(outputMatrixSlice);\n      counter++;\n    }, 250)\n  }\n\n  function handleMouseover(event) {\n    let outputMappings = generateOutputMappings(stride, output, kernelLength, padded_input_size, dilation);\n    outputHighlights = array1d(output.length * output.length, (i) => false);\n    const animatedH = event.detail.hoverH;\n    const animatedW = event.detail.hoverW;\n    outputHighlights[animatedH * output.length + animatedW] = true;\n    inputHighlights = compute_input_multiplies_with_weight(animatedH, animatedW, padded_input_size, kernelLength, outputMappings, kernelLength)\n    const inputMatrixSlice = getMatrixSliceFromInputHighlights(image, inputHighlights, kernelLength);\n    testInputMatrixSlice = gridData(inputMatrixSlice);\n    const outputMatrixSlice = getMatrixSliceFromOutputHighlights(output, outputHighlights);\n    testOutputMatrixSlice = gridData(outputMatrixSlice);\n    isPaused = true;\n    dispatch('message', {\n      text: isPaused\n    });\n  }\n\n  startMaxPool(stride);\n  let testImage = gridData(image)\n  let testOutput = gridData(output)\n  $ : {\n    startMaxPool(stride);\n    testImage = gridData(image)\n    testOutput = gridData(output)\n  }\n</script>\n\n<style>\n  .column {\n    padding: 5px;\n  }\n</style>\n\n<div class=\"column has-text-centered\">\n  <div class=\"header-text\">\n    Input ({testImage.length}, {testImage[0].length})\n  </div>\n\n  <Dataview on:message={handleMouseover} data={testImage} highlights={inputHighlights} outputLength={output.length}\n      isKernelMath={false} constraint={getVisualizationSizeConstraint(image.length)} dataRange={dataRange} stride={stride}/>  \n</div>\n<div class=\"column has-text-centered\">\n  <span>\n    max(\n    <Dataview data={testInputMatrixSlice} highlights={outputHighlights} isKernelMath={true} \n      constraint={getVisualizationSizeConstraint(kernelLength)} dataRange={dataRange}/>\n    )\n    =\n    <Dataview data={testOutputMatrixSlice} highlights={outputHighlights} isKernelMath={true} \n      constraint={getVisualizationSizeConstraint(kernelLength)} dataRange={dataRange}/>\n  </span> \n</div>\n<div class=\"column has-text-centered\">\n  <div class=\"header-text\">\n    Output ({testOutput.length}, {testOutput[0].length})\n  </div>\n  <Dataview on:message={handleMouseover} data={testOutput} highlights={outputHighlights} isKernelMath={false} \n      outputLength={output.length} constraint={getVisualizationSizeConstraint(output.length)} dataRange={dataRange} stride={stride}/>\n</div>"
  },
  {
    "path": "src/detail-view/Poolview.svelte",
    "content": "<script>\n\timport PoolAnimator from './PoolAnimator.svelte';\n  import { singleMaxPooling } from '../utils/cnn.js';\n  import { createEventDispatcher } from 'svelte';\n\n  export let input;\n  export let kernelLength;\n  export let dataRange;\n  export let isExited;\n  \n  const dispatch = createEventDispatcher();\n  // let isExited = false;\n\tlet stride = 2;\n  const dilation = 1;\n  var isPaused = false;\n  var outputFinal = singleMaxPooling(input);\n  // let dragging = false;\n  // let dragInfo = {x1: 0, x2: 0, y1: 0, y2: 0};\n  // let detailView = d3.select('#detailview').node();\n  $: if (stride > 0) {\n    try { \n      outputFinal = singleMaxPooling(input);\n    } catch {\n      console.log(\"Cannot handle stride of \" + stride);\n    }\n  }\n  \n  function handleClickPause() {\n    isPaused = !isPaused;\n    console.log(isPaused)\n  }\n\n  function handlePauseFromInteraction(event) {\n    isPaused = event.detail.text;\n  }\n\n  function handleClickX() {\n    dispatch('message', {\n      text: true\n    });\n  }\n\n  function handleScroll() {\n    let svgHeight = Number(d3.select('#cnn-svg').style('height').replace('px', '')) + 150;\n    let scroll = new SmoothScroll('a[href*=\"#\"]', {offset: -svgHeight});\n    let anchor = document.querySelector(`#article-pooling`);\n    scroll.animateScroll(anchor);\n  }\n\n  // Test dragging detail view, need more work\n  // const detailViewDragStart = (e) => {\n  //   // Record the starting pos\n  //   dragInfo.x1 = 0;\n  //   dragInfo.y1 = 0;\n  //   dragInfo.x2 = e.clientX;\n  //   dragInfo.y2 = e.clientY;\n  \n  //   dragging = true;\n  // }\n\n  // const detailViewDragEnd = (e) => {\n  //   dragging = false;\n  // }\n\n  // const detailViewDragMove = (e) => {\n  //   // Add up move to the starting pos\n  //   dragInfo.x1 = dragInfo.x2 - e.clientX;\n  //   dragInfo.y1 = dragInfo.y2 - e.clientY;\n  //   dragInfo.x2 = e.clientX;\n  //   dragInfo.y2 = e.clientY;\n\n  //   // Move detail view\n  //   detailView.style.top = (detailView.offsetTop - dragInfo.y1) + 'px';\n  //   detailView.style.left = (detailView.offsetLeft - dragInfo.x1) + 'px';\n  // }\n</script>\n\n<style>\n  .control-pannel {\n    display: flex;\n    position: relative;\n    flex-direction: column;\n    align-items: center;\n  }\n\n  .buttons {\n    cursor: pointer;\n    position: absolute;\n    top: 0px;\n    right: 0px;\n  }\n\n  .control-button {\n    color: gray;\n    font-size: 15px;\n    opacity: 0.4;\n    cursor: pointer;\n  }\n\n  .control-button:not(:first-child) {\n    margin-left: 5px;\n  }\n\n  .annotation {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    padding-left : 10px;\n    font-size: 12px;\n  }\n\n  .annotation > img {\n    width: 17px;\n    margin-right: 5px;\n  }\n\n\n  .control-button:hover {\n    opacity: 0.8;\n  }\n\n  .box {\n    padding: 5px 15px 10px 15px;\n  }\n\n  .container {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n\n  .title-text {\n    font-size: 1.2em;\n    font-weight: 500;\n    color: #4a4a4a;\n  }\n</style>\n\n{#if !isExited}\n  <div class=\"container\">\n\n    <!-- old stride input -->\n    <!-- <div class=\"columns is-mobile\">\n      <div class=\"column is-half is-offset-one-quarter\">\n        <div class=\"field is-grouped\">\n          <p class=\"control is-expanded\">\n            <input class=\"input\" type=\"text\" placeholder=\"Stride\" bind:value={stride} />\n          </p>\n          <p class=\"control\">\n            <button class=\"button is-success\" on:click={handleClickPause}>\n              Toggle Movement\n            </button>\n          </p>\n        </div>\n      </div>\n    </div> -->\n    <div class=\"box\">\n\n      <div class=\"control-pannel\">\n      \n        <div class=\"title-text\">\n          Max Pooling\n        </div>\n\n        <div class=\"buttons\">\n\n          <div class=\"control-button\" on:click={handleScroll} title=\"Jump to article section\">\n            <i class=\"fas fa-info-circle\"></i>\n          </div>\n\n          <div class=\"play-button control-button\" on:click={handleClickPause} title=\"Play animation\">\n            {@html isPaused ?\n              '<i class=\"fas fa-play-circle play-icon\"></i>' :\n              '<i class=\"fas fa-pause-circle\"></i>'}\n          </div>\n\n          <div class=\"delete-button control-button\" on:click={handleClickX} title=\"Close\">\n            <i class=\"fas control-icon fa-times-circle\"></i>\n          </div>\n        </div>\n\n      </div>\n\n      <div class=\"container is-centered is-vcentered\">\n        <PoolAnimator on:message={handlePauseFromInteraction} \n          kernelLength={kernelLength} image={input} output={outputFinal} \n          stride={stride} dilation={dilation} isPaused={isPaused}\n          dataRange={dataRange} />\n      </div>\n\n      <div class=\"annotation\">\n        <img src='PUBLIC_URL/assets/img/pointer.svg' alt='pointer icon'>\n          <div class=\"annotation-text\">\n            <span style=\"font-weight:600\">Hover over</span> the matrices to change kernel position.\n          </div>\n      </div>\n\n    </div>\n  </div>\n{/if}"
  },
  {
    "path": "src/detail-view/Softmaxview.svelte",
    "content": "<script>\n  import { onMount, afterUpdate, createEventDispatcher } from 'svelte';\n  export let logits;\n  export let logitColors;\n  export let selectedI;\n  export let highlightI = -1;\n  export let outputName;\n  export let outputValue;\n  export let startAnimation;\n\n  let softmaxViewComponent;\n  let svg = null;\n  const dispatch = createEventDispatcher();\n  const formater = (n, d) => {\n    if (d === undefined) {\n      return d3.format('.2f')(n);\n    } else {\n      return d3.format(`.${d}f`)(n);\n    }\n  }\n\n  $: highlightI, (() => {\n    if (svg !== null) {\n      svg.selectAll(`.formula-term`)\n        .style('text-decoration', 'none')\n        .style('font-weight', 'normal');\n\n      svg.selectAll(`.formula-term-${highlightI}`)\n      .style('font-weight', 'bold')\n      .style('text-decoration', 'underline');\n    }\n  })();\n\n  $: startAnimation, (() => {\n    if (svg !== null) {\n      svg.select(`.formula-term-${startAnimation.i}`)\n        .transition('softmax-edge')\n        .duration(startAnimation.duration)\n        .style('fill-opacity', 1);\n    }\n  })();\n\n  const mouseOverHandler = (d, i, g, curI) => {\n    highlightI = curI;\n    dispatch('mouseOver', {curI: curI});\n  }\n\n  const mouseLeaveHandler = (d, i, g, curI) => {\n    highlightI = -1;\n    dispatch('mouseLeave', {curI: curI});\n  }\n\n  const handleClickX = () => {\n    dispatch('xClicked', {});\n  }\n\n  function handleScroll() {\n    let svgHeight = Number(d3.select('#cnn-svg').style('height').replace('px', '')) + 150;\n    let scroll = new SmoothScroll('a[href*=\"#\"]', {offset: -svgHeight});\n    let anchor = document.querySelector(`#article-softmax`);\n    scroll.animateScroll(anchor);\n  }\n\n  onMount(() => {\n    svg = d3.select(softmaxViewComponent)\n      .select('#softmax-svg');\n\n    let formulaRightGroup = svg.append('g')\n      .attr('class', 'formula-right')\n      .attr('transform', `translate(${10}, ${0})`)\n      .style('font-size', '15px');\n\n    // Denominator\n    let denominatorGroup = formulaRightGroup.append('g')\n      .attr('class', 'denominator')\n      .attr('transform', `translate(${0}, ${58})`);\n      \n    // Add the left (\n    denominatorGroup.append('text')\n      .attr('x', 0)\n      .attr('y', 0)\n      .style('fill', 'gray')\n      .text('(');\n\n    // Need to loop through the logits array instead of data-binding because\n    // we want dynamic positioning based on prior '-' occurance\n    let curX = 8;\n    let numOfRows = 4;\n\n    logits.forEach((d, i) => {\n      if (i / numOfRows >= 1 && i % numOfRows === 0) {\n          curX = 8;\n      }\n\n      let curText = denominatorGroup.append('text')\n        .attr('x', curX)\n        .attr('y', Math.floor(i / numOfRows) * 20)\n        .style('cursor', 'crosshair')\n        .style('pointer-events', 'all')\n        .on('mouseover', (d, n, g) => mouseOverHandler(d, n, g, i))\n        .on('mouseleave', (d, n, g) => mouseLeaveHandler(d, n, g, i))\n        .text(`exp(`);\n      \n      curText.append('tspan')\n        .attr('class', `formula-term-${i} formula-term`)\n        .attr('dx', '1')\n        .style('fill', logitColors[i])\n        .style('fill-opacity', (i === selectedI) || startAnimation.hasInitialized ? 1 : 0)\n        .text(formater(d));\n      \n      curText.append('tspan')\n        .attr('dx', '1')\n        .text(')');\n      \n      let curBBox = curText.node().getBBox();\n      curX += curBBox.width + 4;\n\n      if (i !== logits.length - 1) {\n        denominatorGroup.append('text')\n          .attr('x', curX)\n          .attr('y', Math.floor(i / numOfRows) * 20)\n          .text('+');\n        curX += 14;\n      } else {\n        denominatorGroup.append('text')\n          .attr('x', curX-2)\n          .attr('y', Math.floor(i / numOfRows) * 20)\n          .style('fill', 'gray')\n          .text(')');\n      }\n    })\n\n    denominatorGroup.selectAll('text')\n      .data(logits)\n      .enter()\n      .append('text')\n      .attr('x', (d, i) => 40 * i)\n      .attr('y', 0)\n      .text(d => formater(d));\n    \n    // Calculate the dynamic denominator group width\n    let denominatorGroupBBox = denominatorGroup.node().getBBox();\n\n    // Draw the fraction line\n    formulaRightGroup.append('line')\n      .attr('class', 'separation-line')\n      .attr('x1', -5)\n      .attr('x2', denominatorGroupBBox.width + 5)\n      .attr('y1', 32)\n      .attr('y2', 32)\n      .style('stroke-width', 1.2)\n      .style('stroke', 'gray');\n    \n    // Draw the numerator\n    let numeratorGroup = formulaRightGroup.append('g')\n      .attr('class', 'numerator-group')\n      .attr('transform', `translate(${0}, ${20})`);\n    \n    let numeratorText = numeratorGroup.append('text')\n      .attr('x', denominatorGroupBBox.x + denominatorGroupBBox.width / 2)\n      .attr('y', 0)\n      .on('mouseover', (d, n, g) => mouseOverHandler(d, n, g, selectedI))\n      .on('mouseleave', (d, n, g) => mouseLeaveHandler(d, n, g, selectedI))\n      .style('pointer-events', 'all')\n      .style('cursor', 'crosshair')\n      .style('text-anchor', 'middle')\n      .text('exp(');\n\n    numeratorText.append('tspan')\n      .attr('class', `formula-term-${selectedI} formula-term`)\n      .attr('dx', 1)\n      .style('fill', logitColors[selectedI])\n      .text(`${formater(logits[selectedI])}`);\n\n    numeratorText.append('tspan')\n       .attr('dx', 1)\n      .text(')');\n    \n    // Draw the left part of the formula\n    let formulaLeftGroup = svg.append('g')\n      .attr('class', 'formula-left')\n      .attr('transform', `translate(${395}, ${32})`);\n    \n    let softmaxText = formulaLeftGroup.append('text')\n      .attr('x', 20)\n      .attr('dominant-baseline', 'middle')\n      .text(`${formater(outputValue, 4)}`);\n    \n    let softmaxTextBBox = softmaxText.node().getBBox();\n    \n    formulaLeftGroup.append('text')\n      .attr('dominant-baseline', 'middle')\n      .attr('x', 0)\n      .attr('y', 0)\n      .style('fill', 'gray')\n      .style('font-weight', 'bold')\n      .text('=');\n\n  })\n\n</script>\n\n<style>\n  .buttons {\n    cursor: pointer;\n    position: absolute;\n    top: 6px;\n    right: 10px;\n  }\n\n  .control-button {\n    color: gray;\n    font-size: 15px;\n    opacity: 0.4;\n  }\n\n  .control-button:hover {\n    opacity: 0.8;\n  }\n\n  .control-button:not(:first-child) {\n    margin-left: 5px;\n  }\n\n  .title-text {\n    font-size: 1.2em;\n    font-weight: 500;\n    color: #4a4a4a;\n  }\n\n  .annotation {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    padding-left : 10px;\n    font-size: 12px;\n  }\n\n  .annotation > img {\n    width: 17px;\n    margin-right: 5px;\n  }\n\n  .box {\n    padding: 5px 10px 15px 10px;\n    position: relative;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n  }\n\n  svg {\n    margin: 10px 0 12px 0;\n  }\n</style>\n\n<div class=\"container\" bind:this={softmaxViewComponent}>\n  <div class=\"box\">\n\n    <div class=\"buttons\">\n      <div class=\"control-button\" on:click={handleScroll} title=\"Jump to article section\">\n        <i class=\"fas fa-info-circle\"></i>\n      </div>\n\n      <div class=\"delete-button control-button\" on:click={handleClickX} title=\"Close\">\n        <i class=\"fas control-icon fa-times-circle\"></i>\n      </div>\n    </div>\n\n    <div class=\"title-text\">\n      Softmax Score for <i>\"{outputName}\"</i>\n    </div>\n\n    <svg id=\"softmax-svg\" width=\"470\" height=\"105\"/>\n\n    <div class=\"annotation\">\n      <img src='PUBLIC_URL/assets/img/pointer.svg' alt='pointer icon'>\n      <div class=\"annotation-text\">\n        <span style=\"font-weight:600\">Hover over</span> the numbers to highlight logit circles.\n      </div>\n    </div>\n\n  </div>\n</div>\n\n"
  },
  {
    "path": "src/main.js",
    "content": "import App from './App.svelte';\n\nconst app = new App({\n\ttarget: document.body,\n\tprops: {}\n});\n\nexport default app;"
  },
  {
    "path": "src/overview/Modal.svelte",
    "content": "<script>\n  /* global d3 */\n\n  import { onMount, createEventDispatcher } from 'svelte';\n  import { modalStore } from '../stores.js';\n\n  let modalComponent;\n  let valiImg;\n  let inputValue = '';\n  let showLoading = false;\n  let files;\n  let usingURL = true;\n  let errorInfo = {\n    show: false,\n    error: ''\n  };\n  const dispatch = createEventDispatcher();\n\n  let modalInfo = {\n    show: false\n  };\n  modalStore.set(modalInfo);\n  modalStore.subscribe(value => {modalInfo = value});\n\n  const errorCallback = () => {\n    // The URL is invalid, show an error message on the UI\n    showLoading = false;\n    errorInfo.show = true;\n    errorInfo.error = usingURL ? \"We can't find the image at that URL.\" :\n      \"Not a valid image file.\";\n  }\n\n  const loadCallback = () => {\n    // The URL is valid, but we are not sure if loading it to canvas would be\n    // blocked by crossOrigin setting. Try it here before dispatch to parent.\n\n    // https://stackoverflow.com/questions/13674835/canvas-tainted-by-cross-origin-data\n    let canvas = document.createElement(\"canvas\");\n    let context = canvas.getContext(\"2d\");\n\n    canvas.width = valiImg.width;\n    canvas.height = valiImg.height;\n    context.drawImage(valiImg, 0, 0);\n\n    try {\n      context.getImageData(0, 0, valiImg.width, valiImg.height);\n      // If the foreign image does support CORS -> use this image\n      // dispatch to parent component to use the input image\n      showLoading = false;\n      modalInfo.show = false;\n      modalStore.set(modalInfo);\n      dispatch('urlTyped', {url: valiImg.src});\n      inputValue = null;\n    } catch(err) {\n      // If the foreign image does not support CORS -> use this image\n      showLoading = false;\n      errorInfo.show = true;\n      errorInfo.error = \"No permission to load this image.\"\n    }\n  }\n\n  const imageUpload = () => {\n    usingURL = false;\n    let reader = new FileReader();\n    reader.onload = (event) => {\n      valiImg.src = event.target.result;\n    }\n    reader.readAsDataURL(files[0]);\n  }\n\n  const crossClicked = () => {\n    modalInfo.show = false;\n    modalStore.set(modalInfo);\n    // Dispatch the parent component\n    dispatch('xClicked', {preImage: modalInfo.preImage});\n  }\n\n  const addClicked = () => {\n    // Validate the input URL\n    showLoading = true;\n    errorInfo.show = false;\n    valiImg.crossOrigin = \"Anonymous\";\n    valiImg.src = inputValue;\n  }\n\n  onMount(() => {\n    let modal = d3.select(modalComponent)\n      .select('#input-modal');\n  })\n\n</script>\n\n<style>\n  .modal-card {\n    max-width: 500px;\n  }\n\n  .modal-card-title {\n    font-size: 20px;\n  }\n\n  .modal-card-head {\n    padding: 15px 20px;\n  }\n\n  .modal-card-foot {\n    padding: 12px 20px;\n    justify-content: space-between;\n  }\n\n  .is-smaller {\n    font-size: 15px;\n    padding: 0.5em 0.8em;\n    max-height: 2.2em;\n  }\n\n  .small-font {\n    font-size: 15px;\n  }\n\n  .error-message {\n    font-size: 15px;\n    padding: 0.5em 0;\n    color: #F22B61;\n  }\n\n  .control {\n    width: 100%;\n  }\n\n  .or-label {\n    font-size: 15px;\n    margin: 0 10px;\n    padding: 0.5em 0;\n  }\n\n  .field {\n    display: flex;\n    justify-content: space-between;\n  }\n\n</style>\n\n\n<div class=\"modal-component\"\n  bind:this={modalComponent}>\n\n  <div class=\"modal\"\n    id=\"input-modal\"\n    class:is-active={modalInfo.show}>\n\n    <div class=\"modal-background\" on:click={crossClicked}></div>\n\n    <div class=\"modal-card\">\n      <header class=\"modal-card-head\">\n        <p class=\"modal-card-title\">Add Input Image</p>\n        <button class=\"delete\" aria-label=\"close\" on:click={crossClicked}></button>\n      </header>\n\n      <section class=\"modal-card-body\">\n        <div class=\"field\">\n          <div class=\"control has-icons-left\"\n            class:is-loading={showLoading}>\n\n            <input class=\"input small-font\" type=\"url\"\n              bind:value={inputValue}\n              placeholder=\"Paste URL of image...\">\n\n            <span class=\"icon small-font is-left\">\n              <i class=\"fas fa-link\"></i>\n            </span>\n\n          </div>\n\n          <div class=\"or-label\">or</div>\n\n          <div class=\"file\">\n            <label class=\"file-label\">\n              <input class=\"file-input\" type=\"file\" name=\"image\"\n                accept=\".png,.jpeg,.tiff,.jpg,.png\"\n                bind:files={files}\n                on:change={imageUpload}>\n              <span class=\"file-cta small-font\">\n                <span class=\"file-icon\">\n                  <i class=\"fas fa-upload\"></i>\n                </span>\n                <span class=\"file-label\">\n                  Upload\n                </span>\n              </span>\n            </label>\n          </div>\n\n        </div>\n\n      </section>\n\n      <footer class=\"modal-card-foot\">\n\n        <div class=\"error-message\"\n          class:hidden={!errorInfo.show}>\n          {errorInfo.error}\n        </div>\n\n        <div class=\"button-container\">\n          <button class=\"button is-smaller\"\n            on:click={crossClicked}>\n            Cancel\n          </button>\n\n          <button class=\"button is-success is-smaller\"\n            on:click={addClicked}>\n            Add\n          </button>\n        </div>\n\n\n      </footer>\n    </div>\n\n  </div>\n\n  <!-- An invisible image to check if the user input URL is valid -->\n  <img style=\"display: none\"\n    id=\"vali-image\"\n    alt=\"hidden image\"\n    bind:this={valiImg}\n    on:error={errorCallback}\n    on:load={loadCallback} />\n\n</div>\n"
  },
  {
    "path": "src/overview/Overview.svelte",
    "content": "<script>\n  // Svelte functions\n  import { onMount } from 'svelte';\n  import {\n    cnnStore, svgStore, vSpaceAroundGapStore, hSpaceAroundGapStore,\n    nodeCoordinateStore, selectedScaleLevelStore, cnnLayerRangesStore,\n    needRedrawStore, cnnLayerMinMaxStore, detailedModeStore,\n    shouldIntermediateAnimateStore, isInSoftmaxStore, softmaxDetailViewStore,\n    hoverInfoStore, allowsSoftmaxAnimationStore, modalStore,\n    intermediateLayerPositionStore\n  } from '../stores.js';\n\n  // Svelte views\n  import ConvolutionView from '../detail-view/Convolutionview.svelte';\n  import ActivationView from '../detail-view/Activationview.svelte';\n  import PoolView from '../detail-view/Poolview.svelte';\n  import SoftmaxView from '../detail-view/Softmaxview.svelte';\n  import Modal from './Modal.svelte'\n  import Article from '../article/Article.svelte';\n\n  // Overview functions\n  import { loadTrainedModel, constructCNN } from '../utils/cnn-tf.js';\n  import { overviewConfig } from '../config.js';\n\n  import {\n    addOverlayRect, drawConv1, drawConv2, drawConv3, drawConv4\n  } from './intermediate-draw.js';\n\n  import {\n    moveLayerX, addOverlayGradient\n  } from './intermediate-utils.js';\n\n  import {\n    drawFlatten, softmaxDetailViewMouseOverHandler, softmaxDetailViewMouseLeaveHandler\n  } from './flatten-draw.js';\n\n  import {\n    drawOutput, drawCNN, updateCNN, updateCNNLayerRanges, drawCustomImage\n  } from './overview-draw.js';\n\n\n  // View bindings\n  let overviewComponent;\n  let scaleLevelSet = new Set(['local', 'module', 'global']);\n  let selectedScaleLevel = 'local';\n  selectedScaleLevelStore.set(selectedScaleLevel);\n  let previousSelectedScaleLevel = selectedScaleLevel;\n  let wholeSvg = undefined;\n  let svg = undefined;\n\n  $: selectedScaleLevel, selectedScaleLevelChanged();\n\n  // Configs\n  const layerColorScales = overviewConfig.layerColorScales;\n  const nodeLength = overviewConfig.nodeLength;\n  const plusSymbolRadius = overviewConfig.plusSymbolRadius;\n  const numLayers = overviewConfig.numLayers;\n  const edgeOpacity = overviewConfig.edgeOpacity;\n  const edgeInitColor = overviewConfig.edgeInitColor;\n  const edgeHoverColor = overviewConfig.edgeHoverColor;\n  const edgeHoverOuting = overviewConfig.edgeHoverOuting;\n  const edgeStrokeWidth = overviewConfig.edgeStrokeWidth;\n  const intermediateColor = overviewConfig.intermediateColor;\n  const kernelRectLength = overviewConfig.kernelRectLength;\n  const svgPaddings = overviewConfig.svgPaddings;\n  const gapRatio = overviewConfig.gapRatio;\n  const overlayRectOffset = overviewConfig.overlayRectOffset;\n  const classLists = overviewConfig.classLists;\n\n  // Shared properties\n  let needRedraw = [undefined, undefined];\n  needRedrawStore.subscribe( value => {needRedraw = value;} );\n\n  let nodeCoordinate = undefined;\n  nodeCoordinateStore.subscribe( value => {nodeCoordinate = value;} )\n\n  let cnnLayerRanges = undefined;\n  cnnLayerRangesStore.subscribe( value => {cnnLayerRanges = value;} )\n\n  let cnnLayerMinMax = undefined;\n  cnnLayerMinMaxStore.subscribe( value => {cnnLayerMinMax = value;} )\n\n  let detailedMode = undefined;\n  detailedModeStore.subscribe( value => {detailedMode = value;} )\n\n  let shouldIntermediateAnimate = undefined;\n  shouldIntermediateAnimateStore.subscribe(value => {\n    shouldIntermediateAnimate = value;\n  })\n\n  let vSpaceAroundGap = undefined;\n  vSpaceAroundGapStore.subscribe( value => {vSpaceAroundGap = value;} )\n\n  let hSpaceAroundGap = undefined;\n  hSpaceAroundGapStore.subscribe( value => {hSpaceAroundGap = value;} )\n\n  let isInSoftmax = undefined;\n  isInSoftmaxStore.subscribe( value => {isInSoftmax = value;} )\n\n  let softmaxDetailViewInfo = undefined;\n  softmaxDetailViewStore.subscribe( value => {\n    softmaxDetailViewInfo = value;\n  } )\n\n  let modalInfo = undefined;\n  modalStore.subscribe( value => {modalInfo = value;} )\n\n  let hoverInfo = undefined;\n  hoverInfoStore.subscribe( value => {hoverInfo = value;} )\n\n  let intermediateLayerPosition = undefined;\n  intermediateLayerPositionStore.subscribe ( value => {intermediateLayerPosition = value;} )\n\n  let width = undefined;\n  let height = undefined;\n  let model = undefined;\n  let selectedNode = {layerName: '', index: -1, data: null};\n  let isInIntermediateView = false;\n  let isInActPoolDetailView = false;\n  let actPoolDetailViewNodeIndex = -1;\n  let actPoolDetailViewLayerIndex = -1;\n  let detailedViewNum = undefined;\n  let disableControl = false;\n\n  // Wait to load\n  let cnn = undefined;\n\n  let detailedViewAbsCoords = {\n    1 : [600, 270, 490, 290],\n    2: [500, 270, 490, 290],\n    3 : [700, 270, 490, 290],\n    4: [600, 270, 490, 290],\n    5: [650, 270, 490, 290],\n    6 : [775, 270, 490, 290],\n    7 : [100, 270, 490, 290],\n    8 : [60, 270, 490, 290],\n    9 : [200, 270, 490, 290],\n    10 : [300, 270, 490, 290],\n  }\n\n  const layerIndexDict = {\n    'input': 0,\n    'conv_1_1': 1,\n    'relu_1_1': 2,\n    'conv_1_2': 3,\n    'relu_1_2': 4,\n    'max_pool_1': 5,\n    'conv_2_1': 6,\n    'relu_2_1': 7,\n    'conv_2_2': 8,\n    'relu_2_2': 9,\n    'max_pool_2': 10,\n    'output': 11\n  }\n\n  const layerLegendDict = {\n    0: {local: 'input-legend', module: 'input-legend', global: 'input-legend'},\n    1: {local: 'local-legend-0-1', module: 'module-legend-0', global: 'global-legend'},\n    2: {local: 'local-legend-0-1', module: 'module-legend-0', global: 'global-legend'},\n    3: {local: 'local-legend-0-2', module: 'module-legend-0', global: 'global-legend'},\n    4: {local: 'local-legend-0-2', module: 'module-legend-0', global: 'global-legend'},\n    5: {local: 'local-legend-0-2', module: 'module-legend-0', global: 'global-legend'},\n    6: {local: 'local-legend-1-1', module: 'module-legend-1', global: 'global-legend'},\n    7: {local: 'local-legend-1-1', module: 'module-legend-1', global: 'global-legend'},\n    8: {local: 'local-legend-1-2', module: 'module-legend-1', global: 'global-legend'},\n    9: {local: 'local-legend-1-2', module: 'module-legend-1', global: 'global-legend'},\n    10: {local: 'local-legend-1-2', module: 'module-legend-1', global: 'global-legend'},\n    11: {local: 'output-legend', module: 'output-legend', global: 'output-legend'}\n  }\n\n  let imageOptions = [\n    {file: 'boat_1.jpeg', class: 'lifeboat'},\n    {file: 'bug_1.jpeg', class: 'ladybug'},\n    {file: 'pizza_1.jpeg', class: 'pizza'},\n    {file: 'pepper_1.jpeg', class: 'bell pepper'},\n    {file: 'bus_1.jpeg', class: 'bus'},\n    {file: 'koala_1.jpeg', class: 'koala'},\n    {file: 'espresso_1.jpeg', class: 'espresso'},\n    {file: 'panda_1.jpeg', class: 'red panda'},\n    {file: 'orange_1.jpeg', class: 'orange'},\n    {file: 'car_1.jpeg', class: 'sport car'}\n  ];\n  let selectedImage = imageOptions[6].file;\n\n  let nodeData;\n  let selectedNodeIndex = -1;\n  let isExitedFromDetailedView = true;\n  let isExitedFromCollapse = true;\n  let customImageURL = null;\n\n  // Helper functions\n  const selectedScaleLevelChanged = () => {\n    if (svg !== undefined) {\n      if (!scaleLevelSet.add(selectedScaleLevel)) {\n        console.error('Encounter unknown scale level!');\n      }\n\n      // Update nodes and legends\n      if (selectedScaleLevel != previousSelectedScaleLevel){\n        // We can simply redraw all nodes using the new color scale, or we can\n        // make it faster by only redraw certian nodes\n        let updatingLayerIndexDict = {\n          local: {\n            module: [1, 2, 8, 9, 10],\n            global: [1, 2, 3, 4, 5, 8, 9, 10]\n          },\n          module: {\n            local: [1, 2, 8, 9, 10],\n            global: [1, 2, 3, 4, 5, 8, 9, 10]\n          },\n          global: {\n            local: [1, 2, 3, 4, 5, 8, 9, 10],\n            module: [1, 2, 3, 4, 5]\n          }\n        };\n\n        let updatingLayerIndex = updatingLayerIndexDict[\n          previousSelectedScaleLevel][selectedScaleLevel];\n\n        updatingLayerIndex.forEach(l => {\n          let range = cnnLayerRanges[selectedScaleLevel][l];\n          svg.select(`#cnn-layer-group-${l}`)\n            .selectAll('.node-image')\n            .each((d, i, g) => drawOutput(d, i, g, range));\n        });\n \n        // Hide previous legend\n        svg.selectAll(`.${previousSelectedScaleLevel}-legend`)\n          .classed('hidden', true);\n\n        // Show selected legends\n        svg.selectAll(`.${selectedScaleLevel}-legend`)\n          .classed('hidden', !detailedMode);\n      }\n      previousSelectedScaleLevel = selectedScaleLevel;\n      selectedScaleLevelStore.set(selectedScaleLevel);\n    }\n  }\n\n  const intermediateNodeMouseOverHandler = (d, i, g) => {\n    if (detailedViewNum !== undefined) { return; }\n    svg.select(`rect#underneath-gateway-${d.index}`)\n      .style('opacity', 1);\n  }\n\n  const intermediateNodeMouseLeaveHandler = (d, i, g) => {\n    // screenshot\n    // return;\n    if (detailedViewNum !== undefined) { return; }\n    svg.select(`rect#underneath-gateway-${d.index}`)\n      .style('opacity', 0);\n  }\n\n  const intermediateNodeClicked = (d, i, g, selectedI, curLayerIndex) => {\n    d3.event.stopPropagation();\n    isExitedFromCollapse = false;\n    // Use this event to trigger the detailed view\n    if (detailedViewNum === d.index) {\n      // Setting this for testing purposes currently.\n      selectedNodeIndex = -1; \n      // User clicks this node again -> rewind\n      detailedViewNum = undefined;\n      svg.select(`rect#underneath-gateway-${d.index}`)\n        .style('opacity', 0);\n    } \n    // We need to show a new detailed view (two cases: if we need to close the\n    // old detailed view or not)\n    else {\n      // Setting this for testing purposes currently.\n      selectedNodeIndex = d.index;\n      let inputMatrix = d.output;\n      let kernelMatrix = d.outputLinks[selectedI].weight;\n      // let interMatrix = singleConv(inputMatrix, kernelMatrix);\n      let colorScale = layerColorScales.conv;\n\n      // Compute the color range\n      let rangePre = cnnLayerRanges[selectedScaleLevel][curLayerIndex - 1];\n      let rangeCur = cnnLayerRanges[selectedScaleLevel][curLayerIndex];\n      let range = Math.max(rangePre, rangeCur);\n\n      // User triggers a different detailed view\n      if (detailedViewNum !== undefined) {\n        // Change the underneath highlight\n        svg.select(`rect#underneath-gateway-${detailedViewNum}`)\n          .style('opacity', 0);\n        svg.select(`rect#underneath-gateway-${d.index}`)\n          .style('opacity', 1);\n      }\n      \n      // Dynamically position the detail view\n      let wholeSvg = d3.select('#cnn-svg');\n      let svgYMid = +wholeSvg.style('height').replace('px', '') / 2;\n      let svgWidth = +wholeSvg.style('width').replace('px', '');\n      let detailViewTop = 100 + svgYMid - 250 / 2;\n      let positionX = intermediateLayerPosition[Object.keys(layerIndexDict)[curLayerIndex]];\n\n      let posX = 0;\n      if (curLayerIndex > 6) {\n        posX = (positionX - svgPaddings.left) / 2;\n        posX = svgPaddings.left + posX - 486 / 2;\n      } else {\n        posX = (svgWidth + svgPaddings.right - positionX) / 2;\n        posX = positionX + posX - 486 / 2;\n      }\n\n      const detailview = document.getElementById('detailview');\n      detailview.style.top = `${detailViewTop}px`;\n      detailview.style.left = `${posX}px`;\n      detailview.style.position = 'absolute';\n\n      detailedViewNum = d.index;\n\n      // Send the currently used color range to detailed view\n      nodeData.colorRange = range;\n      nodeData.inputIsInputLayer = curLayerIndex <= 1;\n    }\n  }\n\n  // The order of the if/else statements in this function is very critical\n  const emptySpaceClicked = () => {\n    // If detail view -> rewind to intermediate view\n    if (detailedViewNum !== undefined) {\n          // Setting this for testing purposes currently.\n      selectedNodeIndex = -1; \n      // User clicks this node again -> rewind\n      svg.select(`rect#underneath-gateway-${detailedViewNum}`)\n        .style('opacity', 0);\n      detailedViewNum = undefined;\n    }\n\n    // If softmax view -> rewind to flatten layer view\n    else if (isInSoftmax) {\n      svg.select('.softmax-symbol')\n        .dispatch('click');\n    }\n\n    // If intermediate view -> rewind to overview\n    else if (isInIntermediateView) {\n      let curLayerIndex = layerIndexDict[selectedNode.layerName];\n      quitIntermediateView(curLayerIndex, selectedNode.domG, selectedNode.domI);\n      d3.select(selectedNode.domG[selectedNode.domI])\n        .dispatch('mouseleave');\n    }\n\n    // If pool/act detail view -> rewind to overview\n    else if (isInActPoolDetailView) {\n      quitActPoolDetailView();\n    }\n  }\n\n  const prepareToEnterIntermediateView = (d, g, i, curLayerIndex) => {\n    isInIntermediateView = true;\n    // Hide all legends\n    svg.selectAll(`.${selectedScaleLevel}-legend`)\n      .classed('hidden', true);\n    svg.selectAll('.input-legend').classed('hidden', true);\n    svg.selectAll('.output-legend').classed('hidden', true);\n\n    // Hide the input annotation\n    svg.select('.input-annotation')\n      .classed('hidden', true);\n\n    // Highlight the previous layer and this node\n    svg.select(`g#cnn-layer-group-${curLayerIndex - 1}`)\n      .selectAll('rect.bounding')\n      .style('stroke-width', 2);\n    \n    d3.select(g[i])\n      .select('rect.bounding')\n      .style('stroke-width', 2);\n    \n    // Disable control panel UI\n    // d3.select('#level-select').property('disabled', true);\n    // d3.selectAll('.image-container')\n    //   .style('cursor', 'not-allowed')\n    //   .on('mouseclick', () => {});\n    disableControl = true;\n    \n    // Allow infinite animation loop\n    shouldIntermediateAnimateStore.set(true);\n\n    // Highlight the labels\n    svg.selectAll(`g#layer-label-${curLayerIndex - 1},\n      g#layer-detailed-label-${curLayerIndex - 1},\n      g#layer-label-${curLayerIndex},\n      g#layer-detailed-label-${curLayerIndex}`)\n      .style('font-weight', '800');\n    \n    // Register a handler on the svg element so user can click empty space to quit\n    // the intermediate view\n    d3.select('#cnn-svg')\n      .on('click', emptySpaceClicked);\n  }\n\n  const quitActPoolDetailView = () => {\n    isInActPoolDetailView = false;\n    actPoolDetailViewNodeIndex = -1;\n\n    let layerIndex = layerIndexDict[selectedNode.layerName];\n    let nodeIndex = selectedNode.index;\n    svg.select(`g#layer-${layerIndex}-node-${nodeIndex}`)\n      .select('rect.bounding')\n      .classed('hidden', true);\n\n    selectedNode.data.inputLinks.forEach(link => {\n      let layerIndex = layerIndexDict[link.source.layerName];\n      let nodeIndex = link.source.index;\n      svg.select(`g#layer-${layerIndex}-node-${nodeIndex}`)\n        .select('rect.bounding')\n        .classed('hidden', true);\n    })\n\n    // Clean up the underneath rects\n    svg.select('g.underneath')\n      .selectAll('rect')\n      .remove();\n\n    // Show all edges\n    let unimportantEdges = svg.select('g.edge-group')\n      .selectAll('.edge')\n      .filter(d => {\n        return d.targetLayerIndex !== actPoolDetailViewLayerIndex;\n      })\n      .style('visibility', null);\n    \n    // Recover control UI\n    disableControl = false;\n\n    // Show legends if in detailed mode\n    svg.selectAll(`.${selectedScaleLevel}-legend`)\n      .classed('hidden', !detailedMode);\n    svg.selectAll('.input-legend').classed('hidden', !detailedMode);\n    svg.selectAll('.output-legend').classed('hidden', !detailedMode);\n\n    // Also dehighlight the edge\n    let edgeGroup = svg.select('g.cnn-group').select('g.edge-group');\n    edgeGroup.selectAll(`path.edge-${layerIndex}-${nodeIndex}`)\n      .transition()\n      .ease(d3.easeCubicOut)\n      .duration(200)\n      .style('stroke', edgeInitColor)\n      .style('stroke-width', edgeStrokeWidth)\n      .style('opacity', edgeOpacity);\n\n    // Remove the overlay rect\n    svg.selectAll('g.intermediate-layer-overlay, g.intermediate-layer-annotation')\n      .transition('remove')\n      .duration(500)\n      .ease(d3.easeCubicInOut)\n      .style('opacity', 0)\n      .on('end', (d, i, g) => {\n        svg.selectAll('g.intermediate-layer-overlay, g.intermediate-layer-annotation').remove();\n        svg.selectAll('defs.overlay-gradient').remove();\n        svg.select('.input-annotation').classed('hidden', false);\n      });\n\n    // Turn the fade out nodes back\n    svg.select(`g#cnn-layer-group-${layerIndex}`)\n      .selectAll('g.node-group')\n      .each((sd, si, sg) => {\n        d3.select(sg[si])\n          .style('pointer-events', 'all');\n    });\n\n    svg.select(`g#cnn-layer-group-${layerIndex - 1}`)\n      .selectAll('g.node-group')\n      .each((sd, si, sg) => {\n        // Recover the old events\n        d3.select(sg[si])\n          .style('pointer-events', 'all')\n          .on('mouseover', nodeMouseOverHandler)\n          .on('mouseleave', nodeMouseLeaveHandler)\n          .on('click', nodeClickHandler);\n    });\n\n    // Deselect the node\n    selectedNode.layerName = '';\n    selectedNode.index = -1;\n    selectedNode.data = null;\n\n    actPoolDetailViewLayerIndex = -1;\n  }\n\n  const actPoolDetailViewPreNodeMouseOverHandler = (d, i, g) => {\n    // Highlight the edges\n    let layerIndex = layerIndexDict[d.layerName];\n    let nodeIndex = d.index;\n    let edgeGroup = svg.select('g.cnn-group').select('g.edge-group');\n    \n    edgeGroup.selectAll(`path.edge-${actPoolDetailViewLayerIndex}-${nodeIndex}`)\n      .raise()\n      .transition()\n      .ease(d3.easeCubicInOut)\n      .duration(400)\n      .style('stroke', edgeHoverColor)\n      .style('stroke-width', '1')\n      .style('opacity', 1);\n    \n    // Highlight its border\n    d3.select(g[i]).select('rect.bounding')\n      .classed('hidden', false);\n    \n    // Highlight node's pair\n    let associatedLayerIndex = layerIndex - 1;\n    if (layerIndex === actPoolDetailViewLayerIndex - 1) {\n      associatedLayerIndex = layerIndex + 1;\n    }\n\n    svg.select(`g#layer-${associatedLayerIndex}-node-${nodeIndex}`)\n      .select('rect.bounding')\n      .classed('hidden', false);\n  }\n\n  const actPoolDetailViewPreNodeMouseLeaveHandler = (d, i, g) => {\n    // De-highlight the edges\n    let layerIndex = layerIndexDict[d.layerName];\n    let nodeIndex = d.index;\n    let edgeGroup = svg.select('g.cnn-group').select('g.edge-group');\n\n    edgeGroup.selectAll(`path.edge-${actPoolDetailViewLayerIndex}-${nodeIndex}`)\n      .transition()\n      .ease(d3.easeCubicOut)\n      .duration(200)\n      .style('stroke', edgeInitColor)\n      .style('stroke-width', edgeStrokeWidth)\n      .style('opacity', edgeOpacity);\n    \n    // De-highlight its border\n    d3.select(g[i]).select('rect.bounding')\n      .classed('hidden', true);\n    \n    // De-highlight node's pair\n    let associatedLayerIndex = layerIndex - 1;\n    if (layerIndex === actPoolDetailViewLayerIndex - 1) {\n      associatedLayerIndex = layerIndex + 1;\n    }\n\n    svg.select(`g#layer-${associatedLayerIndex}-node-${nodeIndex}`)\n      .select('rect.bounding')\n      .classed('hidden', true);\n  }\n\n  const actPoolDetailViewPreNodeClickHandler = (d, i, g) => {\n    let layerIndex = layerIndexDict[d.layerName];\n    let nodeIndex = d.index;\n\n    // Click the pre-layer node in detail view has the same effect as clicking\n    // the cur-layer node, which is to open a new detail view window\n    svg.select(`g#layer-${layerIndex + 1}-node-${nodeIndex}`)\n      .node()\n      .dispatchEvent(new Event('click'));\n  }\n\n  const enterDetailView = (curLayerIndex, i) => {\n    isInActPoolDetailView = true;\n    actPoolDetailViewNodeIndex = i;\n    actPoolDetailViewLayerIndex = curLayerIndex;\n\n    // Dynamically position the detail view\n    let wholeSvg = d3.select('#cnn-svg');\n    let svgYMid = +wholeSvg.style('height').replace('px', '') / 2;\n    let svgWidth = +wholeSvg.style('width').replace('px', '');\n    let detailViewTop = 100 + svgYMid - 260 / 2;\n\n    let posX = 0;\n    if (curLayerIndex > 5) {\n      posX = nodeCoordinate[curLayerIndex - 1][0].x + 50;\n      posX = posX / 2 - 500 / 2;\n    } else {\n      posX = (svgWidth - nodeCoordinate[curLayerIndex][0].x - nodeLength) / 2;\n      posX = nodeCoordinate[curLayerIndex][0].x + nodeLength + posX - 500 / 2;\n\n    }\n\n    const detailview = document.getElementById('detailview');\n    detailview.style.top = `${detailViewTop}px`;\n    detailview.style.left = `${posX}px`;\n    detailview.style.position = 'absolute';\n\n    // Hide all edges\n    let unimportantEdges = svg.select('g.edge-group')\n      .selectAll('.edge')\n      .filter(d => {\n        return d.targetLayerIndex !== curLayerIndex;\n      })\n      .style('visibility', 'hidden');\n    \n    // Disable UI\n    disableControl = true;\n    \n    // Hide input annotaitons\n    svg.select('.input-annotation')\n      .classed('hidden', true);\n\n    // Hide legends\n    svg.selectAll(`.${selectedScaleLevel}-legend`)\n      .classed('hidden', true);\n    svg.selectAll('.input-legend').classed('hidden', true);\n    svg.selectAll('.output-legend').classed('hidden', true);\n    svg.select(`#${layerLegendDict[curLayerIndex][selectedScaleLevel]}`)\n      .classed('hidden', false);\n\n    // Add overlay rects\n    let leftX = nodeCoordinate[curLayerIndex - 1][i].x;\n    // +5 to cover the detailed mode long label\n    let rightStart = nodeCoordinate[curLayerIndex][i].x + nodeLength + 5;\n\n    // Compute the left and right overlay rect width\n    let rightWidth = width - rightStart - overlayRectOffset / 2;\n    let leftWidth = leftX - nodeCoordinate[0][0].x;\n\n    // The overlay rects should be symmetric\n    if (rightWidth > leftWidth) {\n      let stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: 0.85},\n        {offset: '50%', color: 'rgb(250, 250, 250)', opacity: 0.9},\n        {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 1}];\n      addOverlayGradient('overlay-gradient-right', stops);\n      \n      let leftEndOpacity = 0.85 + (0.95 - 0.85) * (leftWidth / rightWidth);\n      stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: leftEndOpacity},\n        {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 0.85}];\n      addOverlayGradient('overlay-gradient-left', stops);\n    } else {\n      let stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: 1},\n        {offset: '50%', color: 'rgb(250, 250, 250)', opacity: 0.9},\n        {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 0.85}];\n      addOverlayGradient('overlay-gradient-left', stops);\n\n      let rightEndOpacity = 0.85 + (0.95 - 0.85) * (rightWidth / leftWidth);\n      stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: 0.85},\n        {offset: '100%', color: 'rgb(250, 250, 250)', opacity: rightEndOpacity}];\n      addOverlayGradient('overlay-gradient-right', stops);\n    }\n    \n    addOverlayRect('overlay-gradient-right',\n      rightStart + overlayRectOffset / 2 + 0.5,\n      0, rightWidth, height + svgPaddings.top);\n    \n    addOverlayRect('overlay-gradient-left',\n      nodeCoordinate[0][0].x - overlayRectOffset / 2,\n      0, leftWidth, height + svgPaddings.top);\n\n    svg.selectAll('rect.overlay')\n      .on('click', emptySpaceClicked);\n    \n    // Add underneath rectangles\n    let underGroup = svg.select('g.underneath');\n    let padding = 7;\n    for (let n = 0; n < cnn[curLayerIndex - 1].length; n++) {\n      underGroup.append('rect')\n        .attr('class', 'underneath-gateway')\n        .attr('id', `underneath-gateway-${n}`)\n        .attr('x', nodeCoordinate[curLayerIndex - 1][n].x - padding)\n        .attr('y', nodeCoordinate[curLayerIndex - 1][n].y - padding)\n        .attr('width', (2 * nodeLength + hSpaceAroundGap) + 2 * padding)\n        .attr('height', nodeLength + 2 * padding)\n        .attr('rx', 10)\n        .style('fill', 'rgba(160, 160, 160, 0.3)')\n        .style('opacity', 0);\n      \n      // Update the event functions for these two layers\n      svg.select(`g#layer-${curLayerIndex - 1}-node-${n}`)\n        .style('pointer-events', 'all')\n        .style('cursor', 'pointer')\n        .on('mouseover', actPoolDetailViewPreNodeMouseOverHandler)\n        .on('mouseleave', actPoolDetailViewPreNodeMouseLeaveHandler)\n        .on('click', actPoolDetailViewPreNodeClickHandler);\n    }\n    underGroup.lower();\n\n    // Highlight the selcted pair\n    underGroup.select(`#underneath-gateway-${i}`)\n      .style('opacity', 1);\n  }\n\n  const quitIntermediateView = (curLayerIndex, g, i) => {\n    // If it is the softmax detail view, quit that view first\n    if (isInSoftmax) {\n      svg.select('.logit-layer').remove();\n      svg.select('.logit-layer-lower').remove();\n      svg.selectAll('.plus-symbol-clone').remove();\n\n      // Instead of removing the paths, we hide them, so it is faster to load in\n      // the future\n      svg.select('.underneath')\n        .selectAll('.logit-lower')\n        .style('opacity', 0);\n\n      softmaxDetailViewStore.set({\n          show: false,\n          logits: []\n      })\n\n      allowsSoftmaxAnimationStore.set(false);\n    }\n    isInSoftmaxStore.set(false);\n    isInIntermediateView = false;\n\n    // Show the legend\n    svg.selectAll(`.${selectedScaleLevel}-legend`)\n      .classed('hidden', !detailedMode);\n    svg.selectAll('.input-legend').classed('hidden', !detailedMode);\n    svg.selectAll('.output-legend').classed('hidden', !detailedMode);\n\n    // Recover control panel UI\n    disableControl = false;\n\n    // Recover the input layer node's event\n    for (let n = 0; n < cnn[curLayerIndex - 1].length; n++) {\n      svg.select(`g#layer-${curLayerIndex - 1}-node-${n}`)\n        .on('mouseover', nodeMouseOverHandler)\n        .on('mouseleave', nodeMouseLeaveHandler)\n        .on('click', nodeClickHandler);\n    }\n\n    // Clean up the underneath rects\n    svg.select('g.underneath')\n      .selectAll('rect')\n      .remove();\n    detailedViewNum = undefined;\n\n    // Highlight the previous layer and this node\n    svg.select(`g#cnn-layer-group-${curLayerIndex - 1}`)\n      .selectAll('rect.bounding')\n      .style('stroke-width', 1);\n    \n    d3.select(g[i])\n      .select('rect.bounding')\n      .style('stroke-width', 1);\n\n    // Highlight the labels\n    svg.selectAll(`g#layer-label-${curLayerIndex - 1},\n      g#layer-detailed-label-${curLayerIndex - 1},\n      g#layer-label-${curLayerIndex},\n      g#layer-detailed-label-${curLayerIndex}`)\n      .style('font-weight', 'normal');\n\n    // Also unclick the node\n    // Record the current clicked node\n    selectedNode.layerName = '';\n    selectedNode.index = -1;\n    selectedNode.data = null;\n    isExitedFromCollapse = true;\n\n    // Remove the intermediate layer\n    let intermediateLayer = svg.select('g.intermediate-layer');\n\n    // Kill the infinite animation loop\n    shouldIntermediateAnimateStore.set(false);\n\n    intermediateLayer.transition('remove')\n      .duration(500)\n      .ease(d3.easeCubicInOut)\n      .style('opacity', 0)\n      .on('end', (d, i, g) => { d3.select(g[i]).remove()});\n    \n    // Remove the output node overlay mask\n    svg.selectAll('.overlay-group').remove();\n    \n    // Remove the overlay rect\n    svg.selectAll('g.intermediate-layer-overlay, g.intermediate-layer-annotation')\n      .transition('remove')\n      .duration(500)\n      .ease(d3.easeCubicInOut)\n      .style('opacity', 0)\n      .on('end', (d, i, g) => {\n        svg.selectAll('g.intermediate-layer-overlay, g.intermediate-layer-annotation').remove();\n        svg.selectAll('defs.overlay-gradient').remove();\n      });\n    \n    // Recover the layer if we have drdrawn it\n    if (needRedraw[0] !== undefined) {\n      let redrawRange = cnnLayerRanges[selectedScaleLevel][needRedraw[0]];\n      if (needRedraw[1] !== undefined) {\n        svg.select(`g#layer-${needRedraw[0]}-node-${needRedraw[1]}`)\n          .select('image.node-image')\n          .each((d, i, g) => drawOutput(d, i, g, redrawRange));\n      } else {\n        svg.select(`g#cnn-layer-group-${needRedraw[0]}`)\n          .selectAll('image.node-image')\n          .each((d, i, g) => drawOutput(d, i, g, redrawRange));\n      }\n    }\n    \n    // Move all layers to their original place\n    for (let i = 0; i < numLayers; i++) {\n      moveLayerX({layerIndex: i, targetX: nodeCoordinate[i][0].x,\n        disable:false, delay:500, opacity: 1});\n    }\n\n    moveLayerX({layerIndex: numLayers - 2,\n      targetX: nodeCoordinate[numLayers - 2][0].x, opacity: 1,\n      disable:false, delay:500, onEndFunc: () => {\n        // Show all edges on the last moving animation end\n        svg.select('g.edge-group')\n          .style('visibility', 'visible');\n\n        // Recover the input annotation\n        svg.select('.input-annotation')\n          .classed('hidden', false);\n      }});\n  }\n\n  const nodeClickHandler = (d, i, g) => {\n    d3.event.stopPropagation();\n    let nodeIndex = d.index;\n\n    // Record the current clicked node\n    selectedNode.layerName = d.layerName;\n    selectedNode.index = d.index;\n    selectedNode.data = d;\n    selectedNode.domI = i;\n    selectedNode.domG = g;\n\n    // Record data for detailed view.\n    if (d.type === 'conv' || d.type === 'relu' || d.type === 'pool') {\n      let data = [];\n      for (let j = 0; j < d.inputLinks.length; j++) {\n        data.push({\n          input: d.inputLinks[j].source.output,\n          kernel: d.inputLinks[j].weight,\n          output: d.inputLinks[j].dest.output,\n        })\n      }\n      let curLayerIndex = layerIndexDict[d.layerName];\n      data.colorRange = cnnLayerRanges[selectedScaleLevel][curLayerIndex];\n      data.isInputInputLayer = curLayerIndex <= 1;\n      nodeData = data;\n    }\n\n    let curLayerIndex = layerIndexDict[d.layerName];\n\n    if (d.type == 'relu' || d.type == 'pool') {\n      isExitedFromDetailedView = false;\n      if (!isInActPoolDetailView) {\n        // Enter the act pool detail view\n        enterDetailView(curLayerIndex, d.index);\n      } else {\n        if (d.index === actPoolDetailViewNodeIndex) {\n          // Quit the act pool detail view\n          quitActPoolDetailView();\n        } else {\n          // Switch the detail view input to the new clicked pair\n\n          // Remove the previous selection effect\n          svg.select(`g#layer-${curLayerIndex}-node-${actPoolDetailViewNodeIndex}`)\n            .select('rect.bounding')\n            .classed('hidden', true);\n\n          svg.select(`g#layer-${curLayerIndex - 1}-node-${actPoolDetailViewNodeIndex}`)\n            .select('rect.bounding')\n            .classed('hidden', true);\n          \n          let edgeGroup = svg.select('g.cnn-group').select('g.edge-group');\n      \n          edgeGroup.selectAll(`path.edge-${curLayerIndex}-${actPoolDetailViewNodeIndex}`)\n            .transition()\n            .ease(d3.easeCubicOut)\n            .duration(200)\n            .style('stroke', edgeInitColor)\n            .style('stroke-width', edgeStrokeWidth)\n            .style('opacity', edgeOpacity);\n          \n          let underGroup = svg.select('g.underneath');\n          underGroup.select(`#underneath-gateway-${actPoolDetailViewNodeIndex}`)\n            .style('opacity', 0);\n        \n          // Add selection effect on the new selected pair\n          svg.select(`g#layer-${curLayerIndex}-node-${nodeIndex}`)\n            .select('rect.bounding')\n            .classed('hidden', false);\n\n          svg.select(`g#layer-${curLayerIndex - 1}-node-${nodeIndex}`)\n            .select('rect.bounding')\n            .classed('hidden', false);\n\n          edgeGroup.selectAll(`path.edge-${curLayerIndex}-${nodeIndex}`)\n            .raise()\n            .transition()\n            .ease(d3.easeCubicInOut)\n            .duration(400)\n            .style('stroke', edgeHoverColor)\n            .style('stroke-width', '1')\n            .style('opacity', 1);\n\n          underGroup.select(`#underneath-gateway-${nodeIndex}`)\n            .style('opacity', 1);\n\n          actPoolDetailViewNodeIndex = nodeIndex;\n        }\n      }\n    }\n\n    // Enter the second view (layer-view) when user clicks a conv node\n    if ((d.type === 'conv' || d.layerName === 'output') && !isInIntermediateView) {\n      prepareToEnterIntermediateView(d, g, nodeIndex, curLayerIndex);\n\n      if (d.layerName === 'conv_1_1') {\n        drawConv1(curLayerIndex, d, nodeIndex, width, height,\n          intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n          intermediateNodeClicked);\n      }\n\n      else if (d.layerName === 'conv_1_2') {\n        drawConv2(curLayerIndex, d, nodeIndex, width, height,\n          intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n          intermediateNodeClicked);\n      }\n\n      else if (d.layerName === 'conv_2_1') {\n        drawConv3(curLayerIndex, d, nodeIndex, width, height,\n          intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n          intermediateNodeClicked);\n      }\n      \n      else if (d.layerName === 'conv_2_2') {\n        drawConv4(curLayerIndex, d, nodeIndex, width, height,\n          intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n          intermediateNodeClicked);\n      }\n    \n      else if (d.layerName === 'output') {\n        drawFlatten(curLayerIndex, d, nodeIndex, width, height);\n      }\n    }\n    // Quit the layerview\n    else if ((d.type === 'conv' || d.layerName === 'output') && isInIntermediateView) {\n      quitIntermediateView(curLayerIndex, g, i);\n    }\n  }\n\n  const nodeMouseOverHandler = (d, i, g) => {\n    // if (isInIntermediateView || isInActPoolDetailView) { return; }\n    if (isInIntermediateView) { return; }\n\n    // Highlight the edges\n    let layerIndex = layerIndexDict[d.layerName];\n    let nodeIndex = d.index;\n    let edgeGroup = svg.select('g.cnn-group').select('g.edge-group');\n    \n    edgeGroup.selectAll(`path.edge-${layerIndex}-${nodeIndex}`)\n      .raise()\n      .transition()\n      .ease(d3.easeCubicInOut)\n      .duration(400)\n      .style('stroke', edgeHoverColor)\n      .style('stroke-width', '1')\n      .style('opacity', 1);\n    \n    // Highlight its border\n    d3.select(g[i]).select('rect.bounding')\n      .classed('hidden', false);\n    \n    // Highlight source's border\n    if (d.inputLinks.length === 1) {\n      let link = d.inputLinks[0];\n      let layerIndex = layerIndexDict[link.source.layerName];\n      let nodeIndex = link.source.index;\n      svg.select(`g#layer-${layerIndex}-node-${nodeIndex}`)\n        .select('rect.bounding')\n        .classed('hidden', false);\n    } else {\n      svg.select(`g#cnn-layer-group-${layerIndex - 1}`)\n        .selectAll('g.node-group')\n        .selectAll('rect.bounding')\n        .classed('hidden', false);\n    }\n\n    // Highlight the output text\n    if (d.layerName === 'output') {\n      d3.select(g[i])\n        .select('.output-text')\n        .style('opacity', 0.8)\n        .style('text-decoration', 'underline');\n    }\n\n    /* Use the following commented code if we have non-linear model\n    d.inputLinks.forEach(link => {\n      let layerIndex = layerIndexDict[link.source.layerName];\n      let nodeIndex = link.source.index;\n      svg.select(`g#layer-${layerIndex}-node-${nodeIndex}`)\n        .select('rect.bounding')\n        .classed('hidden', false);\n    });\n    */\n  }\n\n  const nodeMouseLeaveHandler = (d, i, g) => {\n    // Screenshot\n    // return;\n\n    if (isInIntermediateView) { return; }\n    \n    // Keep the highlight if user has clicked\n    if (isInActPoolDetailView || (\n      d.layerName !== selectedNode.layerName ||\n      d.index !== selectedNode.index)){\n      let layerIndex = layerIndexDict[d.layerName];\n      let nodeIndex = d.index;\n      let edgeGroup = svg.select('g.cnn-group').select('g.edge-group');\n      \n      edgeGroup.selectAll(`path.edge-${layerIndex}-${nodeIndex}`)\n        .transition()\n        .ease(d3.easeCubicOut)\n        .duration(200)\n        .style('stroke', edgeInitColor)\n        .style('stroke-width', edgeStrokeWidth)\n        .style('opacity', edgeOpacity);\n\n      d3.select(g[i]).select('rect.bounding').classed('hidden', true);\n\n      if (d.inputLinks.length === 1) {\n        let link = d.inputLinks[0];\n        let layerIndex = layerIndexDict[link.source.layerName];\n        let nodeIndex = link.source.index;\n        svg.select(`g#layer-${layerIndex}-node-${nodeIndex}`)\n          .select('rect.bounding')\n          .classed('hidden', true);\n      } else {\n        svg.select(`g#cnn-layer-group-${layerIndex - 1}`)\n          .selectAll('g.node-group')\n          .selectAll('rect.bounding')\n          .classed('hidden', d => d.layerName !== selectedNode.layerName ||\n            d.index !== selectedNode.index);\n      }\n\n      // Dehighlight the output text\n      if (d.layerName === 'output') {\n        d3.select(g[i])\n          .select('.output-text')\n          .style('fill', 'black')\n          .style('opacity', 0.5)\n          .style('text-decoration', 'none');\n      }\n\n      /* Use the following commented code if we have non-linear model\n      d.inputLinks.forEach(link => {\n        let layerIndex = layerIndexDict[link.source.layerName];\n        let nodeIndex = link.source.index;\n        svg.select(`g#layer-${layerIndex}-node-${nodeIndex}`)\n          .select('rect.bounding')\n          .classed('hidden', true);\n      });\n      */\n    }\n  }\n  let logits = [-4.28, 2.96, -0.38, 5.24, -7.56, -3.43, 8.63, 2.63, 6.30, 0.68];\n  let selectedI = 4;\n\n  onMount(async () => {\n    // Create SVG\n    wholeSvg = d3.select(overviewComponent)\n      .select('#cnn-svg');\n    svg = wholeSvg.append('g')\n      .attr('class', 'main-svg')\n      .attr('transform', `translate(${svgPaddings.left}, 0)`);\n    svgStore.set(svg);\n\n    width = Number(wholeSvg.style('width').replace('px', '')) -\n      svgPaddings.left - svgPaddings.right;\n    height = Number(wholeSvg.style('height').replace('px', '')) -\n      svgPaddings.top - svgPaddings.bottom;\n\n    let cnnGroup = svg.append('g')\n      .attr('class', 'cnn-group');\n    \n    let underGroup = svg.append('g')\n      .attr('class', 'underneath');\n\n    let svgYMid = +wholeSvg.style('height').replace('px', '') / 2;\n    detailedViewAbsCoords = {\n      1 : [600, 100 + svgYMid - 220 / 2, 490, 290],\n      2: [500, 100 + svgYMid - 220 / 2, 490, 290],\n      3 : [700, 100 + svgYMid - 220 / 2, 490, 290],\n      4: [600, 100 + svgYMid - 220 / 2, 490, 290],\n      5: [650, 100 + svgYMid - 220 / 2, 490, 290],\n      6 : [850, 100 + svgYMid - 220 / 2, 490, 290],\n      7 : [100, 100 + svgYMid - 220 / 2, 490, 290],\n      8 : [60, 100 + svgYMid - 220 / 2, 490, 290],\n      9 : [200, 100 + svgYMid - 220 / 2, 490, 290],\n      10 : [300, 100 + svgYMid - 220 / 2, 490, 290],\n    }\n    \n    // Define global arrow marker end\n    svg.append(\"defs\")\n      .append(\"marker\")\n      .attr(\"id\", 'marker')\n      .attr(\"viewBox\", \"0 -5 10 10\")\n      .attr(\"refX\", 6)\n      .attr(\"refY\", 0)\n      .attr(\"markerWidth\", 6)\n      .attr(\"markerHeight\", 6)\n      .attr(\"orient\", \"auto\")\n      .append(\"path\")\n      .style('stroke-width', 1.2)\n      .style('fill', 'gray')\n      .style('stroke', 'gray')\n      .attr(\"d\", \"M0,-5L10,0L0,5\");\n\n    // Alternative arrow head style for non-interactive annotation\n    svg.append(\"defs\")\n      .append(\"marker\")\n      .attr(\"id\", 'marker-alt')\n      .attr(\"viewBox\", \"0 -5 10 10\")\n      .attr(\"refX\", 6)\n      .attr(\"refY\", 0)\n      .attr(\"markerWidth\", 6)\n      .attr(\"markerHeight\", 6)\n      .attr(\"orient\", \"auto\")\n      .append(\"path\")\n      .style('fill', 'none')\n      .style('stroke', 'gray')\n      .style('stroke-width', 2)\n      .attr(\"d\", \"M-5,-10L10,0L-5,10\");\n    \n    console.time('Construct cnn');\n    model = await loadTrainedModel('PUBLIC_URL/assets/data/model.json');\n    cnn = await constructCNN(`PUBLIC_URL/assets/img/${selectedImage}`, model);\n    console.timeEnd('Construct cnn');\n    cnnStore.set(cnn);\n\n    // Ignore the flatten layer for now\n    let flatten = cnn[cnn.length - 2];\n    cnn.splice(cnn.length - 2, 1);\n    cnn.flatten = flatten;\n    console.log(cnn);\n\n    updateCNNLayerRanges();\n\n    // Create and draw the CNN view\n    drawCNN(width, height, cnnGroup, nodeMouseOverHandler,\n      nodeMouseLeaveHandler, nodeClickHandler);\n  })\n\n  const detailedButtonClicked = () => {\n    detailedMode = !detailedMode;\n    detailedModeStore.set(detailedMode);\n\n    if (!isInIntermediateView){\n      // Show the legend\n      svg.selectAll(`.${selectedScaleLevel}-legend`)\n        .classed('hidden', !detailedMode);\n      \n      svg.selectAll('.input-legend').classed('hidden', !detailedMode);\n      svg.selectAll('.output-legend').classed('hidden', !detailedMode);\n    }\n    \n    // Switch the layer name\n    svg.selectAll('.layer-detailed-label')\n      .classed('hidden', !detailedMode);\n    \n    svg.selectAll('.layer-label')\n      .classed('hidden', detailedMode);\n  }\n\n  const imageOptionClicked = async (e) => {\n    let newImageName = d3.select(e.target).attr('data-imageName');\n\n    if (newImageName !== selectedImage) {\n      selectedImage = newImageName;\n\n      // Re-compute the CNN using the new input image\n      cnn = await constructCNN(`PUBLIC_URL/assets/img/${selectedImage}`, model);\n\n      // Ignore the flatten layer for now\n      let flatten = cnn[cnn.length - 2];\n      cnn.splice(cnn.length - 2, 1);\n      cnn.flatten = flatten;\n      cnnStore.set(cnn);\n\n      // Update all scales used in the CNN view\n      updateCNNLayerRanges();\n      updateCNN();\n    }\n  }\n\n  const customImageClicked = () => {\n\n    // Case 1: there is no custom image -> show the modal to get user input\n    if (customImageURL === null) {\n      modalInfo.show = true;\n      modalInfo.preImage = selectedImage;\n      modalStore.set(modalInfo);\n    }\n\n    // Case 2: there is an existing custom image, not the focus -> switch to this image\n    else if (selectedImage !== 'custom') {\n      let fakeEvent = {detail: {url: customImageURL}};\n      handleCustomImage(fakeEvent);\n    }\n\n    // Case 3: there is an existing custom image, and its the focus -> let user\n    // upload a new image\n    else {\n      modalInfo.show = true;\n      modalInfo.preImage = selectedImage;\n      modalStore.set(modalInfo);\n    }\n\n    if (selectedImage !== 'custom') {\n      selectedImage = 'custom';\n    }\n\n  }\n\n  const handleModalCanceled = (event) => {\n    // User cancels the modal without a successful image, so we restore the\n    // previous selected image as input\n    selectedImage = event.detail.preImage;\n  }\n\n  const handleCustomImage = async (event) => {\n    // User gives a valid image URL\n    customImageURL = event.detail.url;\n\n    // Re-compute the CNN using the new input image\n    cnn = await constructCNN(customImageURL, model);\n\n    // Ignore the flatten layer for now\n    let flatten = cnn[cnn.length - 2];\n    cnn.splice(cnn.length - 2, 1);\n    cnn.flatten = flatten;\n    cnnStore.set(cnn);\n\n    // Update the UI\n    let customImageSlot = d3.select(overviewComponent)\n      .select('.custom-image').node();\n    drawCustomImage(customImageSlot, cnn[0]);\n\n    // Update all scales used in the CNN view\n    updateCNNLayerRanges();\n    updateCNN();\n  }\n\n  function handleExitFromDetiledConvView(event) {\n    if (event.detail.text) {\n      detailedViewNum = undefined;\n      svg.select(`rect#underneath-gateway-${selectedNodeIndex}`)\n        .style('opacity', 0);\n      selectedNodeIndex = -1; \n    }\n  }\n\n  function handleExitFromDetiledPoolView(event) {\n    if (event.detail.text) {\n      quitActPoolDetailView();\n      isExitedFromDetailedView = true;\n    }\n  }\n\n  function handleExitFromDetiledActivationView(event) {\n    if (event.detail.text) {\n      quitActPoolDetailView();\n      isExitedFromDetailedView = true;\n    }\n  }\n\n  function handleExitFromDetiledSoftmaxView(event) {\n    softmaxDetailViewInfo.show = false;\n    softmaxDetailViewStore.set(softmaxDetailViewInfo);\n  }\n\n</script>\n\n<style>\n  .overview {\n    padding: 0;\n    height: 100%;\n    width: 100%;\n    display: flex;\n    position: relative;\n    flex-direction: column;\n    justify-content: space-between;\n    align-items: flex-start;\n  }\n\n  .control-container {\n    padding: 5px 20px;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    width: 100%;\n  }\n\n  .right-control {\n    display: flex;\n  }\n\n  .left-control {\n    display: flex;\n    align-items: center;\n  }\n\n  .control > .select > #level-select {\n    padding-left: 2em;\n    padding-right: 2em;\n  }\n\n  .cnn {\n    width: 100%;\n    padding: 0;\n    background: var(--light-gray);\n    display: flex;\n  }\n\n  svg {\n    margin: 0 auto;\n    min-height: 490px;\n    max-height: 700px;\n    height: calc(100vh - 100px);\n    width: 100vw;\n  }\n\n  .is-very-small {\n    font-size: 12px;\n  }\n\n  #detailed-button {\n    margin-right: 10px;\n    color: #dbdbdb;\n    transition: border-color 300ms ease-in-out, color 200ms ease-in-out;\n  }\n\n  #detailed-button.is-activated, #detailed-button.is-activated:hover {\n    color: #3273dc;\n    border-color: #3273dc;\n  }\n\n  #detailed-button:hover {\n    color: #b5b5b5;\n  }\n\n  #hover-label {\n    transition: opacity 300ms ease-in-out;\n    text-overflow: ellipsis;\n    pointer-events: none;\n    margin-left: 5px;\n  }\n\n  .image-container {\n    width: 40px;\n    height: 40px;\n    border-radius: 4px;\n    display: inline-block;\n    position: relative;\n    border: 2.5px solid #1E1E1E;\n    margin-right: 10px;\n    cursor: pointer;\n    pointer-events: all;\n    transition: border 300ms ease-in-out;\n  }\n\n  .image-container img {\n    object-fit: cover;\n    max-width: 100%;\n    max-height: 100%;\n    z-index: -1;\n    transition: opacity 300ms ease-in-out;\n  }\n\n  .image-container.inactive {\n    border: 2.5px solid rgb(220, 220, 220);\n  }\n\n  .image-container.inactive > img {\n    opacity: 0.3;\n  }\n\n  .image-container.inactive:hover > img {\n    opacity: 0.6;\n  }\n\n  .image-container.inactive.disabled {\n    border: 2.5px solid rgb(220, 220, 220);\n    cursor: not-allowed;\n  }\n\n  .image-container.inactive.disabled:hover {\n    border: 2.5px solid rgb(220, 220, 220);\n    cursor: not-allowed;\n  }\n\n  .image-container.inactive.disabled > img {\n    opacity: 0.3;\n    cursor: not-allowed;\n  }\n\n  .image-container.inactive.disabled:hover > img {\n    opacity: 0.3;\n    cursor: not-allowed;\n  }\n\n  .image-container.inactive > .edit-icon {\n    color: #BABABA;\n  }\n\n  .image-container.inactive:hover > .edit-icon {\n    color: #777777;\n  }\n\n  .image-container.inactive:hover {\n    border: 2.5px solid #1E1E1E;\n  }\n\n  .edit-icon {\n    position: absolute;\n    bottom: -6px;\n    right: -7px;\n    font-size: 7px;\n    color: #1E1E1E;\n    transition: color 300ms ease-in-out;\n  }\n\n  :global(canvas) {\n    image-rendering: crisp-edges;\n  }\n\n  :global(.layer-label), :global(.layer-detailed-label), :global(.layer-intermediate-label) {\n    font-size: 12px;\n    opacity: 0.8;\n    text-anchor: middle;\n  }\n\n  :global(.colorLegend) {\n    font-size: 10px;\n  }\n\n  :global(.legend) {\n    transition: opacity 400ms ease-in-out;\n  }\n\n  :global(.legend > rect) {\n    opacity: 1;\n  }\n\n  :global(.legend text), :global(.legend line), :global(.legend path) {\n    opacity: 0.7;\n  }\n\n  :global(.legend#output-legend > rect) {\n    opacity: 1;\n  }\n\n  :global(.hidden) {\n    opacity: 0;\n    pointer-events: none;\n  }\n\n  :global(.very-strong) {\n    stroke-width: 3px;\n  }\n\n  :global(.bounding), :global(.edge), :global(.edge-group),\n  :global(foreignObject), :global(.bounding-flatten),\n  :global(.underneath-gateway), :global(.input-annotation) {\n    transition: opacity 300ms ease-in-out;\n  }\n\n  :global(rect.bounding) {\n    transition: stroke-width 800ms ease-in-out, opacity 300ms ease-in-out;\n  }\n\n  :global(.annotation-text) {\n    pointer-events: none;\n    font-size: 10px;\n    font-style: italic;\n    fill: gray;\n  }\n\n  /* Change the cursor style on the detailed view input and output matrices */\n  :global(rect.square) {\n    cursor: crosshair;\n  }\n\n  :global(.animation-control-button) {\n    font-family: FontAwesome;\n    opacity: 0.8;\n    cursor: pointer;\n  }\n\n</style>\n\n<div class=\"overview\"\n  bind:this={overviewComponent}>\n\n  <div class=\"control-container\">\n\n    <div class=\"left-control\">\n      {#each imageOptions as image, i}\n        <div class=\"image-container\"\n          on:click={disableControl ? () => {} : imageOptionClicked}\n          class:inactive={selectedImage !== image.file}\n          class:disabled={disableControl}\n          data-imageName={image.file}>\n          <img src=\"PUBLIC_URL/assets/img/{image.file}\"\n            alt=\"image option\"\n            title=\"{image.class}\"\n            data-imageName={image.file}/>\n        </div>\n      {/each}\n\n      <!-- The plus button -->\n        <div class=\"image-container\"\n          class:inactive={selectedImage !== 'custom'}\n          class:disabled={disableControl}\n          data-imageName={'custom'}\n          on:click={disableControl ? () => {} : customImageClicked}>\n\n          <img class=\"custom-image\"\n            src=\"PUBLIC_URL/assets/img/plus.svg\"\n            alt=\"plus button\"\n            title=\"Add new input image\"\n            data-imageName=\"custom\"/>\n\n          <span class=\"fa-stack edit-icon\"\n            class:hidden={customImageURL === null}>\n            <i class=\"fas fa-circle fa-stack-2x\"></i>\n            <i class=\"fas fa-pen fa-stack-1x fa-inverse\"></i>\n          </span>\n\n        </div>\n\n      <button class=\"button is-very-small is-link is-light\"\n        id=\"hover-label\"\n        style=\"opacity:{hoverInfo.show ? 1 : 0}\">\n        <span class=\"icon\" style=\"margin-right: 5px;\">\n          <i class=\"fas fa-crosshairs \"></i>\n        </span>\n        <span id=\"hover-label-text\">\n          {hoverInfo.text}\n        </span>\n      </button>\n    </div>\n\n    <div class=\"right-control\">\n\n      <button class=\"button is-very-small\"\n        id=\"detailed-button\"\n        disabled={disableControl}\n        class:is-activated={detailedMode}\n        on:click={detailedButtonClicked}>\n        <span class=\"icon\">\n          <i class=\"fas fa-eye\"></i>\n        </span>\n        <span id=\"hover-label-text\">\n          Show detail\n        </span>\n      </button>\n\n      <div class=\"control is-very-small has-icons-left\"\n        title=\"Change color scale range\">\n        <span class=\"icon is-left\">\n          <i class=\"fas fa-palette\"></i>\n        </span>\n\n        <div class=\"select\">\n          <select bind:value={selectedScaleLevel} id=\"level-select\"\n            disabled={disableControl}>\n            <option value=\"local\">Unit</option>\n            <option value=\"module\">Module</option>\n            <option value=\"global\">Global</option>\n          </select>\n        </div>\n      </div>\n\n    </div>\n    \n  </div>\n\n  <div class=\"cnn\">\n    <svg id=\"cnn-svg\"></svg>\n  </div>\n</div>\n\n<Article/>\n\n<div id='detailview'>\n  {#if selectedNode.data && selectedNode.data.type === 'conv' && selectedNodeIndex != -1}\n    <ConvolutionView on:message={handleExitFromDetiledConvView} input={nodeData[selectedNodeIndex].input} \n                      kernel={nodeData[selectedNodeIndex].kernel}\n                      dataRange={nodeData.colorRange}\n                      colorScale={nodeData.inputIsInputLayer ?\n                        layerColorScales.input[0] : layerColorScales.conv}\n                      isInputInputLayer={nodeData.inputIsInputLayer}\n                      isExited={isExitedFromCollapse}/>\n  {:else if selectedNode.data && selectedNode.data.type === 'relu'}\n    <ActivationView on:message={handleExitFromDetiledActivationView} input={nodeData[0].input} \n                    output={nodeData[0].output}\n                    dataRange={nodeData.colorRange}\n                    isExited={isExitedFromDetailedView}/>\n  {:else if selectedNode.data && selectedNode.data.type === 'pool'}\n    <PoolView on:message={handleExitFromDetiledPoolView} input={nodeData[0].input} \n              kernelLength={2}\n              dataRange={nodeData.colorRange}\n              isExited={isExitedFromDetailedView}/>\n  {:else if softmaxDetailViewInfo.show}\n    <SoftmaxView logits={softmaxDetailViewInfo.logits}\n                 logitColors={softmaxDetailViewInfo.logitColors}\n                 selectedI={softmaxDetailViewInfo.selectedI}\n                 highlightI={softmaxDetailViewInfo.highlightI}\n                 outputName={softmaxDetailViewInfo.outputName}\n                 outputValue={softmaxDetailViewInfo.outputValue}\n                 startAnimation={softmaxDetailViewInfo.startAnimation}\n                 on:xClicked={handleExitFromDetiledSoftmaxView}\n                 on:mouseOver={softmaxDetailViewMouseOverHandler}\n                 on:mouseLeave={softmaxDetailViewMouseLeaveHandler}/>\n  {/if}\n</div>\n\n<Modal on:xClicked={handleModalCanceled}\n  on:urlTyped={handleCustomImage}/>"
  },
  {
    "path": "src/overview/draw-utils.js",
    "content": "import { overviewConfig } from '../config.js';\n\n// Configs\nconst nodeLength = overviewConfig.nodeLength;\n\n/**\n * Compute the [minimum, maximum] of a 1D or 2D array.\n * @param {[number]} array \n */\nexport  const getExtent = (array) => {\n  let min = Infinity;\n  let max = -Infinity;\n\n  // Scalar\n  if (array.length === undefined) {\n    return [array, array];\n  }\n\n  // 1D array\n  if (array[0].length === undefined) {\n    for (let i = 0; i < array[0].length; i++) {\n      if (array[i] < min) {\n        min = array[i];\n      } else if (array[i] > max) {\n        max = array[i];\n      }\n    }\n    return [min, max];\n  }\n\n  // 2D array\n  for (let i = 0; i < array.length; i++) {\n    for (let j = 0; j < array[0].length; j++) {\n      if (array[i][j] < min) {\n        min = array[i][j];\n      } else if (array[i][j] > max) {\n        max = array[i][j];\n      }\n    }\n  }\n  return [min, max];\n}\n\n/**\n * Convert the svg element center coord to document absolute value\n * // Inspired by https://github.com/caged/d3-tip/blob/master/index.js#L286\n * @param {elem} elem \n */\nexport const getMidCoords = (svg, elem) => {\n  if (svg !== undefined) {\n    let targetel = elem;\n    while (targetel.getScreenCTM == null && targetel.parentNode != null) {\n      targetel = targetel.parentNode;\n    }\n    // Get the absolute coordinate of the E point of element bbox\n    let point = svg.node().ownerSVGElement.createSVGPoint();\n    let matrix = targetel.getScreenCTM();\n    let tbbox = targetel.getBBox();\n    // let width = tbbox.width;\n    let height = tbbox.height;\n\n    point.x += 0;\n    point.y -= height / 2;\n    let bbox = point.matrixTransform(matrix);\n    return {\n      top: bbox.y,\n      left: bbox.x\n    };\n  }\n}\n\n/**\n * Return the output knot (right boundary center)\n * @param {object} point {x: x, y:y}\n */\nexport const getOutputKnot = (point) => {\n  return {\n    x: point.x + nodeLength,\n    y: point.y + nodeLength / 2\n  };\n}\n\n/**\n * Return the output knot (left boundary center)\n * @param {object} point {x: x, y:y}\n */\nexport const getInputKnot = (point) => {\n  return {\n    x: point.x,\n    y: point.y + nodeLength / 2\n  }\n}\n\n/**\n * Compute edge data\n * @param {[[[number, number]]]} nodeCoordinate Constructed neuron svg locations\n * @param {[object]} cnn Constructed CNN model\n */\nexport const getLinkData = (nodeCoordinate, cnn) => {\n  let linkData = [];\n  // Create links backward (starting for the first conv layer)\n  for (let l = 1; l < cnn.length; l++) {\n    for (let n = 0; n < cnn[l].length; n++) {\n      let isOutput = cnn[l][n].layerName === 'output';\n      let curTarget = getInputKnot(nodeCoordinate[l][n]);\n      for (let p = 0; p < cnn[l][n].inputLinks.length; p++) {\n        // Specially handle output layer (since we are ignoring the flatten)\n        let inputNodeIndex = cnn[l][n].inputLinks[p].source.index;\n        \n        if (isOutput) {\n          let flattenDimension = cnn[l-1][0].output.length *\n            cnn[l-1][0].output.length;\n          if (inputNodeIndex % flattenDimension !== 0){\n              continue;\n          }\n          inputNodeIndex = Math.floor(inputNodeIndex / flattenDimension);\n        }\n        let curSource = getOutputKnot(nodeCoordinate[l-1][inputNodeIndex]);\n        let curWeight = cnn[l][n].inputLinks[p].weight;\n        linkData.push({\n          source: curSource,\n          target: curTarget,\n          weight: curWeight,\n          targetLayerIndex: l,\n          targetNodeIndex: n,\n          sourceNodeIndex: inputNodeIndex\n        });\n      }\n    }\n  }\n  return linkData;\n}\n\n\n/**\n * Color scale wrapper (support artificially lighter color!)\n * @param {function} colorScale D3 color scale function\n * @param {number} range Color range (max - min)\n * @param {number} value Color value\n * @param {number} gap Tail of the color scale to skip\n */\nexport const gappedColorScale = (colorScale, range, value, gap) => {\n  if (gap === undefined) { gap = 0; }\n  let normalizedValue = (value + range / 2) / range;\n  return colorScale(normalizedValue * (1 - 2 * gap) + gap);\n}"
  },
  {
    "path": "src/overview/flatten-draw.js",
    "content": "/* global d3, SmoothScroll */\n\nimport {\n  svgStore, vSpaceAroundGapStore, hSpaceAroundGapStore, cnnStore,\n  nodeCoordinateStore, selectedScaleLevelStore, cnnLayerRangesStore,\n  cnnLayerMinMaxStore, isInSoftmaxStore, softmaxDetailViewStore,\n  hoverInfoStore, allowsSoftmaxAnimationStore, detailedModeStore\n} from '../stores.js';\nimport {\n  getOutputKnot, getInputKnot, gappedColorScale, getMidCoords\n} from './draw-utils.js';\nimport {\n  drawIntermediateLayerLegend, moveLayerX, addOverlayGradient,\n  drawArrow\n} from './intermediate-utils.js';\nimport { overviewConfig } from '../config.js';\n\n// Configs\nconst layerColorScales = overviewConfig.layerColorScales;\nconst nodeLength = overviewConfig.nodeLength;\nconst plusSymbolRadius = overviewConfig.plusSymbolRadius;\nconst intermediateColor = overviewConfig.intermediateColor;\nconst kernelRectLength = overviewConfig.kernelRectLength;\nconst svgPaddings = overviewConfig.svgPaddings;\nconst gapRatio = overviewConfig.gapRatio;\nconst classList = overviewConfig.classLists;\nconst formater = d3.format('.4f');\n\n// Shared variables\nlet svg = undefined;\nsvgStore.subscribe( value => {svg = value;} )\n\nlet vSpaceAroundGap = undefined;\nvSpaceAroundGapStore.subscribe( value => {vSpaceAroundGap = value;} )\n\nlet hSpaceAroundGap = undefined;\nhSpaceAroundGapStore.subscribe( value => {hSpaceAroundGap = value;} )\n\nlet cnn = undefined;\ncnnStore.subscribe( value => {cnn = value;} )\n\nlet nodeCoordinate = undefined;\nnodeCoordinateStore.subscribe( value => {nodeCoordinate = value;} )\n\nlet selectedScaleLevel = undefined;\nselectedScaleLevelStore.subscribe( value => {selectedScaleLevel = value;} )\n\nlet cnnLayerRanges = undefined;\ncnnLayerRangesStore.subscribe( value => {cnnLayerRanges = value;} )\n\nlet cnnLayerMinMax = undefined;\ncnnLayerMinMaxStore.subscribe( value => {cnnLayerMinMax = value;} )\n\nlet isInSoftmax = undefined;\nisInSoftmaxStore.subscribe( value => {isInSoftmax = value;} )\n\nlet allowsSoftmaxAnimation = undefined;\nallowsSoftmaxAnimationStore.subscribe( value => {allowsSoftmaxAnimation = value;} )\n\nlet softmaxDetailViewInfo = undefined;\nsoftmaxDetailViewStore.subscribe( value => {softmaxDetailViewInfo = value;} )\n\nlet hoverInfo = undefined;\nhoverInfoStore.subscribe( value => {hoverInfo = value;} )\n\nlet detailedMode = undefined;\ndetailedModeStore.subscribe( value => {detailedMode = value;} )\n\nlet layerIndexDict = {\n  'input': 0,\n  'conv_1_1': 1,\n  'relu_1_1': 2,\n  'conv_1_2': 3,\n  'relu_1_2': 4,\n  'max_pool_1': 5,\n  'conv_2_1': 6,\n  'relu_2_1': 7,\n  'conv_2_2': 8,\n  'relu_2_2': 9,\n  'max_pool_2': 10,\n  'output': 11\n}\n\nlet hasInitialized = false;\nlet logits = [];\nlet flattenFactoredFDict = {};\n\nconst moveLegend = (d, i, g, moveX, duration, restore) => {\n  let legend = d3.select(g[i]);\n\n  if (!restore) {\n    let previousTransform = legend.attr('transform');\n    let previousLegendX = +previousTransform.replace(/.*\\(([\\d\\.]+),.*/, '$1');\n    let previousLegendY = +previousTransform.replace(/.*,\\s([\\d\\.]+)\\)/, '$1');\n  \n    legend.transition('softmax')\n      .duration(duration)\n      .ease(d3.easeCubicInOut)\n      .attr('transform', `translate(${previousLegendX - moveX}, ${previousLegendY})`);\n    \n    // If not in restore mode, we register the previous location to the DOM element\n    legend.attr('data-preX', previousLegendX);\n    legend.attr('data-preY', previousLegendY);\n  } else {\n    // Restore the recorded location\n    let previousLegendX = +legend.attr('data-preX');\n    let previousLegendY = +legend.attr('data-preY');\n\n    legend.transition('softmax')\n      .duration(duration)\n      .ease(d3.easeCubicInOut)\n      .attr('transform', `translate(${previousLegendX}, ${previousLegendY})`);\n  }\n}\n\nconst logitCircleMouseOverHandler = (i) => {\n  // Update the hover info UI\n  hoverInfoStore.set({\n    show: true,\n    text: `Logit: ${formater(logits[i])}`\n  })\n\n  // Highlight the text in the detail view\n  softmaxDetailViewInfo.highlightI = i;\n  softmaxDetailViewStore.set(softmaxDetailViewInfo);\n\n  let logitLayer = svg.select('.logit-layer');\n  let logitLayerLower = svg.select('.underneath');\n  let intermediateLayer = svg.select('.intermediate-layer');\n\n  // Highlight the circle\n  logitLayer.select(`#logit-circle-${i}`)\n    .style('stroke-width', 2);\n\n  // Highlight the associated plus symbol\n  intermediateLayer.select(`#plus-symbol-clone-${i}`)\n    .style('opacity', 1)\n    .select('circle')\n    .style('fill', d => d.fill);\n  \n  // Raise the associated edge group\n  logitLayerLower.select(`#logit-lower-${i}`).raise();\n\n  // Highlight the associated edges\n  logitLayerLower.selectAll(`.softmax-abstract-edge-${i}`)\n    .style('stroke-width', 0.8)\n    .style('stroke', '#E0E0E0');\n\n  logitLayerLower.selectAll(`.softmax-edge-${i}`)\n    .style('stroke-width', 1)\n    .style('stroke', '#E0E0E0');\n  \n  logitLayerLower.selectAll(`.logit-output-edge-${i}`)\n    .style('stroke-width', 3)\n    .style('stroke', '#E0E0E0');\n\n  logitLayer.selectAll(`.logit-output-edge-${i}`)\n    .style('stroke-width', 3)\n    .style('stroke', '#E0E0E0');\n}\n\nconst logitCircleMouseLeaveHandler = (i) => {\n  // screenshot\n  // return;\n\n  // Update the hover info UI\n  hoverInfoStore.set({\n    show: false,\n    text: `Logit: ${formater(logits[i])}`\n  })\n\n  // Dehighlight the text in the detail view\n  softmaxDetailViewInfo.highlightI = -1;\n  softmaxDetailViewStore.set(softmaxDetailViewInfo);\n\n  let logitLayer = svg.select('.logit-layer');\n  let logitLayerLower = svg.select('.underneath');\n  let intermediateLayer = svg.select('.intermediate-layer');\n\n  // Restore the circle\n  logitLayer.select(`#logit-circle-${i}`)\n    .style('stroke-width', 1);\n\n  // Restore the associated plus symbol\n  intermediateLayer.select(`#plus-symbol-clone-${i}`)\n    .style('opacity', 0.2);\n\n  // Restore the associated edges\n  logitLayerLower.selectAll(`.softmax-abstract-edge-${i}`)\n    .style('stroke-width', 0.2)\n    .style('stroke', '#EDEDED');\n\n  logitLayerLower.selectAll(`.softmax-edge-${i}`)\n    .style('stroke-width', 0.2)\n    .style('stroke', '#F1F1F1');\n\n  logitLayerLower.selectAll(`.logit-output-edge-${i}`)\n    .style('stroke-width', 1.2)\n    .style('stroke', '#E5E5E5');\n  \n  logitLayer.selectAll(`.logit-output-edge-${i}`)\n    .style('stroke-width', 1.2)\n    .style('stroke', '#E5E5E5');\n}\n\n// This function is binded to the detail view in Overview.svelte\nexport const softmaxDetailViewMouseOverHandler = (event) => {\n  logitCircleMouseOverHandler(event.detail.curI);\n}\n\n// This function is binded to the detail view in Overview.svelte\nexport const softmaxDetailViewMouseLeaveHandler = (event) => {\n  logitCircleMouseLeaveHandler(event.detail.curI);\n}\n\nconst drawLogitLayer = (arg) => {\n  let curLayerIndex = arg.curLayerIndex,\n    moveX = arg.moveX,\n    softmaxLeftMid = arg.softmaxLeftMid,\n    selectedI = arg.selectedI,\n    intermediateX1 = arg.intermediateX1,\n    intermediateX2 = arg.intermediateX2,\n    pixelWidth = arg.pixelWidth,\n    pixelHeight = arg.pixelHeight,\n    topY = arg.topY,\n    bottomY = arg.bottomY,\n    softmaxX = arg.softmaxX,\n    middleGap = arg.middleGap,\n    middleRectHeight = arg.middleRectHeight,\n    symbolGroup = arg.symbolGroup,\n    symbolX = arg.symbolX,\n    flattenRange = arg.flattenRange;\n\n  let logitLayer = svg.select('.intermediate-layer')\n    .append('g')\n    .attr('class', 'logit-layer')\n    .raise();\n  \n  // Minotr layer ordering change\n  let tempClone = svg.select('.intermediate-layer')\n    .select('.flatten-layer')\n    .select('.plus-symbol')\n    .clone(true)\n    .attr('class', 'temp-clone-plus-symbol')\n    .attr('transform', `translate(${symbolX - moveX},\n      ${nodeCoordinate[curLayerIndex][selectedI].y + nodeLength / 2})`)\n    // Cool hack -> d3 clone doesnt clone events, make the front object pointer\n    // event transparent so users can trigger the underlying object's event!\n    .style('pointer-events', 'none')\n    .remove();\n\n  let tempPlusSymbol = logitLayer.append(() => tempClone.node());\n  \n  svg.select('.softmax-symbol').raise();\n\n  let logitLayerLower = svg.select('.underneath')\n    .append('g')\n    .attr('class', 'logit-layer-lower')\n    .lower();\n  \n  // Use circles to encode logit values\n  let centerX = softmaxLeftMid - moveX * 4 / 5;\n\n  // Get all logits\n  logits = [];\n  for (let i = 0; i < cnn[layerIndexDict['output']].length; i++) {\n    logits.push(cnn[layerIndexDict['output']][i].logit);\n  }\n\n  // Construct a color scale for the logit values\n  let logitColorScale = d3.scaleLinear()\n    .domain(d3.extent(logits))\n    .range([0.2, 1]);\n  \n  // Draw the current logit circle before animation\n  let logitRadius = 8;\n  logitLayer.append('circle')\n    .attr('class', 'logit-circle')\n    .attr('id', `logit-circle-${selectedI}`)\n    .attr('cx', centerX)\n    .attr('cy', nodeCoordinate[curLayerIndex - 1][selectedI].y + nodeLength / 2)\n    .attr('r', logitRadius)\n    .style('fill', layerColorScales.logit(logitColorScale(logits[selectedI])))\n    .style('cursor', 'crosshair')\n    .style('pointer-events', 'all')\n    .style('stroke', intermediateColor)\n    .on('mouseover', () => logitCircleMouseOverHandler(selectedI))\n    .on('mouseleave', () => logitCircleMouseLeaveHandler(selectedI))\n    .on('click', () => { d3.event.stopPropagation() });\n  \n  // Show the logit circle corresponding label\n  let softmaxDetailAnnotation = svg.select('.intermediate-layer-annotation')\n    .select('.softmax-detail-annoataion');\n\n  softmaxDetailAnnotation.select(`#logit-text-${selectedI}`)\n    .style('opacity', 1);\n\n  tempPlusSymbol.raise();\n\n  // Draw another line from plus symbol to softmax symbol\n  logitLayer.append('line')\n    .attr('class', `logit-output-edge-${selectedI}`)\n    .attr('x1', intermediateX2 - moveX + plusSymbolRadius * 2)\n    .attr('x2', softmaxX)\n    .attr('y1', nodeCoordinate[curLayerIndex - 1][selectedI].y + nodeLength / 2)\n    .attr('y2', nodeCoordinate[curLayerIndex - 1][selectedI].y + nodeLength / 2)\n    .style('fill', 'none')\n    .style('stroke', '#EAEAEA')\n    .style('stroke-width', '1.2')\n    .lower();\n\n  // Add the flatten to logit links\n  let linkData = [];\n  let flattenLength = cnn.flatten.length / cnn[1].length;\n  let underneathIs = [...Array(cnn[layerIndexDict['output']].length).keys()]\n    .filter(d => d != selectedI);\n  let curIIndex = 0;\n  let linkGen = d3.linkHorizontal()\n    .x(d => d.x)\n    .y(d => d.y);\n\n  const drawOneEdgeGroup = () => {\n    // Only draw the new group if it is in the softmax mode\n    if (!allowsSoftmaxAnimation) {\n      svg.select('.underneath')\n        .selectAll(`.logit-lower`)\n        .remove();\n      return;\n    }\n\n    let curI = underneathIs[curIIndex];\n\n    let curEdgeGroup = svg.select('.underneath')\n      .select(`#logit-lower-${curI}`);\n    \n    if (curEdgeGroup.empty()) {\n      curEdgeGroup = svg.select('.underneath')\n        .append('g')\n        .attr('class', 'logit-lower')\n        .attr('id', `logit-lower-${curI}`)\n        .style('opacity', 0);\n\n      // Hack: now show all edges, only draw 1/3 of the actual edges\n      for (let f = 0; f < flattenLength; f += 3) {\n        let loopFactors = [0, 9];\n        loopFactors.forEach(l => {\n          let factoredF = f + l * flattenLength;\n    \n          // Flatten -> output\n          linkData.push({\n            source: {x: intermediateX1 + pixelWidth + 3 - moveX,\n              y:  l === 0 ? topY + f * pixelHeight : bottomY + f * pixelHeight},\n            target: {x: intermediateX2 - moveX,\n              y: nodeCoordinate[curLayerIndex][curI].y + nodeLength / 2},\n            index: factoredF,\n            weight: cnn.flatten[factoredF].outputLinks[curI].weight,\n            color: '#F1F1F1',\n            width: 0.5,\n            opacity: 1,\n            class: `softmax-edge-${curI}`\n          });\n        });\n      }\n\n      // Draw middle rect to logits\n      for (let vi = 0; vi < cnn[layerIndexDict['output']].length - 2; vi++) {\n        linkData.push({\n          source: {x: intermediateX1 + pixelWidth + 3 - moveX,\n            y: topY + flattenLength * pixelHeight + middleGap * (vi + 1) +\n            middleRectHeight * (vi + 0.5)},\n          target: {x: intermediateX2 - moveX,\n            y: nodeCoordinate[curLayerIndex][curI].y + nodeLength / 2},\n          index: -1,\n          color: '#EDEDED',\n          width: 0.5,\n          opacity: 1,\n          class: `softmax-abstract-edge-${curI}`\n        });\n      }\n\n      // Render the edges on the underneath layer\n      curEdgeGroup.selectAll(`path.softmax-edge-${curI}`)\n        .data(linkData)\n        .enter()\n        .append('path')\n        .attr('class', d => d.class)\n        .attr('id', d => `edge-${d.name}`)\n        .attr('d', d => linkGen({source: d.source, target: d.target}))\n        .style('fill', 'none')\n        .style('stroke-width', d => d.width)\n        .style('stroke', d => d.color === undefined ? intermediateColor : d.color)\n        .style('opacity', d => d.opacity)\n        .style('pointer-events', 'none');\n    }\n    \n    let curNodeGroup = logitLayer.append('g')\n      .attr('class', `logit-layer-${curI}`)\n      .style('opacity', 0);\n    \n    // Draw the plus symbol\n    let symbolClone = symbolGroup.clone(true)\n      .style('opacity', 0);\n\n    // Change the style of the clone\n    symbolClone.attr('class', 'plus-symbol-clone')\n      .attr('id', `plus-symbol-clone-${curI}`)\n      .select('circle')\n      .datum({fill: gappedColorScale(layerColorScales.weight,\n        flattenRange, cnn[layerIndexDict['output']][curI].bias, 0.35)})\n      .style('pointer-events', 'none')\n      .style('fill', '#E5E5E5');\n\n    symbolClone.attr('transform', `translate(${symbolX},\n      ${nodeCoordinate[curLayerIndex][curI].y + nodeLength / 2})`);\n    \n    // Draw the outter link using only merged path\n    let outputEdgeD1 = linkGen({\n      source: {\n        x: intermediateX2 - moveX + plusSymbolRadius * 2,\n        y: nodeCoordinate[curLayerIndex][curI].y + nodeLength / 2\n      },\n      target: {\n        x: centerX + logitRadius,\n        y: nodeCoordinate[curLayerIndex][curI].y + nodeLength / 2\n      }\n    });\n\n    let outputEdgeD2 = linkGen({\n      source: {\n        x: centerX + logitRadius,\n        y: nodeCoordinate[curLayerIndex][curI].y + nodeLength / 2\n      },\n      target: {\n        x: softmaxX,\n        y: nodeCoordinate[curLayerIndex][selectedI].y + nodeLength / 2\n      }\n    });\n\n    // There are ways to combine these two paths into one. However, the animation\n    // for merged path is not continuous, so we use two saperate paths here.\n\n    let outputEdge1 = logitLayerLower.append('path')\n      .attr('class', `logit-output-edge-${curI}`)\n      .attr('d', outputEdgeD1)\n      .style('fill', 'none')\n      .style('stroke', '#EAEAEA')\n      .style('stroke-width', '1.2');\n\n    let outputEdge2 = logitLayerLower.append('path')\n      .attr('class', `logit-output-edge-${curI}`)\n      .attr('d', outputEdgeD2)\n      .style('fill', 'none')\n      .style('stroke', '#EAEAEA')\n      .style('stroke-width', '1.2');\n    \n    let outputEdgeLength1 = outputEdge1.node().getTotalLength();\n    let outputEdgeLength2 = outputEdge2.node().getTotalLength();\n    let totalLength = outputEdgeLength1 + outputEdgeLength2;\n    let totalDuration = hasInitialized ? 500 : 800;\n    let opacityDuration = hasInitialized ? 400 : 600;\n\n    outputEdge1.attr('stroke-dasharray', outputEdgeLength1 + ' ' + outputEdgeLength1)\n      .attr('stroke-dashoffset', outputEdgeLength1);\n    \n    outputEdge2.attr('stroke-dasharray', outputEdgeLength2 + ' ' + outputEdgeLength2)\n      .attr('stroke-dashoffset', outputEdgeLength2);\n\n    outputEdge1.transition('softmax-output-edge')\n      .duration(outputEdgeLength1 / totalLength * totalDuration)\n      .attr('stroke-dashoffset', 0);\n\n    outputEdge2.transition('softmax-output-edge')\n      .delay(outputEdgeLength1 / totalLength * totalDuration)\n      .duration(outputEdgeLength2 / totalLength * totalDuration)\n      .attr('stroke-dashoffset', 0);\n    \n    // Draw the logit circle\n    curNodeGroup.append('circle')\n      .attr('class', 'logit-circle')\n      .attr('id', `logit-circle-${curI}`)\n      .attr('cx', centerX)\n      .attr('cy', nodeCoordinate[curLayerIndex - 1][curI].y + nodeLength / 2)\n      .attr('r', 7)\n      .style('fill', layerColorScales.logit(logitColorScale(logits[curI])))\n      .style('stroke', intermediateColor)\n      .style('cursor', 'crosshair')\n      .on('mouseover', () => logitCircleMouseOverHandler(curI))\n      .on('mouseleave', () => logitCircleMouseLeaveHandler(curI))\n      .on('click', () => { d3.event.stopPropagation() });\n    \n    // Show the element in the detailed view\n    softmaxDetailViewInfo.startAnimation = {\n      i: curI,\n      duration: opacityDuration,\n      // Always show the animation\n      hasInitialized: false\n    };\n    softmaxDetailViewStore.set(softmaxDetailViewInfo);\n\n    // Show the elements with animation    \n    curNodeGroup.transition('softmax-edge')\n      .duration(opacityDuration)\n      .style('opacity', 1);\n\n    if ((selectedI < 3 && curI == 9) || (selectedI >= 3 && curI == 0)) {\n      // Show the hover text\n      softmaxDetailAnnotation.select('.softmax-detail-hover-annotation')\n        .transition('softmax-edge')\n        .duration(opacityDuration)\n        .style('opacity', 1);\n    }\n\n    softmaxDetailAnnotation.select(`#logit-text-${curI}`)\n      .transition('softmax-edge')\n      .duration(opacityDuration)\n      .style('opacity', 1);\n    \n    curEdgeGroup.transition('softmax-edge')\n      .duration(opacityDuration)\n      .style('opacity', 1)\n      .on('end', () => {\n        // Recursive animaiton\n        curIIndex ++;\n        if (curIIndex < underneathIs.length) {\n          linkData = [];\n          drawOneEdgeGroup();\n        } else {\n          hasInitialized = true;\n          softmaxDetailViewInfo.hasInitialized = true;\n          softmaxDetailViewStore.set(softmaxDetailViewInfo);\n        }\n      });\n    \n    symbolClone.transition('softmax-edge')\n      .duration(opacityDuration)\n      .style('opacity', 0.2);\n  }\n\n  // Show the softmax detail view\n  let anchorElement = svg.select('.intermediate-layer')\n    .select('.layer-label').node();\n  let pos = getMidCoords(svg, anchorElement);\n  let wholeSvg = d3.select('#cnn-svg');\n  let svgYMid = +wholeSvg.style('height').replace('px', '') / 2;\n  let detailViewTop = 100 + svgYMid - 192 / 2;\n\n  const detailview = document.getElementById('detailview');\n  detailview.style.top = `${detailViewTop}px`;\n  detailview.style.left = `${pos.left - 490 - 50}px`;\n  detailview.style.position = 'absolute';\n\n  softmaxDetailViewStore.set({\n    show: true,\n    logits: logits,\n    logitColors: logits.map(d => layerColorScales.logit(logitColorScale(d))),\n    selectedI: selectedI,\n    highlightI: -1,\n    outputName: classList[selectedI],\n    outputValue: cnn[layerIndexDict['output']][selectedI].output,\n    startAnimation: {i: -1, duration: 0, hasInitialized: hasInitialized}\n  })\n\n  drawOneEdgeGroup();\n\n  // Draw logit circle color scale\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: d3.extent(logits)[1] - d3.extent(logits)[0],\n    minMax: {min: d3.extent(logits)[0], max: d3.extent(logits)[1]},\n    group: logitLayer,\n    width: softmaxX - (intermediateX2 + plusSymbolRadius * 2 - moveX + 5),\n    gradientAppendingName: 'flatten-logit-gradient',\n    gradientGap: 0.1,\n    colorScale: layerColorScales.logit,\n    x: intermediateX2 + plusSymbolRadius * 2 - moveX + 5,\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10\n  });\n\n  // Draw logit layer label\n  let logitLabel = logitLayer.append('g')\n    .attr('class', 'layer-label')\n    .classed('hidden', detailedMode)\n    .attr('transform', () => {\n      let x = centerX;\n      let y = (svgPaddings.top + vSpaceAroundGap) / 2 + 5;\n      return `translate(${x}, ${y})`;\n    });\n\n  logitLabel.append('text')\n    .style('text-anchor', 'middle')\n    .style('dominant-baseline', 'middle')\n    .style('opacity', 0.8)\n    .style('font-weight', 800)\n    .text('logit');\n}\n\nconst removeLogitLayer = () => {\n  svg.select('.logit-layer').remove();\n  svg.select('.logit-layer-lower').remove();\n  svg.selectAll('.plus-symbol-clone').remove();\n\n  // Instead of removing the paths, we hide them, so it is faster to load in\n  // the future\n  svg.select('.underneath')\n    .selectAll('.logit-lower')\n    .style('opacity', 0);\n\n  softmaxDetailViewStore.set({\n      show: false,\n      logits: []\n  })\n}\n\nconst softmaxClicked = (arg) => {\n  let curLayerIndex = arg.curLayerIndex,\n    moveX = arg.moveX,\n    symbolX = arg.symbolX,\n    symbolY = arg.symbolY,\n    outputX = arg.outputX,\n    outputY = arg.outputY,\n    softmaxLeftMid = arg.softmaxLeftMid,\n    selectedI = arg.selectedI,\n    intermediateX1 = arg.intermediateX1,\n    intermediateX2 = arg.intermediateX2,\n    pixelWidth = arg.pixelWidth,\n    pixelHeight = arg.pixelHeight,\n    topY = arg.topY,\n    bottomY = arg.bottomY,\n    middleGap = arg.middleGap,\n    middleRectHeight = arg.middleRectHeight,\n    softmaxX = arg.softmaxX,\n    softmaxTextY = arg.softmaxTextY,\n    softmaxWidth = arg.softmaxWidth,\n    symbolGroup = arg.symbolGroup,\n    flattenRange = arg.flattenRange;\n\n  let duration = 600;\n  let centerX = softmaxLeftMid - moveX * 4 / 5;\n  d3.event.stopPropagation();\n\n  // Clean up the logit elemends before moving anything\n  if (isInSoftmax) {\n    allowsSoftmaxAnimationStore.set(false);\n    removeLogitLayer();\n  } else {\n    allowsSoftmaxAnimationStore.set(true);\n  }\n\n  // Move the overlay gradient\n  svg.select('.intermediate-layer-overlay')\n    .select('rect.overlay')\n    .transition('softmax')\n    .ease(d3.easeCubicInOut)\n    .duration(duration)\n    .attr('transform', `translate(${isInSoftmax ? 0 : -moveX}, ${0})`);\n\n  // Move the legends\n  svg.selectAll(`.intermediate-legend-${curLayerIndex - 1}`)\n    .each((d, i, g) => moveLegend(d, i, g, moveX, duration, isInSoftmax));\n\n  svg.select('.intermediate-layer')\n    .select(`.layer-label`)\n    .each((d, i, g) => moveLegend(d, i, g, moveX, duration, isInSoftmax));\n\n  svg.select('.intermediate-layer')\n    .select(`.layer-detailed-label`)\n    .each((d, i, g) => moveLegend(d, i, g, moveX, duration, isInSoftmax));\n\n  // Also move all layers on the left\n  for (let i = curLayerIndex - 1; i >= 0; i--) {\n    let curLayer = svg.select(`g#cnn-layer-group-${i}`);\n    let previousX = +curLayer.select('image').attr('x');\n    let newX = isInSoftmax ? previousX + moveX : previousX - moveX;\n    moveLayerX({\n      layerIndex: i,\n      targetX: newX,\n      disable: true,\n      delay: 0,\n      transitionName: 'softmax',\n      duration: duration\n    });\n  }\n\n  // Hide the sum up annotation\n  svg.select('.plus-annotation')\n    .transition('softmax')\n    .duration(duration)\n    .style('opacity', isInSoftmax ? 1 : 0)\n    .style('pointer-events', isInSoftmax ? 'all' : 'none');\n\n  // Hide the softmax annotation\n  let softmaxAnnotation = svg.select('.softmax-annotation')\n    .style('pointer-events', isInSoftmax ? 'all' : 'none');\n  \n  let softmaxDetailAnnotation = softmaxAnnotation.selectAll('.softmax-detail-annoataion')\n    .data([0])\n    .enter()\n    .append('g')\n    .attr('class', 'softmax-detail-annoataion');\n\n  // Remove the detailed annoatioan when quitting the detail view\n  if (isInSoftmax) {\n    softmaxAnnotation.selectAll('.softmax-detail-annoataion').remove();\n  }\n\n  softmaxAnnotation.select('.arrow-group')\n    .transition('softmax')\n    .duration(duration)\n    .style('opacity', isInSoftmax ? 1 : 0);\n\n  softmaxAnnotation.select('.annotation-text')\n    .style('cursor', 'help')\n    .style('pointer-events', 'all')\n    .on('click', () => {\n      d3.event.stopPropagation();\n      // Scroll to the article element\n      document.querySelector(`#article-softmax`).scrollIntoView({ \n        behavior: 'smooth' \n      });\n    })\n    .transition('softmax')\n    .duration(duration)\n    .style('opacity', isInSoftmax ? 1 : 0)\n    .on('end', () => {\n      if (!isInSoftmax) {\n        // Add new annotation for the softmax button\n        let textX = softmaxX + softmaxWidth / 2;\n        let textY = softmaxTextY - 10;\n\n        if (selectedI === 0) {\n          textY = softmaxTextY + 70;\n        }\n\n        let text = softmaxDetailAnnotation.append('text')\n          .attr('x', textX)\n          .attr('y', textY)\n          .attr('class', 'annotation-text softmax-detail-text')\n          .style('dominant-baseline', 'baseline')\n          .style('text-anchor', 'middle')\n          .text('Normalize ');\n        \n        text.append('tspan') \n          .attr('dx', 1)\n          .style('fill', '#E56014')\n          .text('logits');\n        \n        text.append('tspan')\n          .attr('dx', 1)\n          .text(' into');\n\n        text.append('tspan')\n          .attr('x', textX)\n          .attr('dy', '1.1em')\n          .text('class probabilities');\n\n        if (selectedI === 0) {\n          drawArrow({\n            group: softmaxDetailAnnotation,\n            sx: softmaxX + softmaxWidth / 2 - 5,\n            sy: softmaxTextY + 44,\n            tx: softmaxX + softmaxWidth / 2,\n            ty: textY - 12,\n            dr: 50,\n            hFlip: true,\n            marker: 'marker-alt'\n          });\n        } else {\n          drawArrow({\n            group: softmaxDetailAnnotation,\n            sx: softmaxX + softmaxWidth / 2 - 5,\n            sy: softmaxTextY + 4,\n            tx: softmaxX + softmaxWidth / 2,\n            ty: symbolY - plusSymbolRadius - 4,\n            dr: 50,\n            hFlip: true,\n            marker: 'marker-alt'\n          });\n        }\n\n        // Add annotation for the logit layer label\n        textX = centerX + 45;\n        textY = (svgPaddings.top + vSpaceAroundGap) / 2 + 5;\n        let arrowTX = centerX + 20;\n        let arrowTY = (svgPaddings.top + vSpaceAroundGap) / 2 + 5;\n\n        softmaxDetailAnnotation.append('g')\n          .attr('class', 'layer-detailed-label')\n          .attr('transform', () => {\n            let x = centerX;\n            let y = (svgPaddings.top + vSpaceAroundGap) / 2 - 5;\n            return `translate(${x}, ${y})`;\n          })\n          .classed('hidden', !detailedMode)\n          .append('text')\n          // .attr('x', centerX)\n          // .attr('y',  (svgPaddings.top + vSpaceAroundGap) / 2 - 6)\n          .style('opacity', 0.7)\n          .style('dominant-baseline', 'middle')\n          .style('font-size', '12px')\n          .style('font-weight', '800')\n          .append('tspan')\n          .attr('x', 0)\n          .text('logit')\n          .append('tspan')\n          .attr('x', 0)\n          .style('font-size', '8px')\n          .style('font-weight', 'normal')\n          .attr('dy', '1.5em')\n          .text('(10)');\n\n        softmaxDetailAnnotation.append('text')\n          .attr('class', 'annotation-text')\n          .attr('x', textX)\n          .attr('y', (svgPaddings.top + vSpaceAroundGap) / 2 + 3)\n          .style('text-anchor', 'start')\n          .text('Before')\n          .append('tspan')\n          .attr('x', textX)\n          .attr('dy', '1em')\n          .text('normalization')\n\n\n        drawArrow({\n          group: softmaxDetailAnnotation,\n          tx: arrowTX,\n          ty: arrowTY,\n          sx: textX - 6,\n          sy: textY + 2,\n          dr: 60,\n          hFlip: false,\n          marker: 'marker-alt'\n        });\n\n        softmaxDetailAnnotation.append('text')\n          .attr('class', 'annotation-text')\n          .attr('x', nodeCoordinate[layerIndexDict['output']][0].x - 35)\n          .attr('y', (svgPaddings.top + vSpaceAroundGap) / 2 + 3)\n          .style('text-anchor', 'end')\n          .text('After')\n          .append('tspan')\n          .attr('x', nodeCoordinate[layerIndexDict['output']][0].x - 35)\n          .attr('dy', '1em')\n          .text('normalization')\n\n        drawArrow({\n          group: softmaxDetailAnnotation,\n          tx: nodeCoordinate[layerIndexDict['output']][0].x - 8,\n          ty: arrowTY,\n          sx: nodeCoordinate[layerIndexDict['output']][0].x - 27,\n          sy: textY + 2,\n          dr: 60,\n          hFlip: true,\n          marker: 'marker-alt'\n        });\n\n        // Add annotation for the logit circle\n        for (let i = 0; i < 10; i++) {\n          softmaxDetailAnnotation.append('text')\n            .attr('x', centerX)\n            .attr('y', nodeCoordinate[curLayerIndex - 1][i].y + nodeLength / 2 + 8)\n            .attr('class', 'annotation-text softmax-detail-text')\n            .attr('id', `logit-text-${i}`)\n            .style('text-anchor', 'middle')\n            .style('dominant-baseline', 'hanging')\n            .style('opacity', 0)\n            .text(`${classList[i]}`);\n        }\n\n        let hoverTextGroup = softmaxDetailAnnotation.append('g')\n          .attr('class', 'softmax-detail-hover-annotation')\n          .style('opacity', 0);\n\n        textX = centerX + 50;\n        textY = nodeCoordinate[curLayerIndex - 1][0].y + nodeLength / 2;\n\n        if (selectedI < 3) {\n          textY = nodeCoordinate[curLayerIndex - 1][9].y + nodeLength / 2;\n        }\n\n        // Add annotation to prompt user to check the logit value\n        let hoverText = hoverTextGroup.append('text')\n          .attr('x', textX)\n          .attr('y', textY)\n          .attr('class', 'annotation-text softmax-detail-text softmax-hover-text')\n          .style('text-anchor', 'start')\n          .style('dominant-baseline', 'baseline')\n          .append('tspan')\n          .style('font-weight', 700)\n          .style('dominant-baseline', 'baseline')\n          .text(`Hover over `)\n          .append('tspan')\n          .style('font-weight', 400)\n          .style('dominant-baseline', 'baseline')\n          .text('to see');\n        \n        hoverText.append('tspan')\n          .style('dominant-baseline', 'baseline')\n          .attr('x', textX)\n          .attr('dy', '1em')\n          .text('its ');\n\n        hoverText.append('tspan')\n          .style('dominant-baseline', 'baseline')\n          .attr('dx', 1)\n          .style('fill', '#E56014')\n          .text('logit');\n        \n        hoverText.append('tspan')\n          .style('dominant-baseline', 'baseline')\n          .attr('dx', 1)\n          .text(' value');\n        \n        drawArrow({\n          group: hoverTextGroup,\n          tx: centerX + 15,\n          ty: textY,\n          sx: textX - 8,\n          sy: textY + 2,\n          dr: 60,\n          hFlip: false\n        });\n      }\n    })\n\n  // Hide the annotation\n  svg.select('.flatten-annotation')\n    .transition('softmax')\n    .duration(duration)\n    .style('opacity', isInSoftmax ? 1 : 0)\n    .style('pointer-events', isInSoftmax ? 'all' : 'none');\n\n  // Move the left part of faltten layer elements\n  let flattenLeftPart = svg.select('.flatten-layer-left');\n  flattenLeftPart.transition('softmax')\n    .duration(duration)\n    .ease(d3.easeCubicInOut)\n    .attr('transform', `translate(${isInSoftmax ? 0 : -moveX}, ${0})`)\n    .on('end', () => {\n      // Add the logit layer\n      if (!isInSoftmax) {\n        let logitArg = {\n          curLayerIndex: curLayerIndex,\n          moveX: moveX,\n          softmaxLeftMid: softmaxLeftMid,\n          selectedI: selectedI,\n          intermediateX1: intermediateX1,\n          intermediateX2: intermediateX2,\n          pixelWidth: pixelWidth,\n          pixelHeight: pixelHeight,\n          topY: topY,\n          bottomY: bottomY,\n          middleGap: middleGap,\n          middleRectHeight: middleRectHeight,\n          softmaxX: softmaxX,\n          symbolGroup: symbolGroup,\n          symbolX: symbolX,\n          flattenRange: flattenRange\n        };\n        drawLogitLayer(logitArg);\n      }\n\n      // Redraw the line from the plus symbol to the output node\n      if (!isInSoftmax) {\n        let newLine = flattenLeftPart.select('.edge-group')\n          .append('line')\n          .attr('class', 'symbol-output-line')\n          .attr('x1', symbolX)\n          .attr('y1', symbolY)\n          .attr('x2', outputX + moveX)\n          .attr('y2', outputY)\n          .style('stroke-width', 1.2)\n          .style('stroke', '#E5E5E5')\n          .style('opacity', 0);\n        \n        newLine.transition('softmax')\n          .delay(duration / 3)\n          .duration(duration * 2 / 3)\n          .style('opacity', 1);\n      } else {\n        flattenLeftPart.select('.symbol-output-line').remove();\n      }\n      \n      isInSoftmax = !isInSoftmax;\n      isInSoftmaxStore.set(isInSoftmax);\n    })\n}\n\n/**\n * Draw the flatten layer before output layer\n * @param {number} curLayerIndex Index of the selected layer\n * @param {object} d Bounded d3 data\n * @param {number} i Index of the selected node\n * @param {number} width CNN group width\n * @param {number} height CNN group height\n */\nexport const drawFlatten = (curLayerIndex, d, i, width, height) => {\n  // Show the output legend\n  svg.selectAll('.output-legend')\n    .classed('hidden', false);\n\n  let pixelWidth = nodeLength / 2;\n  let pixelHeight = 1.1;\n  let totalLength = (2 * nodeLength +\n    5.5 * hSpaceAroundGap * gapRatio + pixelWidth);\n  let leftX = nodeCoordinate[curLayerIndex][0].x - totalLength;\n  let intermediateGap = (hSpaceAroundGap * gapRatio * 4) / 2;\n  const minimumGap = 20;\n  let linkGen = d3.linkHorizontal()\n    .x(d => d.x)\n    .y(d => d.y);\n\n  // Hide the edges\n  svg.select('g.edge-group')\n    .style('visibility', 'hidden');\n\n  // Move the previous layer\n  moveLayerX({layerIndex: curLayerIndex - 1, targetX: leftX,\n    disable: true, delay: 0});\n\n  // Disable the current layer (output layer)\n  moveLayerX({layerIndex: curLayerIndex,\n    targetX: nodeCoordinate[curLayerIndex][0].x, disable: true,\n    delay: 0, opacity: 0.15, specialIndex: i});\n  \n  // Compute the gap in the left shrink region\n  let leftEnd = leftX - hSpaceAroundGap;\n  let leftGap = (leftEnd - nodeCoordinate[0][0].x - 10 * nodeLength) / 10;\n\n  // Different from other intermediate view, we push the left part dynamically\n  // 1. If there is enough space, we fix the first layer position and move all\n  // other layers;\n  // 2. If there is not enough space, we maintain the minimum gap and push all\n  // left layers to the left (could be out-of-screen)\n  if (leftGap > minimumGap) {\n    // Move the left layers\n    for (let i = 0; i < curLayerIndex - 1; i++) {\n      let curX = nodeCoordinate[0][0].x + i * (nodeLength + leftGap);\n      moveLayerX({layerIndex: i, targetX: curX, disable: true, delay: 0});\n    }\n  } else {\n    leftGap = minimumGap;\n    let curLeftBound = leftX - leftGap * 2 - nodeLength;\n    // Move the left layers\n    for (let i = curLayerIndex - 2; i >= 0; i--) {\n      moveLayerX({layerIndex: i, targetX: curLeftBound, disable: true, delay: 0});\n      curLeftBound = curLeftBound - leftGap - nodeLength;\n    }\n  }\n\n  // Add an overlay\n  let stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: 1},\n    {offset: '50%', color: 'rgb(250, 250, 250)', opacity: 0.95},\n    {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 0.85}];\n  addOverlayGradient('overlay-gradient-left', stops);\n\n  let intermediateLayerOverlay = svg.append('g')\n    .attr('class', 'intermediate-layer-overlay');\n\n  intermediateLayerOverlay.append('rect')\n    .attr('class', 'overlay')\n    .style('fill', 'url(#overlay-gradient-left)')\n    .style('stroke', 'none')\n    .attr('width', leftX + svgPaddings.left - (leftGap * 2) + 3)\n    .attr('height', height + svgPaddings.top + svgPaddings.bottom)\n    .attr('x', -svgPaddings.left)\n    .attr('y', 0)\n    .style('opacity', 0);\n  \n  intermediateLayerOverlay.selectAll('rect.overlay')\n    .transition('move')\n    .duration(800)\n    .ease(d3.easeCubicInOut)\n    .style('opacity', 1);\n\n  // Add the intermediate layer\n  let intermediateLayer = svg.append('g')\n    .attr('class', 'intermediate-layer')\n    .style('opacity', 0);\n  \n  let intermediateX1 = leftX + nodeLength + intermediateGap;\n  let intermediateX2 = intermediateX1 + intermediateGap + pixelWidth;\n  let range = cnnLayerRanges[selectedScaleLevel][curLayerIndex - 1];\n  let colorScale = layerColorScales.conv;\n  let flattenLength = cnn.flatten.length / cnn[1].length;\n  let linkData = [];\n\n  let flattenLayer = intermediateLayer.append('g')\n    .attr('class', 'flatten-layer');\n  \n  let flattenLayerLeftPart = flattenLayer.append('g')\n    .attr('class', 'flatten-layer-left');\n  \n  let topY = nodeCoordinate[curLayerIndex - 1][0].y;\n  let bottomY = nodeCoordinate[curLayerIndex - 1][9].y + nodeLength -\n        flattenLength * pixelHeight;\n  \n  // Compute the pre-layer gap\n  let preLayerDimension = cnn[curLayerIndex - 1][0].output.length;\n  let preLayerGap = nodeLength / (2 * preLayerDimension);\n\n  // Compute bounding box length\n  let boundingBoxLength = nodeLength / preLayerDimension;\n\n  // Compute the weight color scale\n  let flattenExtent = d3.extent(cnn.flatten.slice(flattenLength)\n    .map(d => d.outputLinks[i].weight)\n    .concat(cnn.flatten.slice(9 * flattenLength, 10 * flattenLength)\n      .map(d => d.outputLinks[i].weight)));\n\n  let flattenRange = 2 * (Math.round(\n    Math.max(...flattenExtent.map(Math.abs)) * 1000) / 1000);\n\n  let flattenMouseOverHandler = (d) => {\n    let index = d.index;\n    // Screenshot\n    // console.log(index);\n\n    // Update the hover info UI\n    if (d.weight === undefined) {\n      hoverInfo = {\n        show: true,\n        text: `Pixel value: ${formater(flattenFactoredFDict[index])}`\n      };\n    } else {\n      hoverInfo = {\n        show: true,\n        text: `Weight: ${formater(d.weight)}`\n      };\n    }\n    hoverInfoStore.set(hoverInfo);\n\n    flattenLayerLeftPart.select(`#edge-flatten-${index}`)\n      .raise()\n      .style('stroke', intermediateColor)\n      .style('stroke-width', 1);\n\n    flattenLayerLeftPart.select(`#edge-flatten-${index}-output`)\n      .raise()\n      .style('stroke-width', 1)\n      .style('stroke', da => gappedColorScale(layerColorScales.weight,\n        flattenRange, da.weight, 0.1));\n\n    flattenLayerLeftPart.select(`#bounding-${index}`)\n      .raise()\n      .style('opacity', 1);\n  }\n\n  let flattenMouseLeaveHandler = (d) => {\n    let index = d.index;\n\n    // screenshot\n    // if (index === 32) {return;}\n\n    // Update the hover info UI\n    if (d.weight === undefined) {\n      hoverInfo = {\n        show: false,\n        text: `Pixel value: ${formater(flattenFactoredFDict[index])}`\n      };\n    } else {\n      hoverInfo = {\n        show: false,\n        text: `Weight: ${formater(d.weight)}`\n      };\n    }\n    hoverInfoStore.set(hoverInfo);\n\n    flattenLayerLeftPart.select(`#edge-flatten-${index}`)\n      .style('stroke-width', 0.6)\n      .style('stroke', '#E5E5E5')\n\n    flattenLayerLeftPart.select(`#edge-flatten-${index}-output`)\n      .style('stroke-width', 0.6)\n      .style('stroke', da => gappedColorScale(layerColorScales.weight,\n        flattenRange, da.weight, 0.35));\n\n    flattenLayerLeftPart.select(`#bounding-${index}`)\n      .raise()\n      .style('opacity', 0);\n  }\n\n  flattenFactoredFDict = {};\n  for (let f = 0; f < flattenLength; f++) {\n    let loopFactors = [0, 9];\n    loopFactors.forEach(l => {\n      let factoredF = f + l * flattenLength;\n      flattenFactoredFDict[factoredF] = cnn.flatten[factoredF].output;\n      flattenLayerLeftPart.append('rect')\n        .attr('x', intermediateX1)\n        .attr('y', l === 0 ? topY + f * pixelHeight : bottomY + f * pixelHeight)\n        .attr('width', pixelWidth)\n        .attr('height', pixelHeight)\n        .style('cursor', 'crosshair')\n        .style('fill', colorScale((cnn.flatten[factoredF].output + range / 2) / range))\n        .on('mouseover', () => flattenMouseOverHandler({index: factoredF}))\n        .on('mouseleave', () => flattenMouseLeaveHandler({index: factoredF}))\n        .on('click', () => { d3.event.stopPropagation() });\n\n      // Flatten -> output\n      linkData.push({\n        source: {x: intermediateX1 + pixelWidth + 3,\n          y:  l === 0 ? topY + f * pixelHeight : bottomY + f * pixelHeight},\n        target: {x: intermediateX2,\n          //nodeCoordinate[curLayerIndex][i].x - nodeLength,\n          y: nodeCoordinate[curLayerIndex][i].y + nodeLength / 2},\n        index: factoredF,\n        weight: cnn.flatten[factoredF].outputLinks[i].weight,\n        name: `flatten-${factoredF}-output`,\n        color: gappedColorScale(layerColorScales.weight,\n          flattenRange, cnn.flatten[factoredF].outputLinks[i].weight, 0.35),\n        width: 0.6,\n        opacity: 1,\n        class: `flatten-output`\n      });\n\n      // Pre-layer -> flatten\n      let row = Math.floor(f / preLayerDimension);\n      linkData.push({\n        target: {x: intermediateX1 - 3,\n          y:  l === 0 ? topY + f * pixelHeight : bottomY + f * pixelHeight},\n        source: {x: leftX + nodeLength + 3,\n          y: nodeCoordinate[curLayerIndex - 1][l].y + (2 * row + 1) * preLayerGap},\n        index: factoredF,\n        name: `flatten-${factoredF}`,\n        color: '#E5E5E5',\n        // color: gappedColorScale(layerColorScales.conv,\n        //   2 * Math.max(Math.abs(cnnLayerMinMax[10].max), Math.abs(cnnLayerMinMax[10].min)),\n        //   cnn.flatten[factoredF].output, 0.2),\n        width: 0.6,\n        opacity: 1,\n        class: `flatten`\n      });\n\n      // Add original pixel bounding box\n      let loc = cnn.flatten[factoredF].inputLinks[0].weight;\n      flattenLayerLeftPart.append('rect')\n        .attr('id', `bounding-${factoredF}`)\n        .attr('class', 'flatten-bounding')\n        .attr('x', leftX + loc[1] * boundingBoxLength)\n        .attr('y', nodeCoordinate[curLayerIndex - 1][l].y + loc[0] * boundingBoxLength)\n        .attr('width', boundingBoxLength)\n        .attr('height', boundingBoxLength)\n        .style('fill', 'none')\n        .style('stroke', intermediateColor)\n        .style('stroke-length', '0.5')\n        .style('pointer-events', 'all')\n        .style('cursor', 'crosshair')\n        .style('opacity', 0)\n        .on('mouseover', () => flattenMouseOverHandler({index: factoredF}))\n        .on('mouseleave', () => flattenMouseLeaveHandler({index: factoredF}))\n        .on('click', () => {d3.event.stopPropagation()});\n    }) \n  }\n  \n  // Use abstract symbol to represent the flatten nodes in between (between\n  // the first and the last nodes)\n  // Compute the average value of input node and weights\n  let meanValues = [];\n  for (let n = 1; n < cnn[curLayerIndex - 1].length - 1; n++) {\n    /*\n    let meanOutput = d3.mean(cnn.flatten.slice(flattenLength * n,\n      flattenLength * (n + 1)).map(d => d.output));\n    let meanWeight= d3.mean(cnn.flatten.slice(flattenLength * n,\n      flattenLength * (n + 1)).map(d => d.outputLinks[i].weight));\n    meanValues.push({index: n, output: meanOutput, weight: meanWeight});\n    */\n    meanValues.push({index: n});\n  }\n\n  // Compute the middle gap\n  let middleGap = 5;\n  let middleRectHeight = (10 * nodeLength + (10 - 1) * vSpaceAroundGap -\n    pixelHeight * flattenLength * 2 - 5 * (8 + 1)) / 8;\n\n  // Add middle nodes\n  meanValues.forEach((v, vi) => {\n    // Add a small rectangle\n    flattenLayerLeftPart.append('rect')\n      .attr('x', intermediateX1 + pixelWidth / 4)\n      .attr('y', topY + flattenLength * pixelHeight + middleGap * (vi + 1) +\n        middleRectHeight * vi)\n      .attr('width', pixelWidth / 2)\n      .attr('height', middleRectHeight)\n      // .style('fill', colorScale((v.output + range / 2) / range));\n      .style('fill', '#E5E5E5');\n    \n    // Add a triangle next to the input node\n    flattenLayerLeftPart.append('polyline')\n      .attr('points',\n        `${leftX + nodeLength + 3}\n        ${nodeCoordinate[curLayerIndex - 1][v.index].y},\n        ${leftX + nodeLength + 10}\n        ${nodeCoordinate[curLayerIndex - 1][v.index].y + nodeLength / 2},\n        ${leftX + nodeLength + 3}\n        ${nodeCoordinate[curLayerIndex - 1][v.index].y + nodeLength}`)\n      .style('fill', '#E5E5E5')\n      .style('opacity', 1);\n    \n    // Input -> flatten\n    linkData.push({\n      source: {x: leftX + nodeLength + 10,\n        y: nodeCoordinate[curLayerIndex - 1][v.index].y + nodeLength / 2},\n      target: {x: intermediateX1 - 3,\n        y: topY + flattenLength * pixelHeight + middleGap * (vi + 1) +\n          middleRectHeight * (vi + 0.5)},\n      index: -1,\n      width: 1,\n      opacity: 1,\n      name: `flatten-abstract-${v.index}`,\n      color: '#E5E5E5',\n      class: `flatten-abstract`\n    });\n\n    // Flatten -> output\n    linkData.push({\n      source: {x: intermediateX1 + pixelWidth + 3,\n      y: topY + flattenLength * pixelHeight + middleGap * (vi + 1) +\n        middleRectHeight * (vi + 0.5)},\n      target: {x: intermediateX2,\n      y: nodeCoordinate[curLayerIndex][i].y + nodeLength / 2},\n      index: -1,\n      name: `flatten-abstract-${v.index}-output`,\n      // color: gappedColorScale(layerColorScales.weight, flattenRange,\n      //   v.weight, 0.35),\n      color: '#E5E5E5',\n      weight: v.weight,\n      width: 1,\n      opacity: 1,\n      class: `flatten-abstract-output`\n    });\n  })\n\n  // Draw the plus operation symbol\n  let symbolX = intermediateX2 + plusSymbolRadius;\n  let symbolY = nodeCoordinate[curLayerIndex][i].y + nodeLength / 2;\n  let symbolRectHeight = 1;\n  let symbolGroup = flattenLayerLeftPart.append('g')\n    .attr('class', 'plus-symbol')\n    .attr('transform', `translate(${symbolX}, ${symbolY})`);\n  \n  symbolGroup.append('rect')\n    .attr('x', -plusSymbolRadius)\n    .attr('y', -plusSymbolRadius)\n    .attr('width', plusSymbolRadius * 2)\n    .attr('height', plusSymbolRadius * 2)\n    .attr('rx', 3)\n    .attr('ry', 3)\n    .style('fill', 'none')\n    .style('stroke', intermediateColor);\n  \n  symbolGroup.append('rect')\n    .attr('x', -(plusSymbolRadius - 3))\n    .attr('y', -symbolRectHeight / 2)\n    .attr('width', 2 * (plusSymbolRadius - 3))\n    .attr('height', symbolRectHeight)\n    .style('fill', intermediateColor);\n\n  symbolGroup.append('rect')\n    .attr('x', -symbolRectHeight / 2)\n    .attr('y', -(plusSymbolRadius - 3))\n    .attr('width', symbolRectHeight)\n    .attr('height', 2 * (plusSymbolRadius - 3))\n    .style('fill', intermediateColor);\n\n  // Place the bias rectangle below the plus sign if user clicks the first\n  // conv node (no need now, since we added annotaiton for softmax to make it\n  // look better aligned)\n  // Add bias symbol to the plus symbol\n  symbolGroup.append('circle')\n    .attr('cx', 0)\n    .attr('cy', -nodeLength / 2 - 0.5 * kernelRectLength)\n    .attr('r', kernelRectLength * 1.5)\n    .style('stroke', intermediateColor)\n    .style('cursor', 'crosshair')\n    .style('fill', gappedColorScale(layerColorScales.weight,\n        flattenRange, d.bias, 0.35))\n    .on('mouseover', () => {\n      hoverInfoStore.set( {show: true, text: `Bias: ${formater(d.bias)}`} );\n    })\n    .on('mouseleave', () => {\n      hoverInfoStore.set( {show: false, text: `Bias: ${formater(d.bias)}`} );\n    })\n    .on('click', () => { d3.event.stopPropagation(); });\n  \n  // Link from bias to the plus symbol\n  symbolGroup.append('path')\n    .attr('d', linkGen({\n      source: { x: 0, y: 0 },\n      target: { x: 0, y: -nodeLength / 2 - 0.5 * kernelRectLength }\n    }))\n    .attr('id', 'bias-plus')\n    .attr('stroke-width', 1.2)\n    .attr('stroke', '#E5E5E5')\n    .lower();\n\n  // Link from the plus symbol to the output\n  linkData.push({\n    source: getOutputKnot({x: intermediateX2 + 2 * plusSymbolRadius - nodeLength,\n      y: nodeCoordinate[curLayerIndex][i].y}),\n    target: getInputKnot({x: nodeCoordinate[curLayerIndex][i].x - 3,\n      y: nodeCoordinate[curLayerIndex][i].y}),\n    name: `symbol-output`,\n    width: 1.2,\n    color: '#E5E5E5'\n  });\n\n  // Draw softmax operation symbol\n  let softmaxWidth = 55;\n  let emptySpace = ((totalLength - 2 * nodeLength - 2 * intermediateGap)\n    - softmaxWidth) / 2;\n  let symbolEndX = intermediateX2 + plusSymbolRadius * 2;\n  let softmaxX = emptySpace + symbolEndX;\n  let softmaxLeftMid = emptySpace / 2 + symbolEndX;\n  let softmaxTextY = nodeCoordinate[curLayerIndex][i].y - 2 * kernelRectLength - 6;\n  let moveX = (intermediateX2 - (intermediateX1 + pixelWidth + 3)) * 2 / 3;\n\n  let softmaxArg = {\n    curLayerIndex: curLayerIndex,\n    moveX: moveX,\n    symbolX: symbolX,\n    symbolY: symbolY,\n    outputX: nodeCoordinate[curLayerIndex][i].x,\n    outputY: symbolY,\n    softmaxLeftMid: softmaxLeftMid,\n    selectedI: i,\n    intermediateX1: intermediateX1,\n    intermediateX2: intermediateX2,\n    pixelWidth: pixelWidth,\n    pixelHeight: pixelHeight,\n    topY: topY,\n    bottomY: bottomY,\n    middleGap: middleGap,\n    middleRectHeight: middleRectHeight,\n    softmaxX: softmaxX,\n    softmaxWidth: softmaxWidth,\n    softmaxTextY: softmaxTextY,\n    symbolGroup: symbolGroup,\n    flattenRange: flattenRange\n  };\n\n  let softmaxSymbol = intermediateLayer.append('g')\n    .attr('class', 'softmax-symbol')\n    .attr('transform', `translate(${softmaxX}, ${symbolY})`)\n    .style('pointer-event', 'all')\n    .style('cursor', 'pointer')\n    .on('click', () => softmaxClicked(softmaxArg));\n  \n  softmaxSymbol.append('rect')\n    .attr('x', 0)\n    .attr('y', -plusSymbolRadius)\n    .attr('width', softmaxWidth)\n    .attr('height', plusSymbolRadius * 2)\n    .attr('stroke', intermediateColor)\n    .attr('rx', 2)\n    .attr('ry', 2)\n    .attr('fill', '#FAFAFA');\n  \n  softmaxSymbol.append('text')\n    .attr('x', 5)\n    .attr('y', 1)\n    .style('dominant-baseline', 'middle')\n    .style('font-size', '12px')\n    .style('opacity', 0.5)\n    .text('softmax');\n\n  // Draw the layer label\n  let layerLabel = intermediateLayer.append('g')\n    .attr('class', 'layer-label')\n    .classed('hidden', detailedMode)\n    .attr('transform', () => {\n      let x = leftX + nodeLength + (4 * hSpaceAroundGap * gapRatio +\n        pixelWidth) / 2;\n      let y = (svgPaddings.top + vSpaceAroundGap) / 2 + 5;\n      return `translate(${x}, ${y})`;\n    })\n    .style('cursor', 'help')\n    .on('click', () => {\n      d3.event.stopPropagation();\n      // Scroll to the article element\n      document.querySelector(`#article-flatten`).scrollIntoView({ \n        behavior: 'smooth' \n      });\n    });\n  \n  layerLabel.append('text')\n    .style('dominant-baseline', 'middle')\n    .style('opacity', 0.8)\n    .style('font-weight', 800)\n    .text('flatten');\n\n  let svgHeight = Number(d3.select('#cnn-svg').style('height').replace('px', '')) + 150;\n  let scroll = new SmoothScroll('a[href*=\"#\"]', {offset: -svgHeight});\n    \n  let detailedLabelGroup = intermediateLayer.append('g')\n    .attr('transform', () => {\n      let x = leftX + nodeLength + (4 * hSpaceAroundGap * gapRatio + pixelWidth) / 2;\n      let y = (svgPaddings.top + vSpaceAroundGap) / 2 - 5;\n      return `translate(${x}, ${y})`;\n    })\n    .attr('class', 'layer-detailed-label')\n    .classed('hidden', !detailedMode)\n    .style('cursor', 'help')\n    .on('click', () => {\n      d3.event.stopPropagation();\n      // Scroll to the article element\n      let anchor = document.querySelector(`#article-flatten`);\n      scroll.animateScroll(anchor);\n    });\n  \n  detailedLabelGroup.append('title')\n    .text('Move to article section');\n\n  let detailedLabelText = detailedLabelGroup.append('text')\n    .style('text-anchor', 'middle')\n    .style('dominant-baseline', 'middle')\n    .style('opacity', '0.7')\n    .style('font-weight', 800)\n    .append('tspan')\n    .text('flatten');\n  \n  let dimension = cnn[layerIndexDict['max_pool_2']].length * \n    cnn[layerIndexDict['max_pool_2']][0].output.length *\n    cnn[layerIndexDict['max_pool_2']][0].output[0].length;\n\n  detailedLabelText.append('tspan')\n    .attr('x', 0)\n    .attr('dy', '1.5em')\n    .style('font-size', '8px')\n    .style('font-weight', 'normal')\n    .text(`(${dimension})`);\n\n  // Add edges between nodes\n  let edgeGroup = flattenLayerLeftPart.append('g')\n    .attr('class', 'edge-group')\n    .lower();\n  \n  edgeGroup.selectAll('path')\n    .data(linkData)\n    .enter()\n    .append('path')\n    .attr('class', d => d.class)\n    .attr('id', d => `edge-${d.name}`)\n    .attr('d', d => linkGen({source: d.source, target: d.target}))\n    .style('fill', 'none')\n    .style('stroke-width', d => d.width)\n    .style('stroke', d => d.color === undefined ? intermediateColor : d.color)\n    .style('opacity', d => d.opacity);\n  \n  edgeGroup.selectAll('path.flatten-abstract-output')\n    .lower();\n\n  edgeGroup.selectAll('path.flatten,path.flatten-output')\n    .style('cursor', 'crosshair')\n    .style('pointer-events', 'all')\n    .on('mouseover', flattenMouseOverHandler)\n    .on('mouseleave', flattenMouseLeaveHandler)\n    .on('click', () => { d3.event.stopPropagation() });\n  \n  // Add legend\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: range,\n    minMax: cnnLayerMinMax[10],\n    group: intermediateLayer,\n    width: intermediateGap + nodeLength - 3,\n    x: leftX,\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10\n  });\n\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: flattenRange,\n    minMax: {min: flattenExtent[0], max: flattenExtent[1]},\n    group: intermediateLayer,\n    width: intermediateGap - 3 - 5,\n    gradientAppendingName: 'flatten-weight-gradient',\n    gradientGap: 0.1,\n    colorScale: layerColorScales.weight,\n    x: leftX + intermediateGap + nodeLength + pixelWidth + 3,\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10\n  });\n\n  // Add annotation to the intermediate layer\n  let intermediateLayerAnnotation = svg.append('g')\n    .attr('class', 'intermediate-layer-annotation')\n    .style('opacity', 0);\n\n  // Add annotation for the sum operation\n  let plusAnnotation = intermediateLayerAnnotation.append('g')\n    .attr('class', 'plus-annotation');\n  \n  // let textX = nodeCoordinate[curLayerIndex][i].x - 50;\n  let textX = intermediateX2;\n  let textY = nodeCoordinate[curLayerIndex][i].y + nodeLength +\n    kernelRectLength * 3;\n  let arrowSY = nodeCoordinate[curLayerIndex][i].y + nodeLength +\n    kernelRectLength * 2;\n  let arrowTY = nodeCoordinate[curLayerIndex][i].y + nodeLength / 2 +\n    plusSymbolRadius;\n\n  if (i == 9) {\n    textY -= 110;\n    arrowSY -= 70;\n    arrowTY -= 18;\n  }\n\n  let plusText = plusAnnotation.append('text')\n    .attr('x', textX)\n    .attr('y', textY)\n    .attr('class', 'annotation-text')\n    .style('dominant-baseline', 'hanging')\n    .style('text-anchor', 'middle');\n  \n  plusText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .text('Add up all products');\n  \n  plusText.append('tspan')\n    .attr('x', textX)\n    .attr('dy', '1em')\n    .style('dominant-baseline', 'hanging')\n    .text('(');\n\n  plusText.append('tspan')\n    .style('fill', '#66a3c8')\n    .style('dominant-baseline', 'hanging')\n    .text('element');\n\n  plusText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .text(' × ');\n\n  plusText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .style('fill', '#b58946')\n    .text('weight');\n\n  plusText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .text(')');\n\n  plusText.append('tspan')\n    .attr('x', textX)\n    .attr('dy', '1em')\n    .style('dominant-baseline', 'hanging')\n    .text('and then ');\n\n  plusText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .style('fill', '#479d94')\n    .text('bias');\n  \n  drawArrow({\n    group: plusAnnotation,\n    sx: intermediateX2 - 2 * plusSymbolRadius - 3,\n    sy: arrowSY,\n    tx: intermediateX2 - 5,\n    ty: arrowTY,\n    dr: 30,\n    hFlip: i === 9,\n    marker: 'marker-alt'\n  });\n\n  // Add annotation for the bias\n  let biasTextY = nodeCoordinate[curLayerIndex][i].y;\n  biasTextY -= 2 * kernelRectLength + 4;\n  \n  flattenLayerLeftPart.append('text')\n    .attr('class', 'annotation-text')\n    .attr('x', intermediateX2 + plusSymbolRadius)\n    .attr('y', biasTextY)\n    .style('text-anchor', 'middle')\n    .style('dominant-baseline', 'baseline')\n    .text('Bias');\n  \n  // Add annotation for the softmax symbol\n  let softmaxAnnotation = intermediateLayerAnnotation.append('g')\n    .attr('class', 'softmax-annotation');\n  \n  softmaxAnnotation.append('text')\n    .attr('x', softmaxX + softmaxWidth / 2)\n    .attr('y', softmaxTextY)\n    .attr('class', 'annotation-text')\n    .style('dominant-baseline', 'baseline')\n    .style('text-anchor', 'middle')\n    .style('font-weight', 700)\n    .text('Click ')\n    .append('tspan')\n    .attr('dx', 1)\n    .style('font-weight', 400)\n    .text('to learn more');\n\n  drawArrow({\n    group: softmaxAnnotation,\n    sx: softmaxX + softmaxWidth / 2 - 5,\n    sy: softmaxTextY + 4,\n    tx: softmaxX + softmaxWidth / 2,\n    ty: symbolY - plusSymbolRadius - 4,\n    dr: 50,\n    hFlip: true\n  });\n\n  // Add annotation for the flatten layer\n  let flattenAnnotation = intermediateLayerAnnotation.append('g')\n    .attr('class', 'flatten-annotation');\n  \n  textX = leftX - 80;\n  textY = nodeCoordinate[curLayerIndex - 1][0].y;\n\n  let flattenText = flattenAnnotation.append('text')\n    .attr('x', textX)\n    .attr('y', textY)\n    .attr('class', 'annotation-text')\n    .style('dominant-baseline', 'hanging')\n    .style('text-anchor', 'middle');\n\n  let tempTspan = flattenText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .style('font-weight', 700)\n    .text('Hover over ');\n  \n  tempTspan.append('tspan')\n    .attr('dx', 1)\n    .style('font-weight', 400)\n    .style('dominant-baseline', 'hanging')\n    .text('matrix to');\n  \n  flattenText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .attr('x', textX)\n    .attr('dy', '1em')\n    .text('see how it is flattened');\n  \n  flattenText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .attr('x', textX)\n    .attr('dy', '1em')\n    .text('into a 1D array!');\n\n  drawArrow({\n    group: flattenAnnotation,\n    sx: textX + 45,\n    sy: textY + nodeLength * 0.4 + 12,\n    tx: leftX - 10,\n    ty: textY + nodeLength / 2,\n    dr: 80,\n    hFlip: true\n  });\n\n  // Add annotation to explain the middle images\n  textY = nodeCoordinate[curLayerIndex - 1][1].y;\n\n  let middleText = flattenAnnotation.append('text')\n    .attr('x', textX)\n    .attr('y', textY)\n    .attr('class', 'annotation-text')\n    .style('dominant-baseline', 'hanging')\n    .style('text-anchor', 'middle');\n\n  middleText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .text('Same flattening');\n  \n  middleText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .attr('x', textX)\n    .attr('dy', '1em')\n    .text('operation for');\n\n  middleText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .attr('x', textX)\n    .attr('dy', '1em')\n    .text('each neuron');\n\n  drawArrow({\n    group: flattenAnnotation,\n    sx: textX + 39,\n    sy: textY + 25,\n    tx: leftX - 10,\n    ty: textY + nodeLength / 2 - 2,\n    dr: 80,\n    hFlip: true,\n    marker: 'marker-alt'\n  });\n\n\n  // Add annotation for the output neuron\n  let outputAnnotation = intermediateLayerAnnotation.append('g')\n    .attr('class', 'output-annotation');\n  \n  outputAnnotation.append('text')\n    .attr('x', nodeCoordinate[layerIndexDict['output']][i].x)\n    .attr('y', nodeCoordinate[layerIndexDict['output']][i].y + 10)\n    .attr('class', 'annotation-text')\n    .text(`(${d3.format('.4f')(cnn[layerIndexDict['output']][i].output)})`);\n\n\n  /* Prototype of using arc to represent the flatten layer (future)\n  let pie = d3.pie()\n    .padAngle(0)\n    .sort(null)\n    .value(d => d.output)\n    .startAngle(0)\n    .endAngle(-Math.PI);\n\n  let radius = 490 / 2;\n  let arc = d3.arc()\n    .innerRadius(radius - 20)\n    .outerRadius(radius);\n\n  let arcs = pie(cnn.flatten);\n  console.log(arcs);\n\n  let test = svg.append('g')\n    .attr('class', 'test')\n    .attr('transform', 'translate(500, 250)');\n\n  test.selectAll(\"path\")\n    .data(arcs)\n    .join(\"path\")\n      .attr('class', 'arc')\n      .attr(\"fill\", d => colorScale((d.value + range/2) / range))\n      .attr(\"d\", arc);\n  */\n\n  // Show everything\n  svg.selectAll('g.intermediate-layer, g.intermediate-layer-annotation')\n    .transition()\n    .delay(500)\n    .duration(500)\n    .ease(d3.easeCubicInOut)\n    .style('opacity', 1);\n}"
  },
  {
    "path": "src/overview/intermediate-draw.js",
    "content": "/* global d3 */\n\nimport {\n  svgStore, vSpaceAroundGapStore, hSpaceAroundGapStore, cnnStore,\n  nodeCoordinateStore, selectedScaleLevelStore, cnnLayerRangesStore,\n  needRedrawStore, cnnLayerMinMaxStore, shouldIntermediateAnimateStore,\n  hoverInfoStore, detailedModeStore, intermediateLayerPositionStore\n} from '../stores.js';\nimport {\n  getExtent, getOutputKnot, getInputKnot, gappedColorScale\n} from './draw-utils.js';\nimport {\n  drawOutput\n} from './overview-draw.js';\nimport {\n  drawIntermediateLayerLegend, moveLayerX, addOverlayGradient,\n  drawArrow\n} from './intermediate-utils.js';\nimport { singleConv } from '../utils/cnn.js';\nimport { overviewConfig } from '../config.js';\n\n// Configs\nconst layerColorScales = overviewConfig.layerColorScales;\nconst nodeLength = overviewConfig.nodeLength;\nconst plusSymbolRadius = overviewConfig.plusSymbolRadius;\nconst numLayers = overviewConfig.numLayers;\nconst intermediateColor = overviewConfig.intermediateColor;\nconst kernelRectLength = overviewConfig.kernelRectLength;\nconst svgPaddings = overviewConfig.svgPaddings;\nconst gapRatio = overviewConfig.gapRatio;\nconst overlayRectOffset = overviewConfig.overlayRectOffset;\nconst formater = d3.format('.4f');\nlet isEndOfAnimation = false;\n\n// Shared variables\nlet svg = undefined;\nsvgStore.subscribe( value => {svg = value;} )\n\nlet vSpaceAroundGap = undefined;\nvSpaceAroundGapStore.subscribe( value => {vSpaceAroundGap = value;} )\n\nlet hSpaceAroundGap = undefined;\nhSpaceAroundGapStore.subscribe( value => {hSpaceAroundGap = value;} )\n\nlet cnn = undefined;\ncnnStore.subscribe( value => {cnn = value;} )\n\nlet nodeCoordinate = undefined;\nnodeCoordinateStore.subscribe( value => {nodeCoordinate = value;} )\n\nlet selectedScaleLevel = undefined;\nselectedScaleLevelStore.subscribe( value => {selectedScaleLevel = value;} )\n\nlet cnnLayerRanges = undefined;\ncnnLayerRangesStore.subscribe( value => {cnnLayerRanges = value;} )\n\nlet cnnLayerMinMax = undefined;\ncnnLayerMinMaxStore.subscribe( value => {cnnLayerMinMax = value;} )\n\nlet needRedraw = [undefined, undefined];\nneedRedrawStore.subscribe( value => {needRedraw = value;} )\n\nlet shouldIntermediateAnimate = undefined;\nshouldIntermediateAnimateStore.subscribe(value => {\n  shouldIntermediateAnimate = value;\n})\n\nlet detailedMode = undefined;\ndetailedModeStore.subscribe( value => {detailedMode = value;} )\n\nlet intermediateLayerPosition = undefined;\nintermediateLayerPositionStore.subscribe ( value => {intermediateLayerPosition = value;} )\n\n// let curRightX = 0;\n\n/**\n * Draw the intermediate layer activation heatmaps\n * @param {element} image Neuron heatmap image\n * @param {number} range Colormap range\n * @param {function} colorScale Colormap\n * @param {number} length Image length\n * @param {[[number]]} dataMatrix Heatmap matrix\n */\nconst drawIntermidiateImage = (image, range, colorScale, length,\n  dataMatrix) => {\n  // Set up a buffer convas in order to resize image\n  let imageLength = length;\n  let bufferCanvas = document.createElement(\"canvas\");\n  let bufferContext = bufferCanvas.getContext(\"2d\");\n  bufferCanvas.width = imageLength;\n  bufferCanvas.height = imageLength;\n\n  // Fill image pixel array\n  let imageSingle = bufferContext.getImageData(0, 0, imageLength, imageLength);\n  let imageSingleArray = imageSingle.data;\n\n  for (let i = 0; i < imageSingleArray.length; i+=4) {\n    let pixeIndex = Math.floor(i / 4);\n    let row = Math.floor(pixeIndex / imageLength);\n    let column = pixeIndex % imageLength;\n    let color = d3.rgb(colorScale((dataMatrix[row][column] + range / 2) / range));\n\n    imageSingleArray[i] = color.r;\n    imageSingleArray[i + 1] = color.g;\n    imageSingleArray[i + 2] = color.b;\n    imageSingleArray[i + 3] = 255;\n  }\n\n  // canvas.toDataURL() only exports image in 96 DPI, so we can hack it to have\n  // higher DPI by rescaling the image using canvas magic\n  let largeCanvas = document.createElement('canvas');\n  largeCanvas.width = nodeLength * 3;\n  largeCanvas.height = nodeLength * 3;\n  let largeCanvasContext = largeCanvas.getContext('2d');\n\n  // Use drawImage to resize the original pixel array, and put the new image\n  // (canvas) into corresponding canvas\n  bufferContext.putImageData(imageSingle, 0, 0);\n  largeCanvasContext.drawImage(bufferCanvas, 0, 0, imageLength, imageLength,\n    0, 0, nodeLength * 3, nodeLength * 3);\n  \n  let imageDataURL = largeCanvas.toDataURL();\n  image.attr('xlink:href', imageDataURL);\n\n  // Destory the buffer canvas\n  bufferCanvas.remove();\n  largeCanvas.remove();\n}\n\n/**\n * Create a node group for the intermediate layer\n * @param {number} curLayerIndex Intermediate layer index\n * @param {number} selectedI Clicked node index\n * @param {element} groupLayer Group element\n * @param {number} x Node's x\n * @param {number} y Node's y\n * @param {number} nodeIndex Node's index\n * @param {function} intermediateNodeMouseOverHandler Mouse over handler\n * @param {function} intermediateNodeMouseLeaveHandler Mouse leave handler\n * @param {function} intermediateNodeClicked Mouse click handler\n * @param {bool} interaction Whether support interaction\n */\nconst createIntermediateNode = (curLayerIndex, selectedI, groupLayer, x, y,\n  nodeIndex, stride, intermediateNodeMouseOverHandler,\n  intermediateNodeMouseLeaveHandler, intermediateNodeClicked, interaction) => {\n  let newNode = groupLayer.append('g')\n    .datum(cnn[curLayerIndex - 1][nodeIndex])\n    .attr('class', 'intermediate-node')\n    .attr('cursor', interaction ? 'pointer': 'default')\n    .attr('pointer-events', interaction ? 'all': 'none')\n    .attr('node-index', nodeIndex)\n    .on('mouseover', intermediateNodeMouseOverHandler)\n    .on('mouseleave', intermediateNodeMouseLeaveHandler)\n    .on('click', (d, g, i) => intermediateNodeClicked(d, g, i, selectedI,\n      curLayerIndex));\n  \n  newNode.append('image')\n    .attr('width', nodeLength)\n    .attr('height', nodeLength)\n    .attr('x', x)\n    .attr('y', y);\n\n  // Overlay the image with a mask of many small rectangles\n  let strideTime = Math.floor(nodeLength / stride);\n  let overlayGroup = newNode.append('g')\n    .attr('class', 'overlay-group')\n    .attr('transform', `translate(${x}, ${y})`);\n  \n  for (let i = 0; i < strideTime; i++) {\n    for (let j = 0; j < strideTime; j++) {\n      overlayGroup.append('rect')\n        .attr('class', `mask-overlay mask-${i}-${j}`)\n        .attr('width', stride)\n        .attr('height', stride)\n        .attr('x', i * stride)\n        .attr('y', j * stride)\n        .style('fill', 'var(--light-gray)')\n        .style('stroke', 'var(--light-gray)')\n        .style('opacity', 1);\n    }\n  }\n\n  // Add a rectangle to show the border\n  newNode.append('rect')\n    .attr('class', 'bounding')\n    .attr('width', nodeLength)\n    .attr('height', nodeLength)\n    .attr('x', x)\n    .attr('y', y)\n    .style('fill', 'none')\n    .style('stroke', intermediateColor)\n    .style('stroke-width', 1);\n  \n  return newNode;\n}\n\nconst startOutputAnimation = (kernelGroup, tickTime1D, stride, delay,\n  curLayerIndex) => {\n  const slidingAnimation = () => {\n    let originX = +kernelGroup.attr('data-origin-x');\n    let originY = +kernelGroup.attr('data-origin-y');\n    let oldTick = +kernelGroup.attr('data-tick');\n    let i = (oldTick) % tickTime1D;\n    let j = Math.floor((oldTick) / tickTime1D);\n    let x = originX + i * stride;\n    let y = originY + j * stride;\n    let newTick = (oldTick + 1) % (tickTime1D * tickTime1D);\n\n    // Remove one mask rect at each tick\n    svg.selectAll(`rect.mask-${i}-${j}`)\n      .transition('window-sliding-mask')\n      .delay(delay + 100)\n      .duration(300)\n      .style('opacity', 0);\n\n      kernelGroup.attr('data-tick', newTick)\n      .transition('window-sliding-input')\n      .delay(delay)\n      .duration(200)\n      .attr('transform', `translate(${x}, ${y})`)\n      .on('end', () => {\n        if (newTick === 0) {\n          /* Uncomment to wrap the sliding\n          svg.selectAll(`rect.mask-overlay`)\n            .transition('window-sliding-mask')\n            .delay(delay - 200)\n            .duration(300)\n            .style('opacity', 1);\n          */\n\n          // Stop the animation\n          // Be careful with animation racing so call this function here instead\n          // of under selectALL\n          if (!isEndOfAnimation) {\n            animationButtonClicked(curLayerIndex);\n          }\n        }\n        if (shouldIntermediateAnimate) {\n          slidingAnimation();\n        }\n      });\n  }\n  slidingAnimation();\n}\n\nconst startIntermediateAnimation = (kernelGroupInput, kernelGroupResult,\n  tickTime1D, stride) => {\n  let delay = 200;\n  const slidingAnimation = () => {\n    let originX = +kernelGroupInput.attr('data-origin-x');\n    let originY = +kernelGroupInput.attr('data-origin-y');\n    let originXResult = +kernelGroupResult.attr('data-origin-x');\n    let oldTick = +kernelGroupInput.attr('data-tick');\n    let i = (oldTick) % tickTime1D;\n    let j = Math.floor((oldTick) / tickTime1D);\n    let x = originX + i * stride;\n    let y = originY + j * stride;\n    let xResult = originXResult + (oldTick % tickTime1D) * stride;\n    let newTick = (oldTick + 1) % (tickTime1D * tickTime1D);\n\n    // Remove one mask rect at each tick\n    svg.selectAll(`rect.mask-${i}-${j}`)\n      .transition('window-sliding-mask')\n      .delay(delay + 100)\n      .duration(300)\n      .style('opacity', 0);\n\n    kernelGroupInput.attr('data-tick', newTick)\n      .transition('window-sliding-input')\n      .delay(delay)\n      .duration(200)\n      .attr('transform', `translate(${x}, ${y})`);\n\n    kernelGroupResult.attr('data-tick', newTick)\n      .transition('window-sliding-result')\n      .delay(delay)\n      .duration(200)\n      .attr('transform', `translate(${xResult}, ${y})`)\n      .on('end', () => {\n        /* Uncomment to wrap the sliding\n        if (newTick === 0) {\n          svg.selectAll(`rect.mask-overlay`)\n            .transition('window-sliding-mask')\n            .delay(delay - 200)\n            .duration(300)\n            .style('opacity', 1);\n        }\n        */\n        if (shouldIntermediateAnimate) {\n          slidingAnimation();\n        }\n      });\n  }\n  slidingAnimation();\n}\n\nconst animationButtonClicked = (curLayerIndex) => {\n  if (d3.event !== null) {\n    d3.event.stopPropagation();\n  }\n  \n  let delay = 200;\n  let tickTime1D = nodeLength / (kernelRectLength * 3);\n  let stride = kernelRectLength * 3; \n\n  if (isEndOfAnimation) {\n    // Start the animation\n    shouldIntermediateAnimateStore.set(true);\n\n    // Show kernel\n    svg.selectAll('.kernel-clone')\n      .transition()\n      .duration(300)\n      .style('opacity', 1)\n\n    // Restore the mask\n    svg.selectAll(`rect.mask-overlay`)\n      .transition()\n      .duration(300)\n      .style('opacity', 1);\n\n    // Start the intermediate animation\n    for (let i  = 0; i < nodeCoordinate[curLayerIndex - 1].length; i++) {\n      startIntermediateAnimation(d3.select(`.kernel-input-${i}`),\n        d3.select(`.kernel-result-${i}`), tickTime1D, stride);\n    }\n\n    // Start the output animation\n    startOutputAnimation(d3.select('.kernel-output'),\n      tickTime1D, stride, delay, curLayerIndex);\n    \n    // Change the flow edge style\n    svg.selectAll('path.flow-edge')\n      .attr('stroke-dasharray', '4 2')\n      .attr('stroke-dashoffset', 0)\n      .each((d, i, g) => animateEdge(d, i, g, 0 - 1000));\n\n    // Change button icon\n    svg.select('.animation-control-button')\n      .attr('xlink:href', 'PUBLIC_URL/assets/img/fast_forward.svg');\n    \n    isEndOfAnimation = false;\n\n  } else {\n    // End the animation\n    shouldIntermediateAnimateStore.set(false);\n    \n    // Show all intermediate and output results\n    svg.selectAll(`rect.mask-overlay`)\n      .transition('skip')\n      .duration(600)\n      .style('opacity', 0);\n    \n    // Move kernel to the beginning to prepare for the next animation\n    let kernelClones = svg.selectAll('.kernel-clone');\n    kernelClones.attr('data-tick', 0)\n      .transition('skip')\n      .duration(300)\n      .style('opacity', 0)\n      .on('end', (d, i, g) => {\n        let element = d3.select(g[i]);\n        let originX = +element.attr('data-origin-x');\n        let originY = +element.attr('data-origin-y');\n        element.attr('transform', `translate(${originX}, ${originY})`);\n      });\n    \n    // Change flow edge style\n    svg.selectAll('path.flow-edge')\n      .interrupt()\n      .attr('stroke-dasharray', '0 0');\n    \n    // Change button icon\n    svg.select('.animation-control-button')\n      .attr('xlink:href', 'PUBLIC_URL/assets/img/redo.svg');\n    \n    isEndOfAnimation = true;\n  }\n}\n\nconst animateEdge = (d, i, g, dashoffset) => {\n  let curPath = d3.select(g[i]);\n  curPath.transition()\n    .duration(60000)\n    .ease(d3.easeLinear)\n    .attr('stroke-dashoffset', dashoffset)\n    .on('end', (d, i, g) => {\n      if (shouldIntermediateAnimate) {\n        animateEdge(d, i, g, dashoffset - 2000);\n      }\n    });\n}\n\n/**\n * Draw one intermediate layer\n * @param {number} curLayerIndex \n * @param {number} leftX X value of intermediate layer left border\n * @param {number} rightX X value of intermediate layer right border\n * @param {number} rightStart X value of right component starting anchor\n * @param {number} intermediateGap The inner gap\n * @param {number} d Clicked node bounded data\n * @param {number} i Clicked node index\n * @param {function} intermediateNodeMouseOverHandler Mouse over handler\n * @param {function} intermediateNodeMouseLeaveHandler Mouse leave handler\n * @param {function} intermediateNodeClicked Mouse click handler\n */\nconst drawIntermediateLayer = (curLayerIndex, leftX, rightX, rightStart,\n  intermediateGap, d, i, intermediateNodeMouseOverHandler,\n  intermediateNodeMouseLeaveHandler, intermediateNodeClicked) => {\n  \n  // curRightX = rightStart;\n\n  // Add the intermediate layer\n  let intermediateLayer = svg.append('g')\n    .attr('class', 'intermediate-layer')\n    .style('opacity', 0);\n  \n  // Recovert the animation counter\n  isEndOfAnimation = false;\n  \n  // Tried to add a rectangle to block the intermediate because of webkit's\n  // horrible support (decade old bug) for foreignObject. It doesnt work either.\n  // https://bugs.webkit.org/show_bug.cgi?id=23113\n  // (1). ForeignObject's inside position is wrong on webkit\n  // (2). 'opacity' of ForeignObject doesn't work on webkit\n  // (3). ForeignObject always show up at the front regardless the svg\n  //      stacking order on webkit\n\n  let intermediateX1 = leftX + nodeLength + intermediateGap;\n  let intermediateX2 = intermediateX1 + nodeLength + intermediateGap * 1.5;\n\n  let range = cnnLayerRanges[selectedScaleLevel][curLayerIndex];\n  let colorScale = layerColorScales[d.type];\n  let intermediateMinMax = [];\n  \n  // Copy the previsious layer to construct foreignObject placeholder\n  // Also add edges from/to the intermediate layer in this loop\n  let linkData = [];\n\n  // Accumulate the intermediate sum\n  // let itnermediateSumMatrix = init2DArray(d.output.length,\n  //  d.output.length, 0);\n\n  // Compute the min max of all kernel weights in the intermediate layer\n  let kernelExtents = d.inputLinks.map(link => getExtent(link.weight));\n  let kernelExtent = kernelExtents.reduce((acc, cur) => {\n    return [Math.min(acc[0], cur[0]), Math.max(acc[1], cur[1])];\n  })\n  let kernelRange = 2 * (Math.round(\n    Math.max(...kernelExtent.map(Math.abs)) * 1000) / 1000);\n  let kernelColorGap = 0.2;\n\n  // Compute stride for the kernel animation\n  let stride = kernelRectLength * 3; \n\n  // Also add the overlay mask on the output node\n  let outputY = nodeCoordinate[curLayerIndex][i].y;\n  let curNode = svg.select(`#layer-${curLayerIndex}-node-${i}`);\n  let outputOverlayGroup = curNode.append('g')\n    .attr('class', 'overlay-group')\n    .attr('transform', `translate(${rightX}, ${outputY})`);\n\n  let strideTime = Math.floor(nodeLength / stride);\n  \n  for (let i = 0; i < strideTime; i++) {\n    for (let j = 0; j < strideTime; j++) {\n      outputOverlayGroup.append('rect')\n        .attr('class', `mask-overlay mask-${i}-${j}`)\n        .attr('width', stride)\n        .attr('height', stride)\n        .attr('x', i * stride)\n        .attr('y', j * stride)\n        .style('fill', 'var(--light-gray)')\n        .style('stroke', 'var(--light-gray)')\n        .style('opacity', 1);\n    }\n  }\n\n  // Make sure the bounding box is on top of other things\n  curNode.select('rect.bounding').raise();\n\n  // Add sliding kernel for the output node\n  let kernelGroup = intermediateLayer.append('g')\n    .attr('class', `kernel kernel-output kernel-clone`)\n    .attr('transform', `translate(${rightX}, ${outputY})`);\n\n  kernelGroup.append('rect')\n    .attr('x', 0)\n    .attr('y', 0)\n    .attr('width', kernelRectLength * 3)\n    .attr('height', kernelRectLength * 3)\n    .attr('fill', 'none')\n    .attr('stroke', intermediateColor);\n  \n  kernelGroup.attr('data-tick', 0)\n    .attr('data-origin-x', rightX)\n    .attr('data-origin-y', outputY);\n\n  let delay = 200;\n  let tickTime1D = nodeLength / (kernelRectLength * 3);\n\n  startOutputAnimation(kernelGroup, tickTime1D, stride, delay, curLayerIndex);\n\n  // First intermediate layer\n  nodeCoordinate[curLayerIndex - 1].forEach((n, ni) => {\n\n    // Compute the intermediate value\n    let inputMatrix = cnn[curLayerIndex - 1][ni].output;\n    let kernelMatrix = cnn[curLayerIndex][i].inputLinks[ni].weight;\n    let interMatrix = singleConv(inputMatrix, kernelMatrix);\n\n    // Compute the intermediate layer min max\n    intermediateMinMax.push(getExtent(interMatrix));\n\n    // Update the intermediate sum\n    // itnermediateSumMatrix = matrixAdd(itnermediateSumMatrix, interMatrix);\n\n    // Layout the canvas and rect\n    let newNode = createIntermediateNode(curLayerIndex, i, intermediateLayer,\n      intermediateX1, n.y, ni, stride, intermediateNodeMouseOverHandler,\n      intermediateNodeMouseLeaveHandler, intermediateNodeClicked, true);\n    \n    // Draw the image\n    let image = newNode.select('image');\n    drawIntermidiateImage(image, range, colorScale, d.output.length,\n      interMatrix);      \n\n    // Edge: input -> intermediate1\n    linkData.push({\n      source: getOutputKnot({x: leftX, y: n.y}),\n      target: getInputKnot({x: intermediateX1, y: n.y}),\n      name: `input-${ni}-inter1-${ni}`\n    });\n\n    // Edge: intermediate1 -> intermediate2-1\n    linkData.push({\n      source: getOutputKnot({x: intermediateX1, y: n.y}),\n      target: getInputKnot({x: intermediateX2,\n        y: nodeCoordinate[curLayerIndex][i].y}),\n      name: `inter1-${ni}-inter2-1`\n    });\n\n    // Create a small kernel illustration\n    // Here we minus 2 because of no padding\n    // let tickTime1D = nodeLength / (kernelRectLength) - 2;\n    let kernelRectX = leftX - kernelRectLength * 3 * 2;\n    let kernelGroup = intermediateLayer.append('g')\n      .attr('class', `kernel kernel-${ni}`)\n      .attr('transform', `translate(${kernelRectX}, ${n.y})`);\n\n    let weightText = 'Kernel weights: [';\n    let f2 = d3.format('.2f');\n    for (let r = 0; r < kernelMatrix.length; r++) {\n      for (let c = 0; c < kernelMatrix[0].length; c++) {\n        kernelGroup.append('rect')\n          .attr('class', 'kernel')\n          .attr('x', kernelRectLength * c)\n          .attr('y', kernelRectLength * r)\n          .attr('width', kernelRectLength)\n          .attr('height', kernelRectLength)\n          .attr('fill', gappedColorScale(layerColorScales.weight, kernelRange,\n            kernelMatrix[r][c], kernelColorGap));\n\n        let sep = '';\n        if (c === 0 && r == 0) { sep = ''; }\n        else if (c === 0) { sep = '; '; }\n        else { sep = ', '; }\n        weightText = weightText.concat(sep, `${f2(kernelMatrix[r][c])}`);\n      }\n    }\n    weightText = weightText.concat(']');\n\n    kernelGroup.append('rect')\n      .attr('x', 0)\n      .attr('y', 0)\n      .attr('width', kernelRectLength * 3)\n      .attr('height', kernelRectLength * 3)\n      .attr('fill', 'none')\n      .attr('stroke', intermediateColor);\n    \n    kernelGroup.style('pointer-events', 'all')\n      .style('cursor', 'crosshair')\n      .on('mouseover', () => {\n        hoverInfoStore.set( {show: true, text: weightText} );\n      })\n      .on('mouseleave', () => {\n        hoverInfoStore.set( {show: false, text: weightText} );\n      })\n      .on('click', () => {d3.event.stopPropagation()});\n\n    // Sliding the kernel on the input channel and result channel at the same\n    // time\n    let kernelGroupInput = kernelGroup.clone(true)\n      .style('pointer-events', 'none')\n      .style('cursor', 'pointer')\n      .classed('kernel-clone', true)\n      .classed(`kernel-input-${ni}`, true);\n\n    kernelGroupInput.style('opacity', 0.9)\n      .selectAll('rect.kernel')\n      .style('opacity', 0.7);\n\n    kernelGroupInput.attr('transform', `translate(${leftX}, ${n.y})`)\n      .attr('data-tick', 0)\n      .attr('data-origin-x', leftX)\n      .attr('data-origin-y', n.y);\n\n    let kernelGroupResult = kernelGroup.clone(true)\n      .style('pointer-events', 'none')\n      .style('cursor', 'pointer')\n      .classed('kernel-clone', true)\n      .classed(`kernel-result-${ni}`, true);\n\n    kernelGroupResult.style('opacity', 0.9)\n      .selectAll('rect.kernel')\n      .style('fill', 'none');\n\n    kernelGroupResult.attr('transform',\n      `translate(${intermediateX1}, ${n.y})`)\n      .attr('data-origin-x', intermediateX1)\n      .attr('data-origin-y', n.y);\n    \n    startIntermediateAnimation(kernelGroupInput, kernelGroupResult, tickTime1D,\n      stride);\n  });\n\n  // Aggregate the intermediate min max\n  let aggregatedExtent = intermediateMinMax.reduce((acc, cur) => {\n    return [Math.min(acc[0], cur[0]), Math.max(acc[1], cur[1])];\n  })\n  let aggregatedMinMax = {min: aggregatedExtent[0], max: aggregatedExtent[1]};\n\n  // Draw the plus operation symbol\n  let symbolY = nodeCoordinate[curLayerIndex][i].y + nodeLength / 2;\n  let symbolRectHeight = 1;\n  let symbolGroup = intermediateLayer.append('g')\n    .attr('class', 'plus-symbol')\n    .attr('transform', `translate(${intermediateX2 + plusSymbolRadius}, ${symbolY})`);\n  \n  symbolGroup.append('rect')\n    .attr('x', -plusSymbolRadius)\n    .attr('y', -plusSymbolRadius)\n    .attr('width', 2 * plusSymbolRadius)\n    .attr('height', 2 * plusSymbolRadius)\n    .attr('rx', 3)\n    .attr('ry', 3)\n    .style('fill', 'none')\n    .style('stroke', intermediateColor);\n  \n  symbolGroup.append('rect')\n    .attr('x', -(plusSymbolRadius - 3))\n    .attr('y', -symbolRectHeight / 2)\n    .attr('width', 2 * (plusSymbolRadius - 3))\n    .attr('height', symbolRectHeight)\n    .style('fill', intermediateColor);\n\n  symbolGroup.append('rect')\n    .attr('x', -symbolRectHeight / 2)\n    .attr('y', -(plusSymbolRadius - 3))\n    .attr('width', symbolRectHeight)\n    .attr('height', 2 * (plusSymbolRadius - 3))\n    .style('fill', intermediateColor);\n\n  // Place the bias rectangle below the plus sign if user clicks the firrst\n  // conv node\n  if (i == 0) {\n    // Add bias symbol to the plus symbol\n    symbolGroup.append('circle')\n        .attr('cx', 0)\n        .attr('cy', nodeLength / 2 + kernelRectLength)\n        .attr('r', 4)\n        .style('stroke', intermediateColor)\n        .style('cursor', 'crosshair')\n        .style('fill', gappedColorScale(layerColorScales.weight, kernelRange,\n          d.bias, kernelColorGap))\n        .on('mouseover', () => {\n          hoverInfoStore.set( {show: true, text: `Bias: ${formater(d.bias)}`} );\n        })\n        .on('mouseleave', () => {\n          hoverInfoStore.set( {show: false, text: `Bias: ${formater(d.bias)}`} );\n        });\n\n    // Link from bias to the plus symbol\n    linkData.push({\n      source: {x: intermediateX2 + plusSymbolRadius,\n        y: nodeCoordinate[curLayerIndex][i].y + nodeLength},\n      target: {x: intermediateX2 + plusSymbolRadius,\n        y: nodeCoordinate[curLayerIndex][i].y + nodeLength / 2 + plusSymbolRadius},\n      name: `bias-plus`\n    });\n  } else {\n    // Add bias symbol to the plus symbol\n    symbolGroup.append('circle')\n      .attr('cx', 0)\n      .attr('cy', -nodeLength / 2 - kernelRectLength)\n      .attr('r', 4)\n      .style('stroke', intermediateColor)\n      .style('cursor', 'crosshair')\n      .style('fill', gappedColorScale(layerColorScales.weight, kernelRange,\n        d.bias, kernelColorGap))\n      .on('mouseover', () => {\n        hoverInfoStore.set( {show: true, text: `Bias: ${formater(d.bias)}`} );\n      })\n      .on('mouseleave', () => {\n        hoverInfoStore.set( {show: false, text: `Bias: ${formater(d.bias)}`} );\n      });\n    \n    // Link from bias to the plus symbol\n    linkData.push({\n      source: {x: intermediateX2 + plusSymbolRadius,\n        y: nodeCoordinate[curLayerIndex][i].y},\n      target: {x: intermediateX2 + plusSymbolRadius,\n        y: nodeCoordinate[curLayerIndex][i].y + nodeLength / 2 - plusSymbolRadius},\n      name: `bias-plus`\n    });\n  }\n\n  // Link from the plus symbol to the output\n  linkData.push({\n    source: getOutputKnot({x: intermediateX2 + 2 * plusSymbolRadius - nodeLength,\n      y: nodeCoordinate[curLayerIndex][i].y}),\n    target: getInputKnot({x: rightX,\n      y: nodeCoordinate[curLayerIndex][i].y}),\n    name: `symbol-output`\n  });\n  \n  // Output -> next layer\n  linkData.push({\n    source: getOutputKnot({x: rightX,\n      y: nodeCoordinate[curLayerIndex][i].y}),\n    target: getInputKnot({x: rightStart,\n      y: nodeCoordinate[curLayerIndex][i].y}),\n    name: `output-next`\n  });\n\n  // Draw the layer label\n  intermediateLayer.append('g')\n    .attr('class', 'layer-intermediate-label layer-label')\n    .attr('transform', () => {\n      let x = intermediateX1 + nodeLength / 2;\n      let y = (svgPaddings.top + vSpaceAroundGap) / 2 + 5;\n      return `translate(${x}, ${y})`;\n    })\n    .classed('hidden', detailedMode)\n    .append('text')\n    .style('text-anchor', 'middle')\n    .style('dominant-baseline', 'middle')\n    .style('font-weight', 800)\n    .style('opacity', '0.8')\n    .text('intermediate');\n  \n  intermediateLayer.append('g')\n    .attr('class', 'animation-control')\n    .attr('transform', () => {\n      let x = intermediateX1 + nodeLength / 2;\n      let y = (svgPaddings.top + vSpaceAroundGap) / 2 - 4;\n      return `translate(${x}, ${y})`;\n    })\n    .on('click', () => animationButtonClicked(curLayerIndex))\n    .append('image')\n    .attr('class', 'animation-control-button')\n    .attr('xlink:href', 'PUBLIC_URL/assets/img/fast_forward.svg')\n    .attr('x', 50)\n    .attr('y', 0)\n    .attr('height', 13)\n    .attr('width', 13);\n\n  // Draw the detailed model layer label\n  intermediateLayer.append('g')\n    .attr('class', 'layer-intermediate-label layer-detailed-label')\n    .attr('transform', () => {\n      let x = intermediateX1 + nodeLength / 2;\n      let y = (svgPaddings.top + vSpaceAroundGap) / 2 - 5;\n      return `translate(${x}, ${y})`;\n    })\n    .classed('hidden', !detailedMode)\n    .append('text')\n    .style('text-anchor', 'middle')\n    .style('dominant-baseline', 'middle')\n    .style('opacity', '0.7')\n    .style('font-weight', 800)\n    .append('tspan')\n    .text('intermediate')\n    .append('tspan')\n    .style('font-size', '8px')\n    .style('font-weight', 'normal')\n    .attr('x', 0)\n    .attr('dy', '1.5em')\n    .text(`(${cnn[curLayerIndex][0].output.length},\n      ${cnn[curLayerIndex][0].output[0].length},\n      ${cnn[curLayerIndex].length})`);\n\n  // Draw the edges\n  let linkGen = d3.linkHorizontal()\n    .x(d => d.x)\n    .y(d => d.y);\n  \n  let edgeGroup = intermediateLayer.append('g')\n    .attr('class', 'edge-group')\n    .lower();\n  \n  let dashoffset = 0;\n\n  edgeGroup.selectAll('path')\n    .data(linkData)\n    .enter()\n    .append('path')\n    .classed('flow-edge', d => d.name !== 'output-next')\n    .attr('id', d => `edge-${d.name}`)\n    .attr('d', d => linkGen({source: d.source, target: d.target}))\n    .style('fill', 'none')\n    .style('stroke-width', 1)\n    .style('stroke', intermediateColor);\n\n  edgeGroup.select('#edge-output-next')\n    .style('opacity', 0.1);\n  \n  edgeGroup.selectAll('path.flow-edge')\n    .attr('stroke-dasharray', '4 2')\n    .attr('stroke-dashoffset', 0)\n    .each((d, i, g) => animateEdge(d, i, g, dashoffset - 1000));\n  \n  return {intermediateLayer: intermediateLayer,\n    intermediateMinMax: aggregatedMinMax,\n    kernelRange: kernelRange,\n    kernelMinMax: {min: kernelExtent[0], max: kernelExtent[1]}};\n}\n\n/**\n * Add an annotation for the kernel and the sliding\n * @param {object} arg \n * {\n *  leftX: X value of the left border of intermedaite layer\n *  group: element group\n *  intermediateGap: the inner gap of intermediate layer\n *  isFirstConv: if this intermediate layer is after the first layer\n *  i: index of the selected node\n * }\n */\nconst drawIntermediateLayerAnnotation = (arg) => {\n  let leftX = arg.leftX,\n    curLayerIndex = arg.curLayerIndex,\n    group = arg.group,\n    intermediateGap = arg.intermediateGap,\n    isFirstConv = arg.isFirstConv,\n    i = arg.i;\n\n  let kernelAnnotation = group.append('g')\n    .attr('class', 'kernel-annotation');\n  \n  kernelAnnotation.append('text')\n    .text('Kernel')\n    .attr('class', 'annotation-text')\n    .attr('x', leftX - 2.5 * kernelRectLength * 3)\n    .attr('y', nodeCoordinate[curLayerIndex - 1][0].y + kernelRectLength * 3)\n    .style('dominant-baseline', 'baseline')\n    .style('text-anchor', 'end');\n\n  let sliderX, sliderY, arrowSX, arrowSY, dr;\n  let sliderX2, sliderY2, arrowSX2, arrowSY2, dr2, arrowTX2, arrowTY2;\n  \n  if (isFirstConv) {\n    sliderX = leftX;\n    sliderY = nodeCoordinate[curLayerIndex - 1][0].y + nodeLength +\n      kernelRectLength * 3;\n    arrowSX = leftX - 5;\n    arrowSY = nodeCoordinate[curLayerIndex - 1][0].y + nodeLength +\n      kernelRectLength * 3 + 5;\n    dr = 20;\n\n    sliderX2 = leftX;\n      sliderY2 = nodeCoordinate[curLayerIndex - 1][1].y + nodeLength +\n    kernelRectLength * 3;\n    arrowSX2 = leftX - kernelRectLength * 3;\n    arrowSY2 = nodeCoordinate[curLayerIndex - 1][1].y + nodeLength + 15;\n    arrowTX2 = leftX - 13;\n    arrowTY2 =  nodeCoordinate[curLayerIndex - 1][1].y + 15;\n    dr2 = 35;\n  } else {\n    sliderX = leftX - 3 * kernelRectLength * 3;\n    sliderY = nodeCoordinate[curLayerIndex - 1][0].y + nodeLength / 3;\n    arrowSX = leftX - 2 * kernelRectLength * 3 - 5;\n    arrowSY = nodeCoordinate[curLayerIndex - 1][0].y + nodeLength - 10;\n    dr = 50;\n\n    sliderX2 = leftX - 3 * kernelRectLength * 3;\n    sliderY2 = nodeCoordinate[curLayerIndex - 1][2].y - 3;\n    arrowTX2 = leftX - kernelRectLength * 3 - 4;\n    arrowTY2 = nodeCoordinate[curLayerIndex - 1][2].y + kernelRectLength * 3 + 6;\n    arrowSX2 = leftX - kernelRectLength * 3 - 13;\n    arrowSY2 = nodeCoordinate[curLayerIndex - 1][2].y + 26;\n    dr2 = 20;\n  }\n\n  let slideText = kernelAnnotation.append('text')\n    .attr('x', sliderX)\n    .attr('y', sliderY)\n    .attr('class', 'annotation-text')\n    .style('dominant-baseline', 'hanging')\n    .style('text-anchor', isFirstConv ? 'start' : 'end');\n  \n  slideText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .text('Slide kernel over input channel');\n\n  slideText.append('tspan')\n    .attr('x', sliderX)\n    .attr('dy', '1em')\n    .style('dominant-baseline', 'hanging')\n    .text('to get intermediate result');\n\n  // slideText.append('tspan')\n  //   .attr('x', sliderX)\n  //   .attr('dy', '1em')\n  //   .style('dominant-baseline', 'hanging')\n  //   .text('');\n\n  slideText.append('tspan')\n    .attr('x', sliderX)\n    .attr('dy', '1.2em')\n    .style('dominant-baseline', 'hanging')\n    .style('font-weight', 700)\n    .text('Click ');\n  \n  slideText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .style('font-weight', 400)\n    .text('to learn more')\n\n  drawArrow({\n    group: group,\n    tx: leftX - 7,\n    ty: nodeCoordinate[curLayerIndex - 1][0].y + nodeLength / 2,\n    sx: arrowSX,\n    sy: arrowSY,\n    hFlip: !isFirstConv,\n    dr: dr,\n    marker: 'marker'\n  });\n\n  // Add kernel annotation\n  let slideText2 = kernelAnnotation.append('text')\n    .attr('x', sliderX2)\n    .attr('y', sliderY2)\n    .attr('class', 'annotation-text')\n    .style('dominant-baseline', 'hanging')\n    .style('text-anchor', isFirstConv ? 'start' : 'end');\n\n  slideText2.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .text('Each input chanel');\n\n  slideText2.append('tspan')\n    .attr('x', sliderX)\n    .attr('dy', '1em')\n    .style('dominant-baseline', 'hanging')\n    .text('gets a different kernel');\n\n  slideText2.append('tspan')\n    .attr('x', sliderX)\n    .attr('dy', '1.3em')\n    .style('font-weight', 700)\n    .style('dominant-baseline', 'hanging')\n    .text('Hover over ');\n\n  slideText2.append('tspan')\n    .style('font-weight', 400)\n    .style('dominant-baseline', 'hanging')\n    .text('to see value!')\n\n  drawArrow({\n    group: group,\n    tx: arrowTX2,\n    ty: arrowTY2,\n    sx: arrowSX2,\n    sy: arrowSY2,\n    dr: dr2,\n    hFlip: !isFirstConv,\n    marker: 'marker'\n  });\n\n\n  // Add annotation for the sum operation\n  let plusAnnotation = group.append('g')\n    .attr('class', 'plus-annotation');\n  \n  let intermediateX2 = leftX + 2 * nodeLength + 2.5 * intermediateGap;\n  let textX = intermediateX2;\n  let textY = nodeCoordinate[curLayerIndex][i].y + nodeLength +\n      kernelRectLength * 3;\n  \n  // Special case 1: first node\n  if (i === 0) { textX += 30; }\n\n  // Special case 2: last node \n  if (i === 9) {\n    textX = intermediateX2 + plusSymbolRadius - 10;\n    textY -= 2.5 * nodeLength;\n  }\n\n  let plusText = plusAnnotation.append('text')\n    .attr('x', textX)\n    .attr('y', textY)\n    .attr('class', 'annotation-text')\n    .style('dominant-baseline', 'hanging')\n    .style('text-anchor', 'start');\n  \n  plusText.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .text('Add up all intermediate');\n  \n  plusText.append('tspan')\n    .attr('x', textX)\n    .attr('dy', '1em')\n    .style('dominant-baseline', 'hanging')\n    .text('results and then add bias');\n  \n  if (i === 9) {\n    drawArrow({\n      group: group,\n      sx: intermediateX2 + 50,\n      sy: nodeCoordinate[curLayerIndex][i].y - (nodeLength / 2 + kernelRectLength * 2),\n      tx: intermediateX2 + 2 * plusSymbolRadius + 5,\n      ty: nodeCoordinate[curLayerIndex][i].y + nodeLength / 2 - plusSymbolRadius,\n      dr: 50,\n      hFlip: false,\n      marker: 'marker-alt'\n    });\n  } else {\n    drawArrow({\n      group: group,\n      sx: intermediateX2 + 35,\n      sy: nodeCoordinate[curLayerIndex][i].y + nodeLength + kernelRectLength * 2,\n      tx: intermediateX2 + 2 * plusSymbolRadius + 5,\n      ty: nodeCoordinate[curLayerIndex][i].y + nodeLength / 2 + plusSymbolRadius,\n      dr: 30,\n      hFlip: true,\n      marker: 'marker-alt'\n    });\n  }\n\n  // Add annotation for the bias\n  let biasTextY = nodeCoordinate[curLayerIndex][i].y;\n  if (i === 0) {\n    biasTextY += nodeLength + 3 * kernelRectLength;\n  } else {\n    biasTextY -= 2 * kernelRectLength + 5;\n  }\n  plusAnnotation.append('text')\n    .attr('class', 'annotation-text')\n    .attr('x', intermediateX2 + plusSymbolRadius)\n    .attr('y', biasTextY)\n    .style('text-anchor', 'middle')\n    .style('dominant-baseline', i === 0 ? 'hanging' : 'baseline')\n    .text('Bias');\n}\n\n/**\n * Append a filled rectangle under a pair of nodes.\n * @param {number} curLayerIndex Index of the selected layer\n * @param {number} i Index of the selected node\n * @param {number} leftX X value of the left border of intermediate layer\n * @param {number} intermediateGap Inner gap of this intermediate layer\n * @param {number} padding Padding around the rect\n * @param {function} intermediateNodeMouseOverHandler Mouse over handler\n * @param {function} intermediateNodeMouseLeaveHandler Mouse leave handler\n * @param {function} intermediateNodeClicked Mouse click handler\n */\nconst addUnderneathRect = (curLayerIndex, i, leftX,\n  intermediateGap, padding, intermediateNodeMouseOverHandler,\n  intermediateNodeMouseLeaveHandler, intermediateNodeClicked) => {\n  // Add underneath rects\n  let underGroup = svg.select('g.underneath');\n\n  for (let n = 0; n < cnn[curLayerIndex - 1].length; n++) {\n    underGroup.append('rect')\n      .attr('class', 'underneath-gateway')\n      .attr('id', `underneath-gateway-${n}`)\n      .attr('x', leftX - padding)\n      .attr('y', nodeCoordinate[curLayerIndex - 1][n].y - padding)\n      .attr('width', (2 * nodeLength + intermediateGap) + 2 * padding)\n      .attr('height', nodeLength + 2 * padding)\n      .attr('rx', 10)\n      .style('fill', 'rgba(160, 160, 160, 0.2)')\n      .style('opacity', 0);\n    \n    // Register new events for input layer nodes\n    svg.select(`g#layer-${curLayerIndex - 1}-node-${n}`)\n      .style('pointer-events', 'all')\n      .style('cursor', 'pointer')\n      .on('mouseover', intermediateNodeMouseOverHandler)\n      .on('mouseleave', intermediateNodeMouseLeaveHandler)\n      .on('click', (d, ni, g) => intermediateNodeClicked(d, ni, g,\n        i, curLayerIndex));\n      // .on('click', (d, i) => {console.log(i)});\n  }\n  underGroup.lower();\n}\n\n/**\n * Add an overlaying rect\n * @param {string} gradientName Gradient name of overlay rect\n * @param {number} x X value of the overlaying rect\n * @param {number} y Y value of the overlaying rect\n * @param {number} width Rect width\n * @param {number} height Rect height\n */\nexport const addOverlayRect = (gradientName, x, y, width, height) => {\n  if (svg.select('.intermediate-layer-overlay').empty()) {\n    svg.append('g').attr('class', 'intermediate-layer-overlay');\n  }\n\n  let intermediateLayerOverlay = svg.select('.intermediate-layer-overlay');\n\n  let overlayRect = intermediateLayerOverlay.append('rect')\n    .attr('class', 'overlay')\n    .style('fill', `url(#${gradientName})`)\n    .style('stroke', 'none')\n    .attr('width', width)\n    .attr('height', height)\n    .attr('x', x)\n    .attr('y', y)\n    .style('opacity', 0);\n  \n  overlayRect.transition('move')\n    .duration(800)\n    .ease(d3.easeCubicInOut)\n    .style('opacity', 1);\n}\n\n/**\n * Redraw the layer if needed (entering the intermediate view to make sure\n * all layers have the same color scale)\n * @param {number} curLayerIndex Index of the selected layer\n * @param {number} i Index of the selected node\n */\nconst redrawLayerIfNeeded = (curLayerIndex, i) => {\n  // Determine the range for this layerview, and redraw the layer with\n  // smaller range so all layers have the same range\n  let rangePre = cnnLayerRanges[selectedScaleLevel][curLayerIndex - 1];\n  let rangeCur = cnnLayerRanges[selectedScaleLevel][curLayerIndex];\n  let range = Math.max(rangePre, rangeCur);\n\n  if (rangePre > rangeCur) {\n    // Redraw the current layer (selected node)\n    svg.select(`g#layer-${curLayerIndex}-node-${i}`)\n      .select('image.node-image')\n      .each((d, g, i) => drawOutput(d, g, i, range));\n    \n    // Record the change so we will re-redraw the layer when user quits\n    // the intermediate view\n    needRedraw = [curLayerIndex, i];\n    needRedrawStore.set(needRedraw);\n    \n  } else if (rangePre < rangeCur) {\n    // Redraw the previous layer (whole layer)\n    svg.select(`g#cnn-layer-group-${curLayerIndex - 1}`)\n      .selectAll('image.node-image')\n      .each((d, g, i) => drawOutput(d, g, i, range));\n\n    // Record the change so we will re-redraw the layer when user quits\n    // the intermediate view\n    needRedraw = [curLayerIndex - 1, undefined];\n    needRedrawStore.set(needRedraw);\n  }\n\n  // Compute the min, max value of all nodes in pre-layer and the selected\n  // node of cur-layer\n  let min = cnnLayerMinMax[curLayerIndex - 1].min,\n    max = cnnLayerMinMax[curLayerIndex - 1].max;\n\n  // Selected node\n  let n = cnn[curLayerIndex][i];\n  for (let r = 0; r < n.output.length; r++) {\n    for (let c = 0; c < n.output[0].length; c++) {\n      if (n.output[r][c] < min) { min = n.output[r][c]; }\n      if (n.output[r][c] > max) { max = n.output[r][c]; }\n    }\n  }\n\n  return {range: range, minMax: {min: min, max: max}};\n}\n\n/**\n * Draw the intermediate layer before conv_1_1\n * @param {number} curLayerIndex Index of the selected layer\n * @param {object} d Bounded d3 data\n * @param {number} i Index of the selected node\n * @param {number} width CNN group width\n * @param {number} height CNN group height\n * @param {function} intermediateNodeMouseOverHandler mouse over handler\n * @param {function} intermediateNodeMouseLeaveHandler mouse leave handler\n * @param {function} intermediateNodeClicked node clicking handler\n */\nexport const drawConv1 = (curLayerIndex, d, i, width, height,\n  intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n  intermediateNodeClicked) => {\n  // Compute the target location\n  let targetX = nodeCoordinate[curLayerIndex - 1][0].x + 2 * nodeLength +\n    2 * hSpaceAroundGap * gapRatio + plusSymbolRadius * 2;\n  let intermediateGap = (hSpaceAroundGap * gapRatio * 2) / 3;\n  let leftX = nodeCoordinate[curLayerIndex - 1][0].x;\n\n  // Record the left x position for dynamic detial view positioning\n  intermediateLayerPosition['conv_1_1'] = targetX + nodeLength;\n  intermediateLayerPositionStore.set(intermediateLayerPosition);\n\n  // Hide the edges\n  svg.select('g.edge-group')\n    .style('visibility', 'hidden');\n\n  // Move the selected layer\n  moveLayerX({layerIndex: curLayerIndex, targetX: targetX, disable: true,\n    delay: 0, opacity: 0.15, specialIndex: i});\n\n  // Compute the gap in the right shrink region\n  let rightStart = targetX + nodeLength + hSpaceAroundGap * gapRatio;\n  let rightGap = (width - rightStart - 10 * nodeLength) / 10;\n\n  // Move the right layers\n  for (let i = curLayerIndex + 1; i < numLayers; i++) {\n    let curX = rightStart + (i - (curLayerIndex + 1)) * (nodeLength + rightGap);\n    moveLayerX({layerIndex: i, targetX: curX, disable: true, delay: 0});\n  }\n\n  // Add an overlay gradient and rect\n  let stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: 0.85},\n  {offset: '50%', color: 'rgb(250, 250, 250)', opacity: 0.95},\n  {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 1}];\n  addOverlayGradient('overlay-gradient', stops);\n\n  addOverlayRect('overlay-gradient', rightStart - overlayRectOffset / 2,\n  0, width - rightStart + overlayRectOffset,\n  height + svgPaddings.top + svgPaddings.bottom);\n\n  // Draw the intermediate layer\n  let {intermediateLayer, intermediateMinMax, kernelRange, kernelMinMax} =\n  drawIntermediateLayer(curLayerIndex, leftX, targetX, rightStart,\n    intermediateGap, d, i, intermediateNodeMouseOverHandler,\n    intermediateNodeMouseLeaveHandler, intermediateNodeClicked);\n  addUnderneathRect(curLayerIndex, i, leftX, intermediateGap, 8,\n    intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n    intermediateNodeClicked);\n\n  // Compute the selected node's min max\n  // Selected node\n  let min = Infinity, max = -Infinity;\n  let n = cnn[curLayerIndex][i];\n  for (let r = 0; r < n.output.length; r++) {\n  for (let c = 0; c < n.output[0].length; c++) {\n    if (n.output[r][c] < min) { min = n.output[r][c]; }\n    if (n.output[r][c] > max) { max = n.output[r][c]; }\n  }\n  }\n\n  let finalMinMax = {\n  min: Math.min(min, intermediateMinMax.min),\n  max: Math.max(max, intermediateMinMax.max)\n  }\n\n  // Add annotation to the intermediate layer\n  let intermediateLayerAnnotation = svg.append('g')\n  .attr('class', 'intermediate-layer-annotation')\n  .style('opacity', 0);\n\n  drawIntermediateLayerAnnotation({\n    leftX: leftX,\n    curLayerIndex: curLayerIndex,\n    group: intermediateLayerAnnotation,\n    intermediateGap: intermediateGap,\n    isFirstConv: true,\n    i: i\n  });\n\n  let range = cnnLayerRanges.local[curLayerIndex];\n\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: 1,\n    group: intermediateLayer,\n    width: 2 * nodeLength + intermediateGap,\n    isInput: true,\n    x: leftX,\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10 - 25\n  });\n\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: range,\n    minMax: finalMinMax,\n    group: intermediateLayer,\n    width: 2 * nodeLength + intermediateGap,\n    x: nodeCoordinate[curLayerIndex - 1][2].x,\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10\n  });\n\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: kernelRange,\n    minMax: kernelMinMax,\n    group: intermediateLayer,\n    width: 2 * nodeLength + intermediateGap,\n    x: targetX + nodeLength - (2 * nodeLength + intermediateGap),\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10,\n    gradientAppendingName: 'kernelColorGradient',\n    colorScale: layerColorScales.weight,\n    gradientGap: 0.2\n  });\n\n  // Show everything\n  svg.selectAll('g.intermediate-layer, g.intermediate-layer-annotation')\n    .transition()\n    .delay(500)\n    .duration(500)\n    .ease(d3.easeCubicInOut)\n    .style('opacity', 1);\n}\n\n/**\n * Draw the intermediate layer before conv_1_2\n * @param {number} curLayerIndex Index of the selected layer\n * @param {object} d Bounded d3 data\n * @param {number} i Index of the selected node\n * @param {number} width CNN group width\n * @param {number} height CNN group height\n * @param {function} intermediateNodeMouseOverHandler mouse over handler\n * @param {function} intermediateNodeMouseLeaveHandler mouse leave handler\n * @param {function} intermediateNodeClicked node clicking handler\n */\nexport const drawConv2 = (curLayerIndex, d, i, width, height,\n  intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n  intermediateNodeClicked) => {\n  let targetX = nodeCoordinate[curLayerIndex - 1][0].x + 2 * nodeLength +\n    2 * hSpaceAroundGap * gapRatio + plusSymbolRadius * 2;\n  let intermediateGap = (hSpaceAroundGap * gapRatio * 2) / 3;\n\n  // Record the left x position for dynamic detial view positioning\n  intermediateLayerPosition['conv_1_2'] = targetX + nodeLength;\n  intermediateLayerPositionStore.set(intermediateLayerPosition);\n\n  // Make sure two layers have the same range\n  let {range, minMax} = redrawLayerIfNeeded(curLayerIndex, i);\n\n  // Hide the edges\n  svg.select('g.edge-group')\n    .style('visibility', 'hidden');\n\n  // Move the selected layer\n  moveLayerX({layerIndex: curLayerIndex, targetX: targetX, disable: true,\n    delay: 0, opacity: 0.15, specialIndex: i});\n\n  // Compute the gap in the right shrink region\n  let rightStart = targetX + nodeLength + hSpaceAroundGap * gapRatio;\n  let rightGap = (width - rightStart - 8 * nodeLength) / 8;\n\n  // Move the right layers\n  for (let i = curLayerIndex + 1; i < numLayers; i++) {\n    let curX = rightStart + (i - (curLayerIndex + 1)) * (nodeLength + rightGap);\n    moveLayerX({layerIndex: i, targetX: curX, disable: true, delay: 0});\n  }\n\n  // Add an overlay\n  let stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: 0.85},\n    {offset: '50%', color: 'rgb(250, 250, 250)', opacity: 0.95},\n    {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 1}];\n  addOverlayGradient('overlay-gradient-right', stops);\n\n  let leftRightRatio = (2 * nodeLength + hSpaceAroundGap * gapRatio) /\n    (8 * nodeLength + intermediateGap * 7);\n  let endingGradient = 0.85 + (0.95 - 0.85) * leftRightRatio;\n  stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: endingGradient},\n    {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 0.85}];\n  addOverlayGradient('overlay-gradient-left', stops);\n\n  addOverlayRect('overlay-gradient-right', rightStart - overlayRectOffset / 2,\n    0, width - rightStart + overlayRectOffset,\n    height + svgPaddings.top + svgPaddings.bottom);\n\n  addOverlayRect('overlay-gradient-left', nodeCoordinate[0][0].x - overlayRectOffset / 2,\n    0, nodeLength * 2 + hSpaceAroundGap * gapRatio + overlayRectOffset,\n    height + svgPaddings.top + svgPaddings.bottom);\n\n  // Draw the intermediate layer\n  let leftX = nodeCoordinate[curLayerIndex - 1][0].x;\n  let {intermediateLayer, intermediateMinMax, kernelRange, kernelMinMax} =\n    drawIntermediateLayer(curLayerIndex, leftX, targetX, rightStart,\n      intermediateGap, d, i, intermediateNodeMouseOverHandler,\n      intermediateNodeMouseLeaveHandler, intermediateNodeClicked);\n  addUnderneathRect(curLayerIndex, i, leftX, intermediateGap, 5,\n    intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n    intermediateNodeClicked);\n\n  // After getting the intermediateMinMax, we can finally aggregate it with\n  // the preLayer minmax, curLayer minmax\n  let finalMinMax = {\n    min: Math.min(minMax.min, intermediateMinMax.min),\n    max: Math.max(minMax.max, intermediateMinMax.max)\n  }\n\n  // Add annotation to the intermediate layer\n  let intermediateLayerAnnotation = svg.append('g')\n    .attr('class', 'intermediate-layer-annotation')\n    .style('opacity', 0);\n\n  drawIntermediateLayerAnnotation({\n    leftX: leftX,\n    curLayerIndex: curLayerIndex,\n    group: intermediateLayerAnnotation,\n    intermediateGap: intermediateGap,\n    i: i\n  });\n\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: range,\n    minMax: finalMinMax,\n    group: intermediateLayer,\n    width: 2 * nodeLength + intermediateGap,\n    x: leftX,\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10\n  });\n\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: kernelRange,\n    minMax: kernelMinMax,\n    group: intermediateLayer,\n    width: 2 * nodeLength + intermediateGap,\n    x: targetX + nodeLength - (2 * nodeLength + intermediateGap),\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10,\n    gradientAppendingName: 'kernelColorGradient',\n    colorScale: layerColorScales.weight,\n    gradientGap: 0.2\n  });\n\n  // Show everything\n  svg.selectAll('g.intermediate-layer, g.intermediate-layer-annotation')\n    .transition()\n    .delay(500)\n    .duration(500)\n    .ease(d3.easeCubicInOut)\n    .style('opacity', 1);\n}\n\n/**\n * Draw the intermediate layer before conv_2_1\n * @param {number} curLayerIndex Index of the selected layer\n * @param {object} d Bounded d3 data\n * @param {number} i Index of the selected node\n * @param {number} width CNN group width\n * @param {number} height CNN group height\n * @param {function} intermediateNodeMouseOverHandler mouse over handler\n * @param {function} intermediateNodeMouseLeaveHandler mouse leave handler\n * @param {function} intermediateNodeClicked node clicking handler\n */\nexport const drawConv3 = (curLayerIndex, d, i, width, height,\n  intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n  intermediateNodeClicked) => {\n\n  let targetX = nodeCoordinate[curLayerIndex][0].x;\n  let leftX = targetX - (2 * nodeLength +\n    2 * hSpaceAroundGap * gapRatio + plusSymbolRadius * 2);\n  let intermediateGap = (hSpaceAroundGap * gapRatio * 2) / 3;\n\n  // Record the left x position for dynamic detial view positioning\n  intermediateLayerPosition['conv_2_1'] = targetX + nodeLength;\n  intermediateLayerPositionStore.set(intermediateLayerPosition);\n\n  // Hide the edges\n  svg.select('g.edge-group')\n    .style('visibility', 'hidden');\n\n  // Make sure two layers have the same range\n  let {range, minMax} = redrawLayerIfNeeded(curLayerIndex, i);\n\n  // Move the previous layer\n  moveLayerX({layerIndex: curLayerIndex - 1, targetX: leftX,\n    disable: true, delay: 0});\n\n  moveLayerX({layerIndex: curLayerIndex,\n    targetX: targetX, disable: true,\n    delay: 0, opacity: 0.15, specialIndex: i});\n\n  // Compute the gap in the left shrink region\n  let leftEnd = leftX - hSpaceAroundGap;\n  let leftGap = (leftEnd - nodeCoordinate[0][0].x - 5 * nodeLength) / 5;\n  let rightStart = nodeCoordinate[curLayerIndex][0].x +\n    nodeLength + hSpaceAroundGap;\n\n  // Move the left layers\n  for (let i = 0; i < curLayerIndex - 1; i++) {\n    let curX = nodeCoordinate[0][0].x + i * (nodeLength + leftGap);\n    moveLayerX({layerIndex: i, targetX: curX, disable: true, delay: 0});\n  }\n\n  // Add an overlay\n  let stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: 1},\n    {offset: '50%', color: 'rgb(250, 250, 250)', opacity: 0.9},\n    {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 0.85}];\n  addOverlayGradient('overlay-gradient-left', stops);\n\n  stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: 0.85},\n    {offset: '50%', color: 'rgb(250, 250, 250)', opacity: 0.95},\n    {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 1}];\n  addOverlayGradient('overlay-gradient-right', stops);\n\n  addOverlayRect('overlay-gradient-left', nodeCoordinate[0][0].x - overlayRectOffset / 2,\n    0, leftEnd - nodeCoordinate[0][0].x + overlayRectOffset,\n    height + svgPaddings.top + svgPaddings.bottom);\n  \n  addOverlayRect('overlay-gradient-right', rightStart - overlayRectOffset / 2,\n    0, width - rightStart + overlayRectOffset,\n    height + svgPaddings.top + svgPaddings.bottom);\n  \n  // Draw the intermediate layer\n  let {intermediateLayer, intermediateMinMax, kernelRange, kernelMinMax} =\n    drawIntermediateLayer(curLayerIndex, leftX,\n      nodeCoordinate[curLayerIndex][0].x, rightStart, intermediateGap,\n      d, i, intermediateNodeMouseOverHandler,\n      intermediateNodeMouseLeaveHandler, intermediateNodeClicked);\n  addUnderneathRect(curLayerIndex, i, leftX, intermediateGap, 5,\n    intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n    intermediateNodeClicked);\n          \n  // After getting the intermediateMinMax, we can finally aggregate it with\n  // the preLayer minmax, curLayer minmax\n  let finalMinMax = {\n    min: Math.min(minMax.min, intermediateMinMax.min),\n    max: Math.max(minMax.max, intermediateMinMax.max)\n  }\n\n  // Add annotation to the intermediate layer\n  let intermediateLayerAnnotation = svg.append('g')\n    .attr('class', 'intermediate-layer-annotation')\n    .style('opacity', 0);\n\n  drawIntermediateLayerAnnotation({\n    leftX: leftX,\n    curLayerIndex: curLayerIndex,\n    group: intermediateLayerAnnotation,\n    intermediateGap: intermediateGap,\n    i: i\n  });\n\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: range,\n    group: intermediateLayer,\n    width: 2 * nodeLength + intermediateGap,\n    minMax: finalMinMax,\n    x: leftX,\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10\n  });\n\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: kernelRange,\n    minMax: kernelMinMax,\n    group: intermediateLayer,\n    width: 2 * nodeLength + intermediateGap,\n    x: targetX + nodeLength - (2 * nodeLength + intermediateGap),\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10,\n    gradientAppendingName: 'kernelColorGradient',\n    colorScale: layerColorScales.weight,\n    gradientGap: 0.2\n  });\n\n  // Show everything\n  svg.selectAll('g.intermediate-layer, g.intermediate-layer-annotation')\n    .transition()\n    .delay(500)\n    .duration(500)\n    .ease(d3.easeCubicInOut)\n    .style('opacity', 1);\n}\n\n/**\n * Draw the intermediate layer before conv_2_2\n * @param {number} curLayerIndex Index of the selected layer\n * @param {object} d Bounded d3 data\n * @param {number} i Index of the selected node\n * @param {number} width CNN group width\n * @param {number} height CNN group height\n * @param {function} intermediateNodeMouseOverHandler mouse over handler\n * @param {function} intermediateNodeMouseLeaveHandler mouse leave handler\n * @param {function} intermediateNodeClicked node clicking handler\n */\nexport const drawConv4 = (curLayerIndex, d, i, width, height,\n  intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n  intermediateNodeClicked) => {\n  let targetX = nodeCoordinate[curLayerIndex][0].x;\n  let leftX = targetX - (2 * nodeLength +\n    2 * hSpaceAroundGap * gapRatio + plusSymbolRadius * 2);\n  let intermediateGap = (hSpaceAroundGap * gapRatio * 2) / 3;\n\n  // Record the left x position for dynamic detial view positioning\n  intermediateLayerPosition['conv_2_2'] = leftX;\n  intermediateLayerPositionStore.set(intermediateLayerPosition);\n\n  // Hide the edges\n  svg.select('g.edge-group')\n    .style('visibility', 'hidden');\n\n  // Make sure two layers have the same range\n  let {range, minMax} = redrawLayerIfNeeded(curLayerIndex, i);\n\n  // Move the previous layer\n  moveLayerX({layerIndex: curLayerIndex - 1, targetX: leftX,\n    disable: true, delay: 0});\n\n  moveLayerX({layerIndex: curLayerIndex,\n    targetX: targetX, disable: true,\n    delay: 0, opacity: 0.15, specialIndex: i});\n\n  // Compute the gap in the left shrink region\n  let leftEnd = leftX - hSpaceAroundGap;\n  let leftGap = (leftEnd - nodeCoordinate[0][0].x - 7 * nodeLength) / 7;\n  let rightStart = targetX + nodeLength + hSpaceAroundGap;\n\n  // Move the left layers\n  for (let i = 0; i < curLayerIndex - 1; i++) {\n    let curX = nodeCoordinate[0][0].x + i * (nodeLength + leftGap);\n    moveLayerX({layerIndex: i, targetX: curX, disable: true, delay: 0});\n  }\n\n  // Add an overlay\n  let stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: 1},\n    {offset: '50%', color: 'rgb(250, 250, 250)', opacity: 0.95},\n    {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 0.85}];\n  addOverlayGradient('overlay-gradient-left', stops);\n\n  stops = [{offset: '0%', color: 'rgb(250, 250, 250)', opacity: 0.85},\n    {offset: '50%', color: 'rgb(250, 250, 250)', opacity: 0.95},\n    {offset: '100%', color: 'rgb(250, 250, 250)', opacity: 1}];\n  addOverlayGradient('overlay-gradient-right', stops);\n\n  addOverlayRect('overlay-gradient-left', nodeCoordinate[0][0].x - overlayRectOffset / 2,\n    0, leftEnd - nodeCoordinate[0][0].x + overlayRectOffset,\n    height + svgPaddings.top + svgPaddings.bottom);\n  \n  addOverlayRect('overlay-gradient-right', rightStart - overlayRectOffset / 2,\n    0, width - rightStart + overlayRectOffset,\n    height + svgPaddings.top + svgPaddings.bottom);\n  \n  // Draw the intermediate layer\n  let {intermediateLayer, intermediateMinMax, kernelRange, kernelMinMax} =\n    drawIntermediateLayer(curLayerIndex, leftX,\n      nodeCoordinate[curLayerIndex][0].x, rightStart, intermediateGap,\n      d, i, intermediateNodeMouseOverHandler,\n      intermediateNodeMouseLeaveHandler, intermediateNodeClicked);\n  addUnderneathRect(curLayerIndex, i, leftX, intermediateGap, 5,\n    intermediateNodeMouseOverHandler, intermediateNodeMouseLeaveHandler,\n    intermediateNodeClicked);\n          \n  // After getting the intermediateMinMax, we can finally aggregate it with\n  // the preLayer minmax, curLayer minmax\n  let finalMinMax = {\n    min: Math.min(minMax.min, intermediateMinMax.min),\n    max: Math.max(minMax.max, intermediateMinMax.max)\n  }\n\n  // Add annotation to the intermediate layer\n  let intermediateLayerAnnotation = svg.append('g')\n    .attr('class', 'intermediate-layer-annotation')\n    .style('opacity', 0);\n\n  drawIntermediateLayerAnnotation({\n    leftX: leftX,\n    curLayerIndex: curLayerIndex,\n    group: intermediateLayerAnnotation,\n    intermediateGap: intermediateGap,\n    i: i\n  });\n\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: range,\n    group: intermediateLayer,\n    minMax: finalMinMax,\n    width: 2 * nodeLength + intermediateGap,\n    x: leftX,\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10\n  });\n\n  drawIntermediateLayerLegend({\n    legendHeight: 5,\n    curLayerIndex: curLayerIndex,\n    range: kernelRange,\n    minMax: kernelMinMax,\n    group: intermediateLayer,\n    width: 2 * nodeLength + intermediateGap,\n    x: targetX + nodeLength - (2 * nodeLength + intermediateGap),\n    y: svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap + \n      nodeLength * 10,\n    gradientAppendingName: 'kernelColorGradient',\n    colorScale: layerColorScales.weight,\n    gradientGap: 0.2\n  });\n\n  // Show everything\n  svg.selectAll('g.intermediate-layer, g.intermediate-layer-annotation')\n    .transition()\n    .delay(500)\n    .duration(500)\n    .ease(d3.easeCubicInOut)\n    .style('opacity', 1);\n}\n"
  },
  {
    "path": "src/overview/intermediate-utils.js",
    "content": "/* global d3 */\n\nimport { svgStore, vSpaceAroundGapStore } from '../stores.js';\nimport { overviewConfig } from '../config.js';\n\n// Configs\nconst layerColorScales = overviewConfig.layerColorScales;\nconst nodeLength = overviewConfig.nodeLength;\nconst intermediateColor = overviewConfig.intermediateColor;\nconst svgPaddings = overviewConfig.svgPaddings;\n\n// Shared variables\nlet svg = undefined;\nsvgStore.subscribe( value => {svg = value;} )\n\nlet vSpaceAroundGap = undefined;\nvSpaceAroundGapStore.subscribe( value => {vSpaceAroundGap = value;} )\n\n/**\n * Move one layer horizontally\n * @param {object} arg Multiple arguments {\n *   layerIndex: current layer index\n *   targetX: destination x\n *   disable: make this layer unresponsible\n *   delay: animation delay\n *   opacity: change the current layer's opacity\n *   specialIndex: avoid manipulating `specialIndex`th node\n *   onEndFunc: call this function when animation finishes\n *   transitionName: animation ID\n * }\n */\nexport const moveLayerX = (arg) => {\n  let layerIndex = arg.layerIndex;\n  let targetX = arg.targetX;\n  let disable = arg.disable;\n  let delay = arg.delay;\n  let opacity = arg.opacity;\n  let specialIndex = arg.specialIndex;\n  let onEndFunc = arg.onEndFunc;\n  let transitionName = arg.transitionName === undefined ? 'move' : arg.transitionName;\n  let duration = arg.duration === undefined ? 500 : arg.duration;\n\n  // Move the selected layer\n  let curLayer = svg.select(`g#cnn-layer-group-${layerIndex}`);\n  curLayer.selectAll('g.node-group').each((d, i, g) => {\n    d3.select(g[i])\n      .style('cursor', disable && i !== specialIndex ? 'default' : 'pointer')\n      .style('pointer-events', disable && i !== specialIndex ? 'none' : 'all')\n      .select('image')\n      .transition(transitionName)\n      .ease(d3.easeCubicInOut)\n      .delay(delay)\n      .duration(duration)\n      .attr('x', targetX);\n    \n    d3.select(g[i])\n      .select('rect.bounding')\n      .transition(transitionName)\n      .ease(d3.easeCubicInOut)\n      .delay(delay)\n      .duration(duration)\n      .attr('x', targetX);\n    \n    if (opacity !== undefined && i !== specialIndex) {\n      d3.select(g[i])\n        .select('image')\n        .style('opacity', opacity);\n    }\n  });\n  \n  // Also move the layer labels\n  svg.selectAll(`g#layer-label-${layerIndex}`)\n    .transition(transitionName)\n    .ease(d3.easeCubicInOut)\n    .delay(delay)\n    .duration(duration)\n    .attr('transform', () => {\n      let x = targetX + nodeLength / 2;\n      let y = (svgPaddings.top + vSpaceAroundGap) / 2 + 5;\n      return `translate(${x}, ${y})`;\n    })\n    .on('end', onEndFunc);\n\n  svg.selectAll(`g#layer-detailed-label-${layerIndex}`)\n    .transition(transitionName)\n    .ease(d3.easeCubicInOut)\n    .delay(delay)\n    .duration(duration)\n    .attr('transform', () => {\n      let x = targetX + nodeLength / 2;\n      let y = (svgPaddings.top + vSpaceAroundGap) / 2 - 6;\n      return `translate(${x}, ${y})`;\n    })\n    .on('end', onEndFunc);\n}\n\n/**\n * Append a gradient definition to `group`\n * @param {string} gradientID CSS ID for the gradient def\n * @param {[{offset: number, color: string, opacity: number}]} stops Gradient stops\n * @param {element} group Element to append def to\n */\nexport const addOverlayGradient = (gradientID, stops, group) => {\n  if (group === undefined) {\n    group = svg;\n  }\n\n  // Create a gradient\n  let defs = group.append(\"defs\")\n    .attr('class', 'overlay-gradient');\n\n  let gradient = defs.append(\"linearGradient\")\n    .attr(\"id\", gradientID)\n    .attr(\"x1\", \"0%\")\n    .attr(\"x2\", \"100%\")\n    .attr(\"y1\", \"100%\")\n    .attr(\"y2\", \"100%\");\n  \n  stops.forEach(s => {\n    gradient.append('stop')\n      .attr('offset', s.offset)\n      .attr('stop-color', s.color)\n      .attr('stop-opacity', s.opacity);\n  })\n}\n\n/**\n * Draw the legend for intermediate layer\n * @param {object} arg \n * {\n *   legendHeight: height of the legend rectangle\n *   curLayerIndex: the index of selected layer\n *   range: colormap range\n *   group: group to append the legend\n *   minMax: {min: min value, max: max value}\n *   width: width of the legend\n *   x: x position of the legend\n *   y: y position of the legend\n *   isInput: if the legend is for the input layer (special handle black to\n *      white color scale)\n *   colorScale: d3 color scale\n *   gradientAppendingName: name of the appending gradient\n *   gradientGap: gap to make the color lighter\n * }\n */\nexport const drawIntermediateLayerLegend = (arg) => {\n  let legendHeight = arg.legendHeight,\n    curLayerIndex = arg.curLayerIndex,\n    range = arg.range,\n    group = arg.group,\n    minMax = arg.minMax,\n    width = arg.width,\n    x = arg.x,\n    y = arg.y,\n    isInput = arg.isInput,\n    colorScale = arg.colorScale,\n    gradientAppendingName = arg.gradientAppendingName,\n    gradientGap = arg.gradientGap;\n  \n  if (colorScale === undefined) { colorScale = layerColorScales.conv; }\n  if (gradientGap === undefined) { gradientGap = 0; }\n  \n  // Add a legend color gradient\n  let gradientName = 'url(#inputGradient)';\n  let normalizedColor = v => colorScale(v * (1 - 2 * gradientGap) + gradientGap);\n\n  if (!isInput) {\n    let leftValue = (minMax.min + range / 2) / range,\n      zeroValue = (0 + range / 2) / range,\n      rightValue = (minMax.max + range / 2) / range,\n      totalRange = minMax.max - minMax.min,\n      zeroLocation = (0 - minMax.min) / totalRange,\n      leftMidValue = leftValue + (zeroValue - leftValue)/2,\n      rightMidValue = zeroValue + (rightValue - zeroValue)/2;\n\n    let stops = [\n      {offset: 0, color: normalizedColor(leftValue), opacity: 1},\n      {offset: zeroLocation / 2,\n        color: normalizedColor(leftMidValue),\n        opacity: 1},\n      {offset: zeroLocation,\n        color: normalizedColor(zeroValue),\n        opacity: 1},\n      {offset: zeroLocation + (1 - zeroValue) / 2,\n        color: normalizedColor(rightMidValue),\n        opacity: 1},\n      {offset: 1, color: normalizedColor(rightValue), opacity: 1}\n    ];\n\n    if (gradientAppendingName === undefined) {\n      addOverlayGradient('intermediate-legend-gradient', stops, group);\n      gradientName = 'url(#intermediate-legend-gradient)';\n    } else {\n      addOverlayGradient(`${gradientAppendingName}`, stops, group);\n      gradientName = `url(#${gradientAppendingName})`;\n    }\n  }\n\n  let legendScale = d3.scaleLinear()\n    .range([0, width - 1.2])\n    .domain(isInput ? [0, range] : [minMax.min, minMax.max]);\n\n  let legendAxis = d3.axisBottom()\n    .scale(legendScale)\n    .tickFormat(d3.format(isInput ? 'd' : '.2f'))\n    .tickValues(isInput ? [0, range] : [minMax.min, 0, minMax.max]);\n  \n  let intermediateLegend = group.append('g')\n    .attr('class', `intermediate-legend-${curLayerIndex - 1}`)\n    .attr('transform', `translate(${x}, ${y})`);\n  \n  let legendGroup = intermediateLegend.append('g')\n    .attr('transform', `translate(0, ${legendHeight - 3})`)\n    .call(legendAxis);\n  \n  legendGroup.selectAll('text')\n    .style('font-size', '9px')\n    .style('fill', intermediateColor);\n  \n  legendGroup.selectAll('path, line')\n    .style('stroke', intermediateColor);\n\n  intermediateLegend.append('rect')\n    .attr('width', width)\n    .attr('height', legendHeight)\n    .attr('transform', `rotate(${isInput ? 180 : 0},\n      ${width / 2}, ${legendHeight / 2})`)\n    .style('fill', gradientName);\n}\n\n/**\n * Draw an very neat arrow!\n * @param {object} arg \n * {\n *   group: element to append this arrow to\n *   sx: source x\n *   sy: source y\n *   tx: target x\n *   ty: target y\n *   dr: radius of curve (I'm using a circle)\n *   hFlip: the direction to choose the circle (there are always two ways)\n * }\n */\nexport const drawArrow = (arg) => {\n  let group = arg.group,\n    sx = arg.sx,\n    sy = arg.sy,\n    tx = arg.tx,\n    ty = arg.ty,\n    dr = arg.dr,\n    hFlip = arg.hFlip,\n    marker = arg.marker === undefined ? 'marker' : arg.marker;\n\n  /* Cool graphics trick -> merge translate and scale together\n  translateX = (1 - scaleX) * tx,\n  translateY = (1 - scaleY) * ty;\n  */\n  \n  let arrow = group.append('g')\n    .attr('class', 'arrow-group');\n\n  arrow.append('path')\n    .attr(\"d\", `M${sx},${sy}A${dr},${dr} 0 0,${hFlip ? 0 : 1} ${tx},${ty}`)\n    .attr('marker-end', `url(#${marker})`)\n    .style('stroke', 'gray')\n    .style('fill', 'none');\n}\n"
  },
  {
    "path": "src/overview/overview-draw.js",
    "content": "/* global d3, SmoothScroll */\n\nimport {\n  svgStore, vSpaceAroundGapStore, hSpaceAroundGapStore, cnnStore,\n  nodeCoordinateStore, selectedScaleLevelStore, cnnLayerRangesStore,\n  detailedModeStore, cnnLayerMinMaxStore, hoverInfoStore\n} from '../stores.js';\nimport {\n  getExtent, getLinkData\n} from './draw-utils.js';\nimport { overviewConfig } from '../config.js';\n\n// Configs\nconst layerColorScales = overviewConfig.layerColorScales;\nconst nodeLength = overviewConfig.nodeLength;\nconst numLayers = overviewConfig.numLayers;\nconst edgeOpacity = overviewConfig.edgeOpacity;\nconst edgeInitColor = overviewConfig.edgeInitColor;\nconst edgeStrokeWidth = overviewConfig.edgeStrokeWidth;\nconst svgPaddings = overviewConfig.svgPaddings;\nconst gapRatio = overviewConfig.gapRatio;\nconst classLists = overviewConfig.classLists;\nconst formater = d3.format('.4f');\n\n// Shared variables\nlet svg = undefined;\nsvgStore.subscribe( value => {svg = value;} )\n\nlet vSpaceAroundGap = undefined;\nvSpaceAroundGapStore.subscribe( value => {vSpaceAroundGap = value;} )\n\nlet hSpaceAroundGap = undefined;\nhSpaceAroundGapStore.subscribe( value => {hSpaceAroundGap = value;} )\n\nlet cnn = undefined;\ncnnStore.subscribe( value => {cnn = value;} )\n\nlet nodeCoordinate = undefined;\nnodeCoordinateStore.subscribe( value => {nodeCoordinate = value;} )\n\nlet selectedScaleLevel = undefined;\nselectedScaleLevelStore.subscribe( value => {selectedScaleLevel = value;} )\n\nlet cnnLayerRanges = undefined;\ncnnLayerRangesStore.subscribe( value => {cnnLayerRanges = value;} )\n\nlet cnnLayerMinMax = undefined;\ncnnLayerMinMaxStore.subscribe( value => {cnnLayerMinMax = value;} )\n\nlet detailedMode = undefined;\ndetailedModeStore.subscribe( value => {detailedMode = value;} )\n\n/**\n * Use bounded d3 data to draw one canvas\n * @param {object} d d3 data\n * @param {index} i d3 data index\n * @param {[object]} g d3 group\n * @param {number} range color range map (max - min)\n */\nexport const drawOutput = (d, i, g, range) => {\n  let image = g[i];\n  let colorScale = layerColorScales[d.type];\n\n  if (d.type === 'input') {\n    colorScale = colorScale[d.index];\n  }\n\n  // Set up a second convas in order to resize image\n  let imageLength = d.output.length === undefined ? 1 : d.output.length;\n  let bufferCanvas = document.createElement(\"canvas\");\n  let bufferContext = bufferCanvas.getContext(\"2d\");\n  bufferCanvas.width = imageLength;\n  bufferCanvas.height = imageLength;\n\n  // Fill image pixel array\n  let imageSingle = bufferContext.getImageData(0, 0, imageLength, imageLength);\n  let imageSingleArray = imageSingle.data;\n\n  if (imageLength === 1) {\n    imageSingleArray[0] = d.output;\n  } else {\n    for (let i = 0; i < imageSingleArray.length; i+=4) {\n      let pixeIndex = Math.floor(i / 4);\n      let row = Math.floor(pixeIndex / imageLength);\n      let column = pixeIndex % imageLength;\n      let color = undefined;\n      if (d.type === 'input' || d.type === 'fc' ) {\n        color = d3.rgb(colorScale(1 - d.output[row][column]))\n      } else {\n        color = d3.rgb(colorScale((d.output[row][column] + range / 2) / range));\n      }\n\n      imageSingleArray[i] = color.r;\n      imageSingleArray[i + 1] = color.g;\n      imageSingleArray[i + 2] = color.b;\n      imageSingleArray[i + 3] = 255;\n    }\n  }\n\n  // canvas.toDataURL() only exports image in 96 DPI, so we can hack it to have\n  // higher DPI by rescaling the image using canvas magic\n  let largeCanvas = document.createElement('canvas');\n  largeCanvas.width = nodeLength * 3;\n  largeCanvas.height = nodeLength * 3;\n  let largeCanvasContext = largeCanvas.getContext('2d');\n\n  // Use drawImage to resize the original pixel array, and put the new image\n  // (canvas) into corresponding canvas\n  bufferContext.putImageData(imageSingle, 0, 0);\n  largeCanvasContext.drawImage(bufferCanvas, 0, 0, imageLength, imageLength,\n    0, 0, nodeLength * 3, nodeLength * 3);\n  \n  let imageDataURL = largeCanvas.toDataURL();\n  d3.select(image).attr('xlink:href', imageDataURL);\n\n  // Destory the buffer canvas\n  bufferCanvas.remove();\n  largeCanvas.remove();\n}\n\n/**\n * Draw bar chart to encode the output value\n * @param {object} d d3 data\n * @param {index} i d3 data index\n * @param {[object]} g d3 group\n * @param {function} scale map value to length\n */\nconst drawOutputScore = (d, i, g, scale) => {\n  let group = d3.select(g[i]);\n  group.select('rect.output-rect')\n    .transition('output')\n    .delay(500)\n    .duration(800)\n    .ease(d3.easeCubicIn)\n    .attr('width', scale(d.output))\n}\n\nexport const drawCustomImage = (image, inputLayer) => {\n\n  let imageWidth = image.width;\n  // Set up a second convas in order to resize image\n  let imageLength = inputLayer[0].output.length;\n  let bufferCanvas = document.createElement(\"canvas\");\n  let bufferContext = bufferCanvas.getContext(\"2d\");\n  bufferCanvas.width = imageLength;\n  bufferCanvas.height = imageLength;\n\n  // Fill image pixel array\n  let imageSingle = bufferContext.getImageData(0, 0, imageLength, imageLength);\n  let imageSingleArray = imageSingle.data;\n\n  for (let i = 0; i < imageSingleArray.length; i+=4) {\n    let pixeIndex = Math.floor(i / 4);\n    let row = Math.floor(pixeIndex / imageLength);\n    let column = pixeIndex % imageLength;\n\n    let red = inputLayer[0].output[row][column];\n    let green = inputLayer[1].output[row][column];\n    let blue = inputLayer[2].output[row][column];\n\n    imageSingleArray[i] = red * 255;\n    imageSingleArray[i + 1] = green * 255;\n    imageSingleArray[i + 2] = blue * 255;\n    imageSingleArray[i + 3] = 255;\n  }\n\n  // canvas.toDataURL() only exports image in 96 DPI, so we can hack it to have\n  // higher DPI by rescaling the image using canvas magic\n  let largeCanvas = document.createElement('canvas');\n  largeCanvas.width = imageWidth * 3;\n  largeCanvas.height = imageWidth * 3;\n  let largeCanvasContext = largeCanvas.getContext('2d');\n\n  // Use drawImage to resize the original pixel array, and put the new image\n  // (canvas) into corresponding canvas\n  bufferContext.putImageData(imageSingle, 0, 0);\n  largeCanvasContext.drawImage(bufferCanvas, 0, 0, imageLength, imageLength,\n    0, 0, imageWidth * 3, imageWidth * 3);\n  \n  let imageDataURL = largeCanvas.toDataURL();\n  // d3.select(image).attr('xlink:href', imageDataURL);\n  image.src = imageDataURL;\n\n  // Destory the buffer canvas\n  bufferCanvas.remove();\n  largeCanvas.remove();\n}\n\n/**\n * Create color gradient for the legend\n * @param {[object]} g d3 group\n * @param {function} colorScale Colormap\n * @param {string} gradientName Label for gradient def\n * @param {number} min Min of legend value\n * @param {number} max Max of legend value\n */\nconst getLegendGradient = (g, colorScale, gradientName, min, max) => {\n  if (min === undefined) { min = 0; }\n  if (max === undefined) { max = 1; }\n  let gradient = g.append('defs')\n    .append('svg:linearGradient')\n    .attr('id', `${gradientName}`)\n    .attr('x1', '0%')\n    .attr('y1', '100%')\n    .attr('x2', '100%')\n    .attr('y2', '100%')\n    .attr('spreadMethod', 'pad');\n  let interpolation = 10\n  for (let i = 0; i < interpolation; i++) {\n    let curProgress = i / (interpolation - 1);\n    let curColor = colorScale(curProgress * (max - min) + min);\n    gradient.append('stop')\n      .attr('offset', `${curProgress * 100}%`)\n      .attr('stop-color', curColor)\n      .attr('stop-opacity', 1);\n  }\n}\n\n/**\n * Draw all legends\n * @param {object} legends Parent group\n * @param {number} legendHeight Height of the legend element\n */\nconst drawLegends = (legends, legendHeight) => {\n  // Add local legends\n  for (let i = 0; i < 2; i++){\n    let start = 1 + i * 5;\n    let range1 = cnnLayerRanges.local[start];\n    let range2 = cnnLayerRanges.local[start + 2];\n\n    let localLegendScale1 = d3.scaleLinear()\n      .range([0, 2 * nodeLength + hSpaceAroundGap - 1.2])\n      .domain([-range1 / 2, range1 / 2]);\n    \n    let localLegendScale2 = d3.scaleLinear()\n      .range([0, 3 * nodeLength + 2 * hSpaceAroundGap - 1.2])\n      .domain([-range2 / 2, range2 / 2]);\n\n    let localLegendAxis1 = d3.axisBottom()\n      .scale(localLegendScale1)\n      .tickFormat(d3.format('.2f'))\n      .tickValues([-range1 / 2, 0, range1 / 2]);\n    \n    let localLegendAxis2 = d3.axisBottom()\n      .scale(localLegendScale2)\n      .tickFormat(d3.format('.2f'))\n      .tickValues([-range2 / 2, 0, range2 / 2]);\n\n    let localLegend1 = legends.append('g')\n      .attr('class', 'legend local-legend')\n      .attr('id', `local-legend-${i}-1`)\n      .classed('hidden', !detailedMode || selectedScaleLevel !== 'local')\n      .attr('transform', `translate(${nodeCoordinate[start][0].x}, ${0})`);\n\n    localLegend1.append('g')\n      .attr('transform', `translate(0, ${legendHeight - 3})`)\n      .call(localLegendAxis1)\n\n    localLegend1.append('rect')\n      .attr('width', 2 * nodeLength + hSpaceAroundGap)\n      .attr('height', legendHeight)\n      .style('fill', 'url(#convGradient)');\n\n    let localLegend2 = legends.append('g')\n      .attr('class', 'legend local-legend')\n      .attr('id', `local-legend-${i}-2`)\n      .classed('hidden', !detailedMode || selectedScaleLevel !== 'local')\n      .attr('transform', `translate(${nodeCoordinate[start + 2][0].x}, ${0})`);\n\n    localLegend2.append('g')\n      .attr('transform', `translate(0, ${legendHeight - 3})`)\n      .call(localLegendAxis2)\n\n    localLegend2.append('rect')\n      .attr('width', 3 * nodeLength + 2 * hSpaceAroundGap)\n      .attr('height', legendHeight)\n      .style('fill', 'url(#convGradient)');\n  }\n\n  // Add module legends\n  for (let i = 0; i < 2; i++){\n    let start = 1 + i * 5;\n    let range = cnnLayerRanges.module[start];\n\n    let moduleLegendScale = d3.scaleLinear()\n      .range([0, 5 * nodeLength + 3 * hSpaceAroundGap +\n        1 * hSpaceAroundGap * gapRatio - 1.2])\n      .domain([-range / 2, range / 2]);\n\n    let moduleLegendAxis = d3.axisBottom()\n      .scale(moduleLegendScale)\n      .tickFormat(d3.format('.2f'))\n      .tickValues([-range / 2, -(range / 4), 0, range / 4, range / 2]);\n\n    let moduleLegend = legends.append('g')\n      .attr('class', 'legend module-legend')\n      .attr('id', `module-legend-${i}`)\n      .classed('hidden', !detailedMode || selectedScaleLevel !== 'module')\n      .attr('transform', `translate(${nodeCoordinate[start][0].x}, ${0})`);\n    \n    moduleLegend.append('g')\n      .attr('transform', `translate(0, ${legendHeight - 3})`)\n      .call(moduleLegendAxis)\n\n    moduleLegend.append('rect')\n      .attr('width', 5 * nodeLength + 3 * hSpaceAroundGap +\n        1 * hSpaceAroundGap * gapRatio)\n      .attr('height', legendHeight)\n      .style('fill', 'url(#convGradient)');\n  }\n\n  // Add global legends\n  let start = 1;\n  let range = cnnLayerRanges.global[start];\n\n  let globalLegendScale = d3.scaleLinear()\n    .range([0, 10 * nodeLength + 6 * hSpaceAroundGap +\n      3 * hSpaceAroundGap * gapRatio - 1.2])\n    .domain([-range / 2, range / 2]);\n\n  let globalLegendAxis = d3.axisBottom()\n    .scale(globalLegendScale)\n    .tickFormat(d3.format('.2f'))\n    .tickValues([-range / 2, -(range / 4), 0, range / 4, range / 2]);\n\n  let globalLegend = legends.append('g')\n    .attr('class', 'legend global-legend')\n    .attr('id', 'global-legend')\n    .classed('hidden', !detailedMode || selectedScaleLevel !== 'global')\n    .attr('transform', `translate(${nodeCoordinate[start][0].x}, ${0})`);\n\n  globalLegend.append('g')\n    .attr('transform', `translate(0, ${legendHeight - 3})`)\n    .call(globalLegendAxis)\n\n  globalLegend.append('rect')\n    .attr('width', 10 * nodeLength + 6 * hSpaceAroundGap +\n      3 * hSpaceAroundGap * gapRatio)\n    .attr('height', legendHeight)\n    .style('fill', 'url(#convGradient)');\n\n\n  // Add output legend\n  let outputRectScale = d3.scaleLinear()\n        .domain(cnnLayerRanges.output)\n        .range([0, nodeLength - 1.2]);\n\n  let outputLegendAxis = d3.axisBottom()\n    .scale(outputRectScale)\n    .tickFormat(d3.format('.1f'))\n    .tickValues([0, cnnLayerRanges.output[1]])\n  \n  let outputLegend = legends.append('g')\n    .attr('class', 'legend output-legend')\n    .attr('id', 'output-legend')\n    .classed('hidden', !detailedMode)\n    .attr('transform', `translate(${nodeCoordinate[11][0].x}, ${0})`);\n  \n  outputLegend.append('g')\n    .attr('transform', `translate(0, ${legendHeight - 3})`)\n    .call(outputLegendAxis);\n\n  outputLegend.append('rect')\n    .attr('width', nodeLength)\n    .attr('height', legendHeight)\n    .style('fill', 'gray');\n  \n  // Add input image legend\n  let inputScale = d3.scaleLinear()\n    .range([0, nodeLength - 1.2])\n    .domain([0, 1]);\n\n  let inputLegendAxis = d3.axisBottom()\n    .scale(inputScale)\n    .tickFormat(d3.format('.1f'))\n    .tickValues([0, 0.5, 1]);\n\n  let inputLegend = legends.append('g')\n    .attr('class', 'legend input-legend')\n    .classed('hidden', !detailedMode)\n    .attr('transform', `translate(${nodeCoordinate[0][0].x}, ${0})`);\n  \n  inputLegend.append('g')\n    .attr('transform', `translate(0, ${legendHeight - 3})`)\n    .call(inputLegendAxis);\n\n  inputLegend.append('rect')\n    .attr('x', 0.3)\n    .attr('width', nodeLength - 0.3)\n    .attr('height', legendHeight)\n    .attr('transform', `rotate(180, ${nodeLength/2}, ${legendHeight/2})`)\n    .style('stroke', 'rgb(20, 20, 20)')\n    .style('stroke-width', 0.3)\n    .style('fill', 'url(#inputGradient)');\n}\n\n/**\n * Draw the overview\n * @param {number} width Width of the cnn group\n * @param {number} height Height of the cnn group\n * @param {object} cnnGroup Group to appen cnn elements to\n * @param {function} nodeMouseOverHandler Callback func for mouseOver\n * @param {function} nodeMouseLeaveHandler Callback func for mouseLeave\n * @param {function} nodeClickHandler Callback func for click\n */\nexport const drawCNN = (width, height, cnnGroup, nodeMouseOverHandler,\n  nodeMouseLeaveHandler, nodeClickHandler) => {\n  // Draw the CNN\n  // There are 8 short gaps and 5 long gaps\n  hSpaceAroundGap = (width - nodeLength * numLayers) / (8 + 5 * gapRatio);\n  hSpaceAroundGapStore.set(hSpaceAroundGap);\n  let leftAccuumulatedSpace = 0;\n\n  // Iterate through the cnn to draw nodes in each layer\n  for (let l = 0; l < cnn.length; l++) {\n    let curLayer = cnn[l];\n    let isOutput = curLayer[0].layerName === 'output';\n\n    nodeCoordinate.push([]);\n\n    // Compute the x coordinate of the whole layer\n    // Output layer and conv layer has long gaps\n    if (isOutput || curLayer[0].type === 'conv') {\n      leftAccuumulatedSpace += hSpaceAroundGap * gapRatio;\n    } else {\n      leftAccuumulatedSpace += hSpaceAroundGap;\n    }\n\n    // All nodes share the same x coordiante (left in div style)\n    let left = leftAccuumulatedSpace;\n\n    let layerGroup = cnnGroup.append('g')\n      .attr('class', 'cnn-layer-group')\n      .attr('id', `cnn-layer-group-${l}`);\n\n    vSpaceAroundGap = (height - nodeLength * curLayer.length) /\n      (curLayer.length + 1);\n    vSpaceAroundGapStore.set(vSpaceAroundGap);\n\n    let nodeGroups = layerGroup.selectAll('g.node-group')\n      .data(curLayer, d => d.index)\n      .enter()\n      .append('g')\n      .attr('class', 'node-group')\n      .style('cursor', 'pointer')\n      .style('pointer-events', 'all')\n      .on('click', nodeClickHandler)\n      .on('mouseover', nodeMouseOverHandler)\n      .on('mouseleave', nodeMouseLeaveHandler)\n      .classed('node-output', isOutput)\n      .attr('id', (d, i) => {\n        // Compute the coordinate\n        // Not using transform on the group object because of a decade old\n        // bug on webkit (safari)\n        // https://bugs.webkit.org/show_bug.cgi?id=23113\n        let top = i * nodeLength + (i + 1) * vSpaceAroundGap;\n        top += svgPaddings.top;\n        nodeCoordinate[l].push({x: left, y: top});\n        return `layer-${l}-node-${i}`\n      });\n    \n    // Overwrite the mouseover and mouseleave function for output nodes to show\n    // hover info in the UI\n    layerGroup.selectAll('g.node-output')\n      .on('mouseover', (d, i, g) => {\n        nodeMouseOverHandler(d, i, g);\n        hoverInfoStore.set( {show: true, text: `Output value: ${formater(d.output)}`} );\n      })\n      .on('mouseleave', (d, i, g) => {\n        nodeMouseLeaveHandler(d, i, g);\n        hoverInfoStore.set( {show: false, text: `Output value: ${formater(d.output)}`} );\n      });\n    \n    if (curLayer[0].layerName !== 'output') {\n      // Embed raster image in these groups\n      nodeGroups.append('image')\n        .attr('class', 'node-image')\n        .attr('width', nodeLength)\n        .attr('height', nodeLength)\n        .attr('x', left)\n        .attr('y', (d, i) => nodeCoordinate[l][i].y);\n      \n      // Add a rectangle to show the border\n      nodeGroups.append('rect')\n        .attr('class', 'bounding')\n        .attr('width', nodeLength)\n        .attr('height', nodeLength)\n        .attr('x', left)\n        .attr('y', (d, i) => nodeCoordinate[l][i].y)\n        .style('fill', 'none')\n        .style('stroke', 'gray')\n        .style('stroke-width', 1)\n        .classed('hidden', true);\n    } else {\n      nodeGroups.append('rect')\n        .attr('class', 'output-rect')\n        .attr('x', left)\n        .attr('y', (d, i) => nodeCoordinate[l][i].y + nodeLength / 2 + 8)\n        .attr('height', nodeLength / 4)\n        .attr('width', 0)\n        .style('fill', 'gray');\n      nodeGroups.append('text')\n        .attr('class', 'output-text')\n        .attr('x', left)\n        .attr('y', (d, i) => nodeCoordinate[l][i].y + nodeLength / 2)\n        .style('dominant-baseline', 'middle')\n        .style('font-size', '11px')\n        .style('fill', 'black')\n        .style('opacity', 0.5)\n        .text((d, i) => classLists[i]);\n      \n      // Add annotation text to tell readers the exact output probability\n      // nodeGroups.append('text')\n      //   .attr('class', 'annotation-text')\n      //   .attr('id', (d, i) => `output-prob-${i}`)\n      //   .attr('x', left)\n      //   .attr('y', (d, i) => nodeCoordinate[l][i].y + 10)\n      //   .text(d => `(${d3.format('.4f')(d.output)})`);\n    }\n    leftAccuumulatedSpace += nodeLength;\n  }\n\n  // Share the nodeCoordinate\n  nodeCoordinateStore.set(nodeCoordinate)\n\n  // Compute the scale of the output score width (mapping the the node\n  // width to the max output score)\n  let outputRectScale = d3.scaleLinear()\n        .domain(cnnLayerRanges.output)\n        .range([0, nodeLength]);\n\n  // Draw the canvas\n  for (let l = 0; l < cnn.length; l++) {\n    let range = cnnLayerRanges[selectedScaleLevel][l];\n    svg.select(`g#cnn-layer-group-${l}`)\n      .selectAll('image.node-image')\n      .each((d, i, g) => drawOutput(d, i, g, range));\n  }\n\n  svg.selectAll('g.node-output').each(\n    (d, i, g) => drawOutputScore(d, i, g, outputRectScale)\n  );\n\n  // Add layer label\n  let layerNames = cnn.map(d => {\n    if (d[0].layerName === 'output') {\n      return {\n        name: d[0].layerName,\n        dimension: `(${d.length})`\n      }\n    } else {\n      return {\n        name: d[0].layerName,\n        dimension: `(${d[0].output.length}, ${d[0].output.length}, ${d.length})`\n      }\n    }\n  });\n\n  let svgHeight = Number(d3.select('#cnn-svg').style('height').replace('px', '')) + 150;\n  let scroll = new SmoothScroll('a[href*=\"#\"]', {offset: -svgHeight});\n  \n  let detailedLabels = svg.selectAll('g.layer-detailed-label')\n    .data(layerNames)\n    .enter()\n    .append('g')\n    .attr('class', 'layer-detailed-label')\n    .attr('id', (d, i) => `layer-detailed-label-${i}`)\n    .classed('hidden', !detailedMode)\n    .attr('transform', (d, i) => {\n      let x = nodeCoordinate[i][0].x + nodeLength / 2;\n      let y = (svgPaddings.top + vSpaceAroundGap) / 2 - 6;\n      return `translate(${x}, ${y})`;\n    })\n    .style('cursor', d => d.name.includes('output') ? 'default' : 'help')\n    .on('click', (d) => {\n      let target = '';\n      if (d.name.includes('conv')) { target = 'convolution' }\n      if (d.name.includes('relu')) { target = 'relu' }\n      if (d.name.includes('max_pool')) { target = 'pooling'}\n      if (d.name.includes('input')) { target = 'input'}\n\n      // Scroll to a article element\n      let anchor = document.querySelector(`#article-${target}`);\n      scroll.animateScroll(anchor);\n    });\n  \n  detailedLabels.append('title')\n    .text('Move to article section');\n    \n  detailedLabels.append('text')\n    .style('opacity', 0.7)\n    .style('dominant-baseline', 'middle')\n    .append('tspan')\n    .style('font-size', '12px')\n    .text(d => d.name)\n    .append('tspan')\n    .style('font-size', '8px')\n    .style('font-weight', 'normal')\n    .attr('x', 0)\n    .attr('dy', '1.5em')\n    .text(d => d.dimension);\n  \n  let labels = svg.selectAll('g.layer-label')\n    .data(layerNames)\n    .enter()\n    .append('g')\n    .attr('class', 'layer-label')\n    .attr('id', (d, i) => `layer-label-${i}`)\n    .classed('hidden', detailedMode)\n    .attr('transform', (d, i) => {\n      let x = nodeCoordinate[i][0].x + nodeLength / 2;\n      let y = (svgPaddings.top + vSpaceAroundGap) / 2 + 5;\n      return `translate(${x}, ${y})`;\n    })\n    .style('cursor', d => d.name.includes('output') ? 'default' : 'help')\n    .on('click', (d) => {\n      let target = '';\n      if (d.name.includes('conv')) { target = 'convolution' }\n      if (d.name.includes('relu')) { target = 'relu' }\n      if (d.name.includes('max_pool')) { target = 'pooling'}\n      if (d.name.includes('input')) { target = 'input'}\n\n      // Scroll to a article element\n      let anchor = document.querySelector(`#article-${target}`);\n      scroll.animateScroll(anchor);\n    });\n  \n  labels.append('title')\n    .text('Move to article section');\n  \n  labels.append('text')\n    .style('dominant-baseline', 'middle')\n    .style('opacity', 0.8)\n    .text(d => {\n      if (d.name.includes('conv')) { return 'conv' }\n      if (d.name.includes('relu')) { return 'relu' }\n      if (d.name.includes('max_pool')) { return 'max_pool'}\n      return d.name\n    });\n\n  // Add layer color scale legends\n  getLegendGradient(svg, layerColorScales.conv, 'convGradient');\n  getLegendGradient(svg, layerColorScales.input[0], 'inputGradient');\n\n  let legendHeight = 5;\n  let legends = svg.append('g')\n      .attr('class', 'color-legend')\n      .attr('transform', `translate(${0}, ${\n        svgPaddings.top + vSpaceAroundGap * (10) + vSpaceAroundGap +\n        nodeLength * 10\n      })`);\n  \n  drawLegends(legends, legendHeight);\n\n  // Add edges between nodes\n  let linkGen = d3.linkHorizontal()\n    .x(d => d.x)\n    .y(d => d.y);\n  \n  let linkData = getLinkData(nodeCoordinate, cnn);\n\n  let edgeGroup = cnnGroup.append('g')\n    .attr('class', 'edge-group');\n  \n  edgeGroup.selectAll('path.edge')\n    .data(linkData)\n    .enter()\n    .append('path')\n    .attr('class', d =>\n      `edge edge-${d.targetLayerIndex} edge-${d.targetLayerIndex}-${d.targetNodeIndex}`)\n    .attr('id', d => \n      `edge-${d.targetLayerIndex}-${d.targetNodeIndex}-${d.sourceNodeIndex}`)\n    .attr('d', d => linkGen({source: d.source, target: d.target}))\n    .style('fill', 'none')\n    .style('stroke-width', edgeStrokeWidth)\n    .style('opacity', edgeOpacity)\n    .style('stroke', edgeInitColor);\n\n  // Add input channel annotations\n  let inputAnnotation = cnnGroup.append('g')\n    .attr('class', 'input-annotation');\n\n  let redChannel = inputAnnotation.append('text')\n    .attr('x', nodeCoordinate[0][0].x + nodeLength / 2)\n    .attr('y', nodeCoordinate[0][0].y + nodeLength + 5)\n    .attr('class', 'annotation-text')\n    .style('dominant-baseline', 'hanging')\n    .style('text-anchor', 'middle');\n  \n  redChannel.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .style('fill', '#C95E67')\n    .text('Red');\n  \n  redChannel.append('tspan')\n    .style('dominant-baseline', 'hanging')\n    .text(' channel');\n\n  inputAnnotation.append('text')\n    .attr('x', nodeCoordinate[0][1].x + nodeLength / 2)\n    .attr('y', nodeCoordinate[0][1].y + nodeLength + 5)\n    .attr('class', 'annotation-text')\n    .style('dominant-baseline', 'hanging')\n    .style('text-anchor', 'middle')\n    .style('fill', '#3DB665')\n    .text('Green');\n\n  inputAnnotation.append('text')\n    .attr('x', nodeCoordinate[0][2].x + nodeLength / 2)\n    .attr('y', nodeCoordinate[0][2].y + nodeLength + 5)\n    .attr('class', 'annotation-text')\n    .style('dominant-baseline', 'hanging')\n    .style('text-anchor', 'middle')\n    .style('fill', '#3F7FBC')\n    .text('Blue');\n}\n\n/**\n * Update canvas values when user changes input image\n */\nexport const updateCNN = () => {\n  // Compute the scale of the output score width (mapping the the node\n  // width to the max output score)\n  let outputRectScale = d3.scaleLinear()\n      .domain(cnnLayerRanges.output)\n      .range([0, nodeLength]);\n\n  // Rebind the cnn data to layer groups layer by layer\n  for (let l = 0; l < cnn.length; l++) {\n    let curLayer = cnn[l];\n    let range = cnnLayerRanges[selectedScaleLevel][l];\n    let layerGroup = svg.select(`g#cnn-layer-group-${l}`);\n\n    let nodeGroups = layerGroup.selectAll('g.node-group')\n      .data(curLayer);\n\n    if (l < cnn.length - 1) {\n      // Redraw the canvas and output node\n      nodeGroups.transition('disappear')\n        .duration(300)\n        .ease(d3.easeCubicOut)\n        .style('opacity', 0)\n        .on('end', function() {\n          d3.select(this)\n            .select('image.node-image')\n            .each((d, i, g) => drawOutput(d, i, g, range));\n          d3.select(this).transition('appear')\n            .duration(700)\n            .ease(d3.easeCubicIn)\n            .style('opacity', 1);\n        });\n    } else {\n      nodeGroups.each(\n        (d, i, g) => drawOutputScore(d, i, g, outputRectScale)\n      );\n    }\n  }\n\n  // Update the color scale legend\n  // Local legends\n  for (let i = 0; i < 2; i++){\n    let start = 1 + i * 5;\n    let range1 = cnnLayerRanges.local[start];\n    let range2 = cnnLayerRanges.local[start + 2];\n\n    let localLegendScale1 = d3.scaleLinear()\n      .range([0, 2 * nodeLength + hSpaceAroundGap])\n      .domain([-range1 / 2, range1 / 2]);\n    \n    let localLegendScale2 = d3.scaleLinear()\n      .range([0, 3 * nodeLength + 2 * hSpaceAroundGap])\n      .domain([-range2 / 2, range2 / 2]);\n\n    let localLegendAxis1 = d3.axisBottom()\n      .scale(localLegendScale1)\n      .tickFormat(d3.format('.2f'))\n      .tickValues([-range1 / 2, 0, range1 / 2]);\n    \n    let localLegendAxis2 = d3.axisBottom()\n      .scale(localLegendScale2)\n      .tickFormat(d3.format('.2f'))\n      .tickValues([-range2 / 2, 0, range2 / 2]);\n    \n    svg.select(`g#local-legend-${i}-1`).select('g').call(localLegendAxis1);\n    svg.select(`g#local-legend-${i}-2`).select('g').call(localLegendAxis2);\n  }\n\n  // Module legend\n  for (let i = 0; i < 2; i++){\n    let start = 1 + i * 5;\n    let range = cnnLayerRanges.local[start];\n\n    let moduleLegendScale = d3.scaleLinear()\n      .range([0, 5 * nodeLength + 3 * hSpaceAroundGap +\n        1 * hSpaceAroundGap * gapRatio - 1.2])\n      .domain([-range, range]);\n\n    let moduleLegendAxis = d3.axisBottom()\n      .scale(moduleLegendScale)\n      .tickFormat(d3.format('.2f'))\n      .tickValues([-range, -(range / 2), 0, range/2, range]);\n    \n    svg.select(`g#module-legend-${i}`).select('g').call(moduleLegendAxis);\n  }\n\n  // Global legend\n  let start = 1;\n  let range = cnnLayerRanges.global[start];\n\n  let globalLegendScale = d3.scaleLinear()\n    .range([0, 10 * nodeLength + 6 * hSpaceAroundGap +\n      3 * hSpaceAroundGap * gapRatio - 1.2])\n    .domain([-range, range]);\n\n  let globalLegendAxis = d3.axisBottom()\n    .scale(globalLegendScale)\n    .tickFormat(d3.format('.2f'))\n    .tickValues([-range, -(range / 2), 0, range/2, range]);\n\n  svg.select(`g#global-legend`).select('g').call(globalLegendAxis);\n\n  // Output legend\n  let outputLegendAxis = d3.axisBottom()\n    .scale(outputRectScale)\n    .tickFormat(d3.format('.1f'))\n    .tickValues([0, cnnLayerRanges.output[1]]);\n  \n  svg.select('g#output-legend').select('g').call(outputLegendAxis);\n}\n\n/**\n * Update the ranges for current CNN layers\n */\nexport const updateCNNLayerRanges = () => {\n  // Iterate through all nodes to find a output ranges for each layer\n  let cnnLayerRangesLocal = [1];\n  let curRange = undefined;\n\n  // Also track the min/max of each layer (avoid computing during intermediate\n  // layer)\n  cnnLayerMinMax = [];\n\n  for (let l = 0; l < cnn.length - 1; l++) {\n    let curLayer = cnn[l];\n\n    // Compute the min max\n    let outputExtents = curLayer.map(l => getExtent(l.output));\n    let aggregatedExtent = outputExtents.reduce((acc, cur) => {\n      return [Math.min(acc[0], cur[0]), Math.max(acc[1], cur[1])];\n    })\n    cnnLayerMinMax.push({min: aggregatedExtent[0], max: aggregatedExtent[1]});\n\n    // conv layer refreshes curRange counting\n    if (curLayer[0].type === 'conv' || curLayer[0].type === 'fc') {\n      aggregatedExtent = aggregatedExtent.map(Math.abs);\n      // Plus 0.1 to offset the rounding error (avoid black color)\n      curRange = 2 * (0.1 + \n        Math.round(Math.max(...aggregatedExtent) * 1000) / 1000);\n    }\n\n    if (curRange !== undefined){\n      cnnLayerRangesLocal.push(curRange);\n    }\n  }\n\n  // Finally, add the output layer range\n  cnnLayerRangesLocal.push(1);\n  cnnLayerMinMax.push({min: 0, max: 1});\n\n  // Support different levels of scales (1) lcoal, (2) component, (3) global\n  let cnnLayerRangesComponent = [1];\n  let numOfComponent = (numLayers - 2) / 5;\n  for (let i = 0; i < numOfComponent; i++) {\n    let curArray = cnnLayerRangesLocal.slice(1 + 5 * i, 1 + 5 * i + 5);\n    let maxRange = Math.max(...curArray);\n    for (let j = 0; j < 5; j++) {\n      cnnLayerRangesComponent.push(maxRange);\n    }\n  }\n  cnnLayerRangesComponent.push(1);\n\n  let cnnLayerRangesGlobal = [1];\n  let maxRange = Math.max(...cnnLayerRangesLocal.slice(1,\n    cnnLayerRangesLocal.length - 1));\n  for (let i = 0; i < numLayers - 2; i++) {\n    cnnLayerRangesGlobal.push(maxRange);\n  }\n  cnnLayerRangesGlobal.push(1);\n\n  // Update the ranges dictionary\n  cnnLayerRanges.local = cnnLayerRangesLocal;\n  cnnLayerRanges.module = cnnLayerRangesComponent;\n  cnnLayerRanges.global = cnnLayerRangesGlobal;\n  cnnLayerRanges.output = [0, d3.max(cnn[cnn.length - 1].map(d => d.output))];\n\n  cnnLayerRangesStore.set(cnnLayerRanges);\n  cnnLayerMinMaxStore.set(cnnLayerMinMax);\n}"
  },
  {
    "path": "src/stores.js",
    "content": "import { writable } from 'svelte/store';\n\nexport const cnnStore = writable([]);\nexport const svgStore = writable(undefined);\n\nexport const vSpaceAroundGapStore = writable(undefined);\nexport const hSpaceAroundGapStore = writable(undefined);\n\nexport const nodeCoordinateStore = writable([]);\nexport const selectedScaleLevelStore = writable(undefined);\n\nexport const cnnLayerRangesStore = writable({});\nexport const cnnLayerMinMaxStore = writable([]);\n\nexport const needRedrawStore = writable([undefined, undefined]);\n\nexport const detailedModeStore = writable(true);\n\nexport const shouldIntermediateAnimateStore = writable(false);\n\nexport const isInSoftmaxStore = writable(false);\nexport const softmaxDetailViewStore = writable({});\nexport const allowsSoftmaxAnimationStore = writable(false);\n\nexport const hoverInfoStore = writable({});\n\nexport const modalStore = writable({});\n\nexport const intermediateLayerPositionStore = writable({});"
  },
  {
    "path": "src/utils/cnn-tf.js",
    "content": "/* global tf */\n\n// Network input image size\nconst networkInputSize = 64;\n\n// Enum of node types\nconst nodeType = {\n  INPUT: 'input',\n  CONV: 'conv',\n  POOL: 'pool',\n  RELU: 'relu',\n  FC: 'fc',\n  FLATTEN: 'flatten'\n}\n\nclass Node {\n  /**\n   * Class structure for each neuron node.\n   * \n   * @param {string} layerName Name of the node's layer.\n   * @param {int} index Index of this node in its layer.\n   * @param {string} type Node type {input, conv, pool, relu, fc}. \n   * @param {number} bias The bias assocated to this node.\n   * @param {number[]} output Output of this node.\n   */\n  constructor(layerName, index, type, bias, output) {\n    this.layerName = layerName;\n    this.index = index;\n    this.type = type;\n    this.bias = bias;\n    this.output = output;\n\n    // Weights are stored in the links\n    this.inputLinks = [];\n    this.outputLinks = [];\n  }\n}\n\nclass Link {\n  /**\n   * Class structure for each link between two nodes.\n   * \n   * @param {Node} source Source node.\n   * @param {Node} dest Target node.\n   * @param {number} weight Weight associated to this link. It can be a number,\n   *  1D array, or 2D array.\n   */\n  constructor(source, dest, weight) {\n    this.source = source;\n    this.dest = dest;\n    this.weight = weight;\n  }\n}\n\n/**\n * Construct a CNN with given extracted outputs from every layer.\n * \n * @param {number[][]} allOutputs Array of outputs for each layer.\n *  allOutputs[i][j] is the output for layer i node j.\n * @param {Model} model Loaded tf.js model.\n * @param {Tensor} inputImageTensor Loaded input image tensor.\n */\nconst constructCNNFromOutputs = (allOutputs, model, inputImageTensor) => {\n  let cnn = [];\n\n  // Add the first layer (input layer)\n  let inputLayer = [];\n  let inputShape = model.layers[0].batchInputShape.slice(1);\n  let inputImageArray = inputImageTensor.transpose([2, 0, 1]).arraySync();\n\n  // First layer's three nodes' outputs are the channels of inputImageArray\n  for (let i = 0; i < inputShape[2]; i++) {\n    let node = new Node('input', i, nodeType.INPUT, 0, inputImageArray[i]);\n    inputLayer.push(node);\n  }\n                                                                                                                   \n  cnn.push(inputLayer);\n  let curLayerIndex = 1;\n\n  for (let l = 0; l < model.layers.length; l++) {\n    let layer = model.layers[l];\n    // Get the current output\n    let outputs = allOutputs[l].squeeze();\n    outputs = outputs.arraySync();\n\n    let curLayerNodes = [];\n    let curLayerType;\n\n    // Identify layer type based on the layer name\n    if (layer.name.includes('conv')) {\n      curLayerType = nodeType.CONV;\n    } else if (layer.name.includes('pool')) {\n      curLayerType = nodeType.POOL;\n    } else if (layer.name.includes('relu')) {\n      curLayerType = nodeType.RELU;\n    } else if (layer.name.includes('output')) {\n      curLayerType = nodeType.FC;\n    } else if (layer.name.includes('flatten')) {\n      curLayerType = nodeType.FLATTEN;\n    } else {\n      console.log('Find unknown type');\n    }\n\n    // Construct this layer based on its layer type\n    switch (curLayerType) {\n      case nodeType.CONV: {\n        let biases = layer.bias.val.arraySync();\n        // The new order is [output_depth, input_depth, height, width]\n        let weights = layer.kernel.val.transpose([3, 2, 0, 1]).arraySync();\n\n        // Add nodes into this layer\n        for (let i = 0; i < outputs.length; i++) {\n          let node = new Node(layer.name, i, curLayerType, biases[i],\n            outputs[i]);\n\n          // Connect this node to all previous nodes (create links)\n          // CONV layers have weights in links. Links are one-to-multiple.\n          for (let j = 0; j < cnn[curLayerIndex - 1].length; j++) {\n            let preNode = cnn[curLayerIndex - 1][j];\n            let curLink = new Link(preNode, node, weights[i][j]);\n            preNode.outputLinks.push(curLink);\n            node.inputLinks.push(curLink);\n          }\n          curLayerNodes.push(node);\n        }\n        break;\n      }\n      case nodeType.FC: {\n        let biases = layer.bias.val.arraySync();\n        // The new order is [output_depth, input_depth]\n        let weights = layer.kernel.val.transpose([1, 0]).arraySync();\n\n        // Add nodes into this layer\n        for (let i = 0; i < outputs.length; i++) {\n          let node = new Node(layer.name, i, curLayerType, biases[i],\n            outputs[i]);\n\n          // Connect this node to all previous nodes (create links)\n          // FC layers have weights in links. Links are one-to-multiple.\n\n          // Since we are visualizing the logit values, we need to track\n          // the raw value before softmax\n          let curLogit = 0;\n          for (let j = 0; j < cnn[curLayerIndex - 1].length; j++) {\n            let preNode = cnn[curLayerIndex - 1][j];\n            let curLink = new Link(preNode, node, weights[i][j]);\n            preNode.outputLinks.push(curLink);\n            node.inputLinks.push(curLink);\n            curLogit += preNode.output * weights[i][j];\n          }\n          curLogit += biases[i];\n          node.logit = curLogit;\n          curLayerNodes.push(node);\n        }\n\n        // Sort flatten layer based on the node TF index\n        cnn[curLayerIndex - 1].sort((a, b) => a.realIndex - b.realIndex);\n        break;\n      }\n      case nodeType.RELU:\n      case nodeType.POOL: {\n        // RELU and POOL have no bias nor weight\n        let bias = 0;\n        let weight = null;\n\n        // Add nodes into this layer\n        for (let i = 0; i < outputs.length; i++) {\n          let node = new Node(layer.name, i, curLayerType, bias, outputs[i]);\n\n          // RELU and POOL layers have no weights. Links are one-to-one\n          let preNode = cnn[curLayerIndex - 1][i];\n          let link = new Link(preNode, node, weight);\n          preNode.outputLinks.push(link);\n          node.inputLinks.push(link);\n\n          curLayerNodes.push(node);\n        }\n        break;\n      }\n      case nodeType.FLATTEN: {\n        // Flatten layer has no bias nor weights.\n        let bias = 0;\n\n        for (let i = 0; i < outputs.length; i++) {\n          // Flatten layer has no weights. Links are multiple-to-one.\n          // Use dummy weights to store the corresponding entry in the previsou\n          // node as (row, column)\n          // The flatten() in tf2.keras has order: channel -> row -> column\n          let preNodeWidth = cnn[curLayerIndex - 1][0].output.length,\n            preNodeNum = cnn[curLayerIndex - 1].length,\n            preNodeIndex = i % preNodeNum,\n            preNodeRow = Math.floor(Math.floor(i / preNodeNum) / preNodeWidth),\n            preNodeCol = Math.floor(i / preNodeNum) % preNodeWidth,\n            // Use channel, row, colume to compute the real index with order\n            // row -> column -> channel\n            curNodeRealIndex = preNodeIndex * (preNodeWidth * preNodeWidth) +\n              preNodeRow * preNodeWidth + preNodeCol;\n          \n          let node = new Node(layer.name, i, curLayerType,\n              bias, outputs[i]);\n          \n          // TF uses the (i) index for computation, but the real order should\n          // be (curNodeRealIndex). We will sort the nodes using the real order\n          // after we compute the logits in the output layer.\n          node.realIndex = curNodeRealIndex;\n\n          let link = new Link(cnn[curLayerIndex - 1][preNodeIndex],\n              node, [preNodeRow, preNodeCol]);\n\n          cnn[curLayerIndex - 1][preNodeIndex].outputLinks.push(link);\n          node.inputLinks.push(link);\n\n          curLayerNodes.push(node);\n        }\n\n        // Sort flatten layer based on the node TF index\n        curLayerNodes.sort((a, b) => a.index - b.index);\n        break;\n      }\n      default:\n        console.error('Encounter unknown layer type');\n        break;\n    }\n\n    // Add current layer to the NN\n    cnn.push(curLayerNodes);\n    curLayerIndex++;\n  }\n\n  return cnn;\n}\n\n/**\n * Construct a CNN with given model and input.\n * \n * @param {string} inputImageFile filename of input image.\n * @param {Model} model Loaded tf.js model.\n */\nexport const constructCNN = async (inputImageFile, model) => {\n  // Load the image file\n  let inputImageTensor = await getInputImageArray(inputImageFile, true);\n\n  // Need to feed the model with a batch\n  let inputImageTensorBatch = tf.stack([inputImageTensor]);\n\n  // To get intermediate layer outputs, we will iterate through all layers in\n  // the model, and sequencially apply transformations.\n  let preTensor = inputImageTensorBatch;\n  let outputs = [];\n\n  // Iterate through all layers, and build one model with that layer as output\n  for (let l = 0; l < model.layers.length; l++) {\n    let curTensor = model.layers[l].apply(preTensor);\n\n    // Record the output tensor\n    // Because there is only one element in the batch, we use squeeze()\n    // We also want to use CHW order here\n    let output = curTensor.squeeze();\n    if (output.shape.length === 3) {\n      output = output.transpose([2, 0, 1]);\n    }\n    outputs.push(output);\n\n    // Update preTensor for next nesting iteration\n    preTensor = curTensor;\n  }\n\n  let cnn = constructCNNFromOutputs(outputs, model, inputImageTensor);\n  return cnn;\n}\n\n// Helper functions\n\n/**\n * Crop the largest central square of size 64x64x3 of a 3d array.\n * \n * @param {[int8]} arr array that requires cropping and padding (if a 64x64 crop\n * is not present)\n * @returns 64x64x3 array\n */\nconst cropCentralSquare = (arr) => {\n  let width = arr.length;\n  let height = arr[0].length;\n  let croppedArray;\n\n  // Crop largest square from image if the image is smaller than 64x64 and pad the\n  // cropped image.\n  if (width < networkInputSize || height < networkInputSize) {\n    // TODO(robert): Finish the padding logic.  Pushing now for Omar to work on when he is ready.\n    let cropDimensions = Math.min(width, height);\n    let startXIdx = Math.floor(width / 2) - (cropDimensions / 2);\n    let startYIdx = Math.floor(height / 2) - (cropDimensions / 2);\n    let unpaddedSubarray = arr.slice(startXIdx, startXIdx + cropDimensions).map(i => i.slice(startYIdx, startYIdx + cropDimensions));\n  } else {\n    let startXIdx = Math.floor(width / 2) - Math.floor(networkInputSize / 2);\n    let startYIdx = Math.floor(height / 2) - Math.floor(networkInputSize / 2);\n    croppedArray = arr.slice(startXIdx, startXIdx + networkInputSize).map(i => i.slice(startYIdx, startYIdx + networkInputSize));\n  }\n  return croppedArray;\n}\n\n/**\n * Convert canvas image data into a 3D tensor with dimension [height, width, 3].\n * Recall that tensorflow uses NHWC order (batch, height, width, channel).\n * Each pixel is in 0-255 scale.\n * \n * @param {[int8]} imageData Canvas image data\n * @param {int} width Canvas image width\n * @param {int} height Canvas image height\n */\nconst imageDataTo3DTensor = (imageData, width, height, normalize=true) => {\n  // Create array placeholder for the 3d array\n  let imageArray = tf.fill([width, height, 3], 0).arraySync();\n\n  // Iterate through the data to fill out channel arrays above\n  for (let i = 0; i < imageData.length; i++) {\n    let pixelIndex = Math.floor(i / 4),\n      channelIndex = i % 4,\n      row = width === height ? Math.floor(pixelIndex / width)\n                              : pixelIndex % width,\n      column = width === height ? pixelIndex % width\n                              : Math.floor(pixelIndex / width);\n    \n    if (channelIndex < 3) {\n      let curEntry  = imageData[i];\n      // Normalize the original pixel value from [0, 255] to [0, 1]\n      if (normalize) {\n        curEntry /= 255;\n      }\n      imageArray[row][column][channelIndex] = curEntry;\n    }\n  }\n\n  // If the image is not 64x64, crop and or pad the image appropriately.\n  if (width != networkInputSize && height != networkInputSize) {\n    imageArray = cropCentralSquare(imageArray)\n  }\n\n  let tensor = tf.tensor3d(imageArray);\n  return tensor;\n}\n\n/**\n * Get the 3D pixel value array of the given image file.\n * \n * @param {string} imgFile File path to the image file\n * @returns A promise with the corresponding 3D array\n */\nconst getInputImageArray = (imgFile, normalize=true) => {\n  let canvas = document.createElement('canvas');\n  canvas.style.cssText = 'display:none;';\n  document.getElementsByTagName('body')[0].appendChild(canvas);\n  let context = canvas.getContext('2d');\n\n  return new Promise((resolve, reject) => {\n    let inputImage = new Image();\n    inputImage.crossOrigin = \"Anonymous\";\n    inputImage.src = imgFile;\n    let canvasImage;\n    inputImage.onload = () => {\n      canvas.width = inputImage.width;\n      canvas.height = inputImage.height;\n      // Resize the input image of the network if it is too large to simply crop\n      // the center 64x64 portion in order to still provide a representative\n      // input image into the network.\n      if (inputImage.width > networkInputSize || inputImage.height > networkInputSize) {\n        // Step 1 - Resize using smaller dimension to scale the image down. \n        let resizeCanvas = document.createElement('canvas'),\n            resizeContext = resizeCanvas.getContext('2d');\n        let smallerDimension = Math.min(inputImage.width, inputImage.height);\n        const resizeFactor = (networkInputSize + 1) / smallerDimension;\n        resizeCanvas.width = inputImage.width * resizeFactor;\n        resizeCanvas.height = inputImage.height * resizeFactor;\n        resizeContext.drawImage(inputImage, 0, 0, resizeCanvas.width,\n          resizeCanvas.height);\n\n        // Step 2 - Flip non-square images horizontally and rotate them 90deg since\n        // non-square images are not stored upright.\n        if (inputImage.width != inputImage.height) {\n          context.translate(resizeCanvas.width, 0);\n          context.scale(-1, 1);\n          context.translate(resizeCanvas.width / 2, resizeCanvas.height / 2);\n          context.rotate(90 * Math.PI / 180);\n        }\n\n        // Step 3 - Draw resized image on original canvas.\n        if (inputImage.width != inputImage.height) {\n          context.drawImage(resizeCanvas, -resizeCanvas.width / 2, -resizeCanvas.height / 2);\n        } else {\n          context.drawImage(resizeCanvas, 0, 0);\n        }\n        canvasImage = context.getImageData(0, 0, resizeCanvas.width,\n          resizeCanvas.height);\n\n      } else {\n        context.drawImage(inputImage, 0, 0);\n        canvasImage = context.getImageData(0, 0, inputImage.width,\n          inputImage.height);\n      }\n      // Get image data and convert it to a 3D array\n      let imageData = canvasImage.data;\n      let imageWidth = canvasImage.width;\n      let imageHeight = canvasImage.height;\n\n      // Remove this newly created canvas element\n      canvas.parentNode.removeChild(canvas);\n\n      resolve(imageDataTo3DTensor(imageData, imageWidth, imageHeight, normalize));\n    }\n    inputImage.onerror = reject;\n  })\n}\n\n/**\n * Wrapper to load a model.\n * \n * @param {string} modelFile Filename of converted (through tensorflowjs.py)\n *  model json file.\n */\nexport const loadTrainedModel = (modelFile) => {\n  return tf.loadLayersModel(modelFile);\n}\n"
  },
  {
    "path": "src/utils/cnn.js",
    "content": "// Enum of node types\nconst nodeType = {\n  INPUT: 'input',\n  CONV: 'conv',\n  POOL: 'pool',\n  RELU: 'relu',\n  FC: 'fc',\n  FLATTEN: 'flatten'\n}\n\nclass Node {\n  /**\n   * Class structure for each neuron node.\n   * \n   * @param {string} layerName Name of the node's layer.\n   * @param {int} index Index of this node in its layer.\n   * @param {string} type Node type {input, conv, pool, relu, fc}. \n   * @param {number} bias The bias assocated to this node.\n   * @param {[[number]]} output Output of this node.\n   */\n  constructor(layerName, index, type, bias, output) {\n    this.layerName = layerName;\n    this.index = index;\n    this.type = type;\n    this.bias = bias;\n    this.output = output;\n\n    // Weights are stored in the links\n    this.inputLinks = [];\n    this.outputLinks = [];\n  }\n}\n\nclass Link {\n  constructor(source, dest, weight) {\n    this.source = source;\n    this.dest = dest;\n    this.weight = weight;\n  }\n}\n\nconst constructNNFromJSON = (nnJSON, inputImageArray) => {\n  console.log(nnJSON);\n  console.log(inputImageArray);\n  let nn = [];\n\n  // Add the first layer (input layer)\n  let inputLayer = [];\n  let inputShape = nnJSON[0].input_shape;\n\n  // First layer's three nodes' outputs are the channels of inputImageArray\n  for (let i = 0; i < inputShape[2]; i++) {\n    let node = new Node('input', i, nodeType.INPUT, 0, inputImageArray[i]);\n    inputLayer.push(node);\n  }\n                                                                                                                   \n  nn.push(inputLayer);\n  let curLayerIndex = 1;\n\n  nnJSON.forEach(layer => {\n    let curLayerNodes = [];\n    let curLayerType;\n\n    if (layer.name.includes('conv')) {\n      curLayerType = nodeType.CONV;\n    } else if (layer.name.includes('pool')) {\n      curLayerType = nodeType.POOL;\n    } else if (layer.name.includes('relu')) {\n      curLayerType = nodeType.RELU;\n    } else if (layer.name.includes('output')) {\n      curLayerType = nodeType.FC;\n    } else if (layer.name.includes('flatten')) {\n      curLayerType = nodeType.FLATTEN;\n    } else {\n      console.log('Find unknown type');\n    }\n\n    let shape = layer.output_shape.slice(0, 2);\n    let bias = 0;\n    let output;\n    if (curLayerType === nodeType.FLATTEN || curLayerType === nodeType.FC) {\n      output = 0;\n    } else {\n      output = init2DArray(shape[0], shape[1], 0);\n    }\n\n    // Add neurons into this layer\n    for (let i = 0; i < layer.num_neurons; i++) {\n      if (curLayerType === nodeType.CONV || curLayerType === nodeType.FC) {\n        bias = layer.weights[i].bias;\n      }\n      let node = new Node(layer.name, i, curLayerType, bias, output)\n\n      // Connect this node to all previous nodes (create links)\n      if (curLayerType === nodeType.CONV || curLayerType === nodeType.FC) {\n        // CONV and FC layers have weights in links. Links are one-to-multiple\n        for (let j = 0; j < nn[curLayerIndex - 1].length; j++) {\n          let preNode = nn[curLayerIndex - 1][j];\n          let curLink = new Link(preNode, node, layer.weights[i].weights[j]);\n          preNode.outputLinks.push(curLink);\n          node.inputLinks.push(curLink);\n        }\n      } else if (curLayerType === nodeType.RELU || curLayerType === nodeType.POOL) {\n        // RELU and POOL layers have no weights. Links are one-to-one\n        let preNode = nn[curLayerIndex - 1][i];\n        let link = new Link(preNode, node, null);\n        preNode.outputLinks.push(link);\n        node.inputLinks.push(link);\n      } else if (curLayerType === nodeType.FLATTEN) {\n        // Flatten layer has no weights. Links are multiple-to-one.\n        // Use dummy weights to store the corresponding entry in the previsou\n        // node as (row, column)\n        // The flatten() in tf2.keras has order: channel -> row -> column\n        let preNodeWidth = nn[curLayerIndex - 1][0].output.length,\n          preNodeNum = nn[curLayerIndex - 1].length,\n          preNodeIndex = i % preNodeNum,\n          preNodeRow = Math.floor(Math.floor(i / preNodeNum) / preNodeWidth),\n          preNodeCol = Math.floor(i / preNodeNum) % preNodeWidth,\n          link = new Link(nn[curLayerIndex - 1][preNodeIndex],\n            node, [preNodeRow, preNodeCol]);\n\n        nn[curLayerIndex - 1][preNodeIndex].outputLinks.push(link);\n        node.inputLinks.push(link);\n      }\n      curLayerNodes.push(node);\n    }\n\n    // Add current layer to the NN\n    nn.push(curLayerNodes);\n    curLayerIndex++;\n  });\n\n  return nn;\n}\n\nexport const constructNN = (inputImageFile) => {\n  // Load the saved model file\n  return new Promise((resolve, reject) => {\n    fetch('PUBLIC_URL/assets/data/nn_10.json')\n      .then(response => {\n        response.json().then(nnJSON => {\n          getInputImageArray(inputImageFile)\n            .then(inputImageArray => {\n              let nn = constructNNFromJSON(nnJSON, inputImageArray);\n              resolve(nn);\n            })\n        });\n      })\n      .catch(error => {\n        reject(error);\n      });\n  });\n}\n\n// Helper functions\n\n/**\n * Create a 2D array (matrix) with given size and default value.\n * \n * @param {int} height Height (number of rows) for the matrix\n * @param {int} width Width (number of columns) for the matrix\n * @param {int} fill Default value to fill this matrix\n */\nexport const init2DArray = (height, width, fill) => {\n  let array = [];\n  // Itereate through rows\n  for (let r = 0; r < height; r++) {\n    let row = new Array(width).fill(fill);\n    array.push(row);\n  }\n  return array;\n}\n\n/**\n * Dot product of two matrices.\n * @param {[[number]]} mat1 Matrix 1\n * @param {[[number]]} mat2 Matrix 2\n */\nconst matrixDot = (mat1, mat2) => {\n  console.assert(mat1.length === mat2.length, 'Dimension not matching');\n  console.assert(mat1[0].length === mat2[0].length, 'Dimension not matching');\n\n  let result = 0;\n  for (let i = 0; i < mat1.length; i++){\n    for (let j = 0; j < mat1[0].length; j++){\n      result += mat1[i][j] * mat2[i][j];\n    }\n  }\n  \n  return result;\n}\n\n/**\n * Matrix elementwise addition.\n * @param {[[number]]} mat1 Matrix 1\n * @param {[[number]]} mat2 Matrix 2\n */\nexport const matrixAdd = (mat1, mat2) => {\n  console.assert(mat1.length === mat2.length, 'Dimension not matching');\n  console.assert(mat1[0].length === mat2[0].length, 'Dimension not matching');\n\n  let result = init2DArray(mat1.length, mat1.length, 0);\n\n  for (let i = 0; i < mat1.length; i++) {\n    for (let j = 0; j < mat1.length; j++) {\n      result[i][j] = mat1[i][j] + mat2[i][j];\n    }\n  }\n\n  return result;\n}\n\n/**\n * 2D slice on a matrix.\n * @param {[[number]]} mat Matrix\n * @param {int} xs First dimension (row) starting index\n * @param {int} xe First dimension (row) ending index\n * @param {int} ys Second dimension (column) starting index\n * @param {int} ye Second dimension (column) ending index\n */\nexport const matrixSlice = (mat, xs, xe, ys, ye) => {\n  return mat.slice(xs, xe).map(s => s.slice(ys, ye));\n}\n\n/**\n * Compute the maximum of a matrix.\n * @param {[[number]]} mat Matrix\n */\nconst matrixMax = (mat) => {\n  let curMax = -Infinity;\n  for (let i = 0; i < mat.length; i++) {\n    for (let j = 0; j < mat[0].length; j++) {\n      if (mat[i][j] > curMax) {\n        curMax = mat[i][j];\n      }\n    }\n  }\n  return curMax;\n}\n\n/**\n * Convert canvas image data into a 3D array with dimension [height, width, 3].\n * Each pixel is in 0-255 scale.\n * @param {[int8]} imageData Canvas image data\n */\nconst imageDataTo3DArray = (imageData) => {\n  // Get image dimension (assume square image)\n  let width = Math.sqrt(imageData.length / 4);\n\n  // Create array placeholder for each channel\n  let imageArray = [init2DArray(width, width, 0), init2DArray(width, width, 0),\n    init2DArray(width, width, 0)];\n  \n  // Iterate through the data to fill out channel arrays above\n  for (let i = 0; i < imageData.length; i++) {\n    let pixelIndex = Math.floor(i / 4),\n      channelIndex = i % 4,\n      row = Math.floor(pixelIndex / width),\n      column = pixelIndex % width;\n    \n    if (channelIndex < 3) {\n      imageArray[channelIndex][row][column] = imageData[i];\n    }\n  }\n\n  return imageArray;\n}\n\n/**\n * Get the 3D pixel value array of the given image file.\n * @param {string} imgFile File path to the image file\n * @returns A promise with the corresponding 3D array\n */\nconst getInputImageArray = (imgFile) => {\n  let canvas = document.createElement('canvas');\n  canvas.style.cssText = 'display:none;';\n  document.getElementsByTagName('body')[0].appendChild(canvas);\n  let context = canvas.getContext('2d');\n\n  return new Promise((resolve, reject) => {\n    let inputImage = new Image();\n    inputImage.src = imgFile;\n    inputImage.onload = () => {\n      context.drawImage(inputImage, 0, 0,);\n      // Get image data and convert it to a 3D array\n      let imageData = context.getImageData(0, 0, inputImage.width,\n        inputImage.height).data;\n\n      // Remove this newly created canvas element\n      canvas.parentNode.removeChild(canvas);\n\n      console.log(imageDataTo3DArray(imageData));\n      resolve(imageDataTo3DArray(imageData));\n    }\n    inputImage.onerror = reject;\n  })\n}\n\n/**\n * Compute convolutions of one kernel on one matrix (one slice of a tensor).\n * @param {[[number]]} input Input, square matrix\n * @param {[[number]]} kernel Kernel weights, square matrix\n * @param {int} stride Stride size\n * @param {int} padding Padding size\n */\nexport const singleConv = (input, kernel, stride=1, padding=0) => {\n  // TODO: implement padding\n\n  // Only support square input and kernel\n  console.assert(input.length === input[0].length,\n     'Conv input is not square');\n  console.assert(kernel.length === kernel[0].length,\n    'Conv kernel is not square');\n\n  let stepSize = (input.length - kernel.length) / stride + 1;\n\n  let result = init2DArray(stepSize, stepSize, 0);\n\n  // Window sliding\n  for (let r = 0; r < stepSize; r++) {\n    for (let c = 0; c < stepSize; c++) {\n      let curWindow = matrixSlice(input, r * stride, r * stride + kernel.length,\n        c * stride, c * stride + kernel.length);\n      let dot = matrixDot(curWindow, kernel);\n      result[r][c] = dot;\n    }\n  }\n  return result;\n}\n\n/**\n * Convolution operation. This function update the outputs property of all nodes\n * in the given layer. Previous layer is accessed by the reference in nodes'\n * links.\n * @param {[Node]} curLayer Conv layer.\n */\nconst convolute = (curLayer) => {\n  console.assert(curLayer[0].type === 'conv', 'Wrong layer type');\n\n  // Itereate through all nodes in curLayer to update their outputs\n  curLayer.forEach(node => {\n    /*\n     * Accumulate the single conv result matrices from previous channels.\n     * Previous channels (node) are accessed by the reference in Link objects.\n     */\n    let newOutput = init2DArray(node.output.length, node.output.length, 0);\n\n    for (let i = 0; i < node.inputLinks.length; i++) {\n      let curLink = node.inputLinks[i];\n      let curConvResult = singleConv(curLink.source.output, curLink.weight);\n      newOutput = matrixAdd(newOutput, curConvResult);\n    }\n\n    // Add bias to all element in the output\n    let biasMatrix = init2DArray(newOutput.length, newOutput.length, node.bias);\n    newOutput = matrixAdd(newOutput, biasMatrix);\n\n    node.output = newOutput;\n  })\n}\n\n/**\n * Activate matrix mat using ReLU (max(0, x)).\n * @param {[[number]]} mat Matrix\n */\nconst singleRelu = (mat) => {\n  // Only support square matrix\n  console.assert(mat.length === mat[0].length, 'Activating non-square matrix!');\n\n  let width = mat.length;\n  let result = init2DArray(width, width, 0);\n\n  for (let i = 0; i < width; i++) {\n    for (let j = 0; j < width; j++) {\n      result[i][j] = Math.max(0, mat[i][j]);\n    }\n  }\n  return result;\n}\n\n/**\n * Update outputs of all nodes in the current ReLU layer. Values of previous\n * layer nodes are accessed by the links stored in the current layer.\n * @param {[Node]} curLayer ReLU layer\n */\nconst relu = (curLayer) => {\n  console.assert(curLayer[0].type === 'relu', 'Wrong layer type');\n\n  // Itereate through all nodes in curLayer to update their outputs\n  for (let i = 0; i < curLayer.length; i++) {\n    let curNode = curLayer[i];\n    let preNode = curNode.inputLinks[0].source;\n    curNode.output = singleRelu(preNode.output);\n  }\n}\n\n/**\n * Max pool one matrix.\n * @param {[[number]]} mat Matrix\n * @param {int} kernelWidth Pooling kernel length (only supports 2)\n * @param {int} stride Pooling sliding stride (only supports 2)\n * @param {string} padding Pading method when encountering odd number mat,\n * currently this function only supports 'VALID'\n */\nexport const singleMaxPooling = (mat, kernelWidth=2, stride=2, padding='VALID') => {\n  console.assert(kernelWidth === 2, 'Only supports kernen = [2,2]');\n  console.assert(stride === 2, 'Only supports stride = 2');\n  console.assert(padding === 'VALID', 'Only support valid padding');\n\n  // Handle odd length mat\n  // 'VALID': ignore edge rows and columns\n  // 'SAME': add zero padding to make the mat have even length\n  if (mat.length % 2 === 1 && padding === 'VALID') {\n    mat = matrixSlice(mat, 0, mat.length - 1, 0, mat.length - 1);\n  }\n\n  let stepSize = (mat.length - kernelWidth) / stride + 1;\n  let result = init2DArray(stepSize, stepSize, 0);\n\n  for (let r = 0; r < stepSize; r++) {\n    for (let c = 0; c < stepSize; c++) {\n      let curWindow = matrixSlice(mat, r * stride, r * stride + kernelWidth,\n        c * stride, c * stride + kernelWidth);\n      result[r][c] = matrixMax(curWindow);\n    }\n }\n return result;\n}\n\n/**\n * Max pooling one layer.\n * @param {[Node]} curLayer MaxPool layer\n */\nconst maxPooling = (curLayer) => {\n  console.assert(curLayer[0].type === 'pool', 'Wrong layer type');\n\n  // Itereate through all nodes in curLayer to update their outputs\n  for (let i = 0; i < curLayer.length; i++) {\n    let curNode = curLayer[i];\n    let preNode = curNode.inputLinks[0].source;\n    curNode.output = singleMaxPooling(preNode.output);\n  }\n}\n\n/**\n * Flatten a previous 2D layer (conv2d or maxpool2d). The flatten order matches\n * tf2.keras' implementation: channel -> row -> column.\n * @param {[Node]} curLayer Flatten layer\n */\nconst flatten = (curLayer) => {\n  console.assert(curLayer[0].type === 'flatten', 'Wrong layer type');\n\n  // Itereate through all nodes in curLayer to update their outputs\n  for (let i = 0; i < curLayer.length; i++) {\n    let curNode = curLayer[i];\n    let preNode = curNode.inputLinks[0].source;\n    let coordinate = curNode.inputLinks[0].weight;\n    // Take advantage of the dummy weights\n    curNode.output = preNode.output[coordinate[0]][coordinate[1]];\n  }\n}\n\nconst fullyConnect = (curLayer) => {\n  console.assert(curLayer[0].type === 'fc', 'Wrong layer type');\n  // TODO\n}\n\nexport const tempMain = async () => {\n  let nn = await constructNN('PUBLIC_URL/assets/img/koala.jpeg');\n  convolute(nn[1]);\n  relu(nn[2])\n  convolute(nn[3]);\n  relu(nn[4]);\n  maxPooling(nn[5]);\n  convolute(nn[6]);\n  relu(nn[7])\n  convolute(nn[8]);\n  relu(nn[9]);\n  maxPooling(nn[10]);\n  convolute(nn[11]);\n  relu(nn[12])\n  convolute(nn[13]);\n  relu(nn[14]);\n  maxPooling(nn[15]);\n  flatten(nn[16]);\n  console.log(nn[16].map(d => d.output));\n}"
  },
  {
    "path": "src/utils/deploy-to-gh-pages.sh",
    "content": "#!/bin/bash\nset -o errexit\n\n# config\ngit config --global user.email \"xiao.hk1997@gmail.com\"\ngit config --global user.name \"xiaohk\"\n\n# build\ngit clone git@github.com:poloclub/cnn-explainer.git\ncd cnn-explainer\n\nnpm install\nnpm run build\n\nmkdir dist\ncopy -r ./public/* ./dist\nsed -i 's/\\/assets/\\/cnn-explainer\\/assets/g' ./dist/index.html\n\ngit add dist\ngit commit -m \"Deploy gh-pages from Travis\"\ngit subtree push --prefix dist origin gh-pages\n"
  },
  {
    "path": "src/utils/utlis.py",
    "content": "import tensorflow as tf\nfrom json import dump\n\nassert(int(tf.__version__.split('.')[0]) == 2)\n\n\ndef convert_h5_to_json(model_h5_file, model_json_file):\n    \"\"\"\n    Helper function to convert tf2 stored model h5 file to a customized json\n    format.\n\n    Args:\n        model_h5_file(string): filename of the stored h5 file\n        model_json_file(string): filename of the output json file\n    \"\"\"\n\n    model = tf.keras.models.load_model(model_h5_file)\n    json_dict = {}\n\n    for l in model.layers:\n        json_dict[l.name] = {\n            'input_shape': l.input_shape[1:],\n            'output_shape': l.output_shape[1:],\n            'num_neurons': l.output_shape[-1]\n        }\n\n        if 'conv' in l.name:\n            all_weights = l.weights[0]\n            neuron_weights = []\n\n            # Iterate through neurons in that layer\n            for n in range(all_weights.shape[3]):\n                cur_neuron_dict = {}\n                cur_neuron_dict['bias'] = l.bias.numpy()[n].item()\n\n                # Get the current weights\n                cur_weights = all_weights[:, :, :, n].numpy().astype(float)\n\n                # Reshape the weights from (height, width, input_c) to\n                # (input_c, height, width)\n                cur_weights = cur_weights.transpose((2, 0, 1)).tolist()\n                cur_neuron_dict['weights'] = cur_weights\n\n                neuron_weights.append(cur_neuron_dict)\n\n            json_dict[l.name]['weights'] = neuron_weights\n\n        elif 'output' in l.name:\n            all_weights = l.weights[0]\n            neuron_weights = []\n\n            # Iterate through neurons in that layer\n            for n in range(all_weights.shape[1]):\n                cur_neuron_dict = {}\n                cur_neuron_dict['bias'] = l.bias.numpy()[n].item()\n\n                # Get the current weights\n                cur_weights = all_weights[:, n].numpy().astype(float).tolist()\n                cur_neuron_dict['weights'] = cur_weights\n\n                neuron_weights.append(cur_neuron_dict)\n\n            json_dict[l.name]['weights'] = neuron_weights\n\n    dump(json_dict, open(model_json_file, 'w'), indent=2)\n"
  },
  {
    "path": "tiny-vgg/README.md",
    "content": "# Train a Tiny VGG\n\nThis directory includes code and data to train a Tiny VGG model\n(inspired by the demo CNN in [Stanford CS231n class](http://cs231n.stanford.edu))\non 10 everyday classes from the [Tiny ImageNet](https://tiny-imagenet.herokuapp.com).\n\n## Installation\n\nFirst, you want to unzip `data.zip`. The file structure would be something like:\n\n```\n.\n├── data\n│   ├── class_10_train\n│   │   ├── n01882714\n│   │   │   ├── images [500 entries exceeds filelimit, not opening dir]\n│   │   │   └── n01882714_boxes.txt\n│   │   ├── n02165456\n│   │   │   ├── images [500 entries exceeds filelimit, not opening dir]\n│   │   │   └── n02165456_boxes.txt\n│   │   ├── n02509815\n│   │   │   ├── images [500 entries exceeds filelimit, not opening dir]\n│   │   │   └── n02509815_boxes.txt\n│   │   ├── n03662601\n│   │   │   ├── images [500 entries exceeds filelimit, not opening dir]\n│   │   │   └── n03662601_boxes.txt\n│   │   ├── n04146614\n│   │   │   ├── images [500 entries exceeds filelimit, not opening dir]\n│   │   │   └── n04146614_boxes.txt\n│   │   ├── n04285008\n│   │   │   ├── images [500 entries exceeds filelimit, not opening dir]\n│   │   │   └── n04285008_boxes.txt\n│   │   ├── n07720875\n│   │   │   ├── images [500 entries exceeds filelimit, not opening dir]\n│   │   │   └── n07720875_boxes.txt\n│   │   ├── n07747607\n│   │   │   ├── images [500 entries exceeds filelimit, not opening dir]\n│   │   │   └── n07747607_boxes.txt\n│   │   ├── n07873807\n│   │   │   ├── images [500 entries exceeds filelimit, not opening dir]\n│   │   │   └── n07873807_boxes.txt\n│   │   └── n07920052\n│   │       ├── images [500 entries exceeds filelimit, not opening dir]\n│   │       └── n07920052_boxes.txt\n│   ├── class_10_val\n│   │   ├── test_images [250 entries exceeds filelimit, not opening dir]\n│   │   └── val_images [250 entries exceeds filelimit, not opening dir]\n│   ├── class_dict_10.json\n│   └── val_class_dict_10.json\n├── data.zip\n├── environment.yaml\n└── tiny-vgg.py\n```\n\nTo install all dependencies, run the following code\n\n```\nconda env create --file environment.yaml\n```\n\n## Training\n\nTo train Tiny VGG on these 10 classes, run the following code\n\n```\npython tiny-vgg.py\n```\n\nAfter training, you will get two saved models in Keras format: `trained_tiny_vgg.h5`\nand `trained_vgg_best.h5`. The first file is the final model after training, and\n`trained_vgg_best.h5` is the model having the best validation performance.\nYou can use either one for CNN Explainer.\n\n## Convert Model Format\n\nBefore loading the model using *tensorflow.js*, you want to convert the model file\nfrom Keras `h5` format to [tensorflow.js format](https://www.tensorflow.org/js/tutorials/conversion/import_keras).\n\n```\ntensorflowjs_converter --input_format keras trained_vgg_best.h5 ./\n```\n\nThen you can put the output file `group1-shard1of1.bin` in `/public/data` and use\n*tensorflow.js* to load the trained model.\n\n"
  },
  {
    "path": "tiny-vgg/environment.yaml",
    "content": "name: tiny-vgg\nchannels:\n  - defaults\ndependencies:\n  - pip=20.0.2\n  - python=3.6.10\n  - pip:\n    - h5py==2.10.0\n    - numpy==1.18.3\n    - pandas==1.0.3\n    - scipy==1.4.1\n    - tensorflow==2.1.0\n    - tensorflow-cpu==2.1.0\n    - tensorflowjs==1.7.4\n"
  },
  {
    "path": "tiny-vgg/tiny-vgg.py",
    "content": "import tensorflow as tf\nimport numpy as np\nimport pandas as pd\nimport re\nfrom shutil import copyfile\nfrom glob import glob\nfrom json import load, dump\nfrom tensorflow.keras.layers import Dense, Flatten, Conv2D, MaxPool2D,\\\n    Activation\nfrom tensorflow.keras import Model, Sequential\nfrom os.path import basename\nfrom time import time\n\nprint(tf.__version__)\n\n\ndef create_class_dict():\n    # Create a new version only including tiny 200 classes\n    df = pd.read_csv('./tiny-imagenet-200/words.txt', sep='\\t', header=None)\n    keys, classes = df[0], df[1]\n    class_dict = dict(zip(keys, classes))\n\n    tiny_class_dict = {}\n    cur_index = 0\n\n    for directory in glob('./tiny-imagenet-200/train/*'):\n        cur_key = basename(directory)\n        tiny_class_dict[cur_key] = {'class': class_dict[cur_key],\n                                    'index': cur_index}\n        cur_index += 1\n\n    dump(tiny_class_dict, open('./tiny-imagenet-200/class_dict.json', 'w'),\n         indent=2)\n\n\ndef create_val_class_dict():\n    tiny_class_dict = load(open('./tiny-imagenet-200/class_dict.json', 'r'))\n    tiny_val_class_dict = {}\n\n    # Create a dictionary for validation images\n    df = pd.read_csv('./tiny-imagenet-200/val/val_annotations.txt', sep='\\t',\n                     header=None)\n    image_names = df[0]\n    image_classes = df[1]\n\n    for i in range(len(image_names)):\n        tiny_val_class_dict[image_names[i]] = {\n            'class': tiny_class_dict[image_classes[i]]['class'],\n            'index': tiny_class_dict[image_classes[i]]['index'],\n        }\n\n    dump(tiny_val_class_dict, open('./tiny-imagenet-200/val_class_dict.json',\n                                   'w'),\n         indent=2)\n\n\ndef split_val_data():\n    # Split validation images to 50% early stopping and 50% hold-out testing\n    val_images = glob('./tiny-imagenet-200/val/images/*.JPEG')\n    np.random.shuffle(val_images)\n\n    for i in range(len(val_images)):\n        if i < len(val_images) // 2:\n            copyfile(val_images[i], val_images[i].replace('images',\n                                                          'val_images'))\n        else:\n            copyfile(val_images[i], val_images[i].replace('images',\n                                                          'test_images'))\n\n\ndef process_path_train(path):\n    \"\"\"\n    Get the (class label, processed image) pair of the given image path. This\n    funciton uses python primitives, so you need to use tf.py_funciton wrapper.\n    This function uses global variables:\n\n        WIDTH(int): the width of the targeting image\n        HEIGHT(int): the height of the targeting iamge\n        NUM_CLASS(int): number of classes\n\n    Args:\n        path(string): path to an image file\n    \"\"\"\n\n    # Get the class\n    path = path.numpy()\n    image_name = basename(path.decode('ascii'))\n    label_name = re.sub(r'(.+)_\\d+\\.JPEG', r'\\1', image_name)\n    label_index = tiny_class_dict[label_name]['index']\n\n    # Convert label to one-hot encoding\n    label = tf.one_hot(indices=[label_index], depth=NUM_CLASS)\n    label = tf.reshape(label, [NUM_CLASS])\n\n    # Read image and convert the image to [0, 1] range 3d tensor\n    img = tf.io.read_file(path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.convert_image_dtype(img, tf.float32)\n    img = tf.image.resize(img, [WIDTH, HEIGHT])\n\n    return(img, label)\n\n\ndef process_path_test(path):\n    \"\"\"\n    Get the (class label, processed image) pair of the given image path. This\n    funciton uses python primitives, so you need to use tf.py_funciton wrapper.\n    This function uses global variables:\n\n        WIDTH(int): the width of the targeting image\n        HEIGHT(int): the height of the targeting iamge\n        NUM_CLASS(int): number of classes\n\n    The filepath encoding for test images is different from training images.\n\n    Args:\n        path(string): path to an image file\n    \"\"\"\n\n    # Get the class\n    path = path.numpy()\n    image_name = basename(path.decode('ascii'))\n    label_index = tiny_val_class_dict[image_name]['index']\n\n    # Convert label to one-hot encoding\n    label = tf.one_hot(indices=[label_index], depth=NUM_CLASS)\n    label = tf.reshape(label, [NUM_CLASS])\n\n    # Read image and convert the image to [0, 1] range 3d tensor\n    img = tf.io.read_file(path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.convert_image_dtype(img, tf.float32)\n    img = tf.image.resize(img, [WIDTH, HEIGHT])\n\n    return(img, label)\n\n\ndef prepare_for_training(dataset, batch_size=32, cache=True,\n                         shuffle_buffer_size=1000):\n\n    if cache:\n        if isinstance(cache, str):\n            dataset = dataset.cache(cache)\n        else:\n            dataset = dataset.cache()\n\n    # Only shuffle elements in the buffer size\n    dataset = dataset.shuffle(buffer_size=shuffle_buffer_size)\n\n    # Pre featch batches in the background\n    dataset = dataset.batch(batch_size)\n    dataset = dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)\n\n    return dataset\n\n\ndef prepare_for_testing(dataset, batch_size=32, cache=True):\n    if cache:\n        if isinstance(cache, str):\n            dataset = dataset.cache(cache)\n        else:\n            dataset = dataset.cache()\n\n    # Pre featch batches in the background\n    dataset = dataset.batch(batch_size)\n    dataset = dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)\n\n    return dataset\n\n\nclass TinyVGG(Model):\n    \"\"\"\n    Tiny VGG structure is adapted from http://cs231n.stanford.edu:\n        > This particular network is classifying CIFAR-10 images into one of 10\n        > classes and was trained with ConvNetJS. Its exact architecture is\n        > [conv-relu-conv-relu-pool]x3-fc-softmax, for a total of 17 layers and\n        > 7000 parameters. It uses 3x3 convolutions and 2x2 pooling regions.\n    \"\"\"\n    def __init__(self, filters=10):\n        super(TinyVGG, self).__init__()\n        self.conv_1_1 = Conv2D(filters, (3, 3), name='conv_1_1')\n        self.relu_1_1 = Activation('relu', name='relu_1_1')\n        self.conv_1_2 = Conv2D(filters, (3, 3), name='conv_1_2')\n        self.relu_1_2 = Activation('relu', name='relu_1_2')\n        self.max_pool_1 = MaxPool2D((2, 2), name='max_pool_1')\n\n        self.conv_2_1 = Conv2D(filters, (3, 3), name='conv_2_1')\n        self.relu_2_1 = Activation('relu', name='relu_2_1')\n        self.conv_2_2 = Conv2D(filters, (3, 3), name='conv_2_2')\n        self.relu_2_2 = Activation('relu', name='relu_2_2')\n        self.max_pool_2 = MaxPool2D((2, 2), name='max_pool_2')\n\n        self.flatten = Flatten()\n        self.fc = Dense(NUM_CLASS, activation='softmax')\n\n    def call(self, x):\n        x = self.conv_1_1(x)\n        x = self.relu_1_1(x)\n        x = self.conv_1_2(x)\n        x = self.relu_1_2(x)\n        x = self.max_pool_1(x)\n\n        x = self.conv_2_1(x)\n        x = self.relu_2_1(x)\n        x = self.conv_2_2(x)\n        x = self.relu_2_2(x)\n        x = self.max_pool_2(x)\n\n        x = self.conv_3_1(x)\n        x = self.relu_3_1(x)\n        x = self.conv_3_2(x)\n        x = self.relu_3_2(x)\n        x = self.max_pool_3(x)\n\n        x = self.flatten(x)\n        return self.fc(x)\n\n\n@tf.function\ndef train_step(image_batch, label_batch):\n    with tf.GradientTape() as tape:\n        # Predict\n        predictions = tiny_vgg(image_batch)\n\n        # Update gradient\n        loss = loss_object(label_batch, predictions)\n        gradients = tape.gradient(loss, tiny_vgg.trainable_variables)\n        optimizer.apply_gradients(zip(gradients, tiny_vgg.trainable_variables))\n\n        train_mean_loss(loss)\n        train_accuracy(label_batch, predictions)\n\n\n@tf.function\ndef vali_step(image_batch, label_batch):\n    predictions = tiny_vgg(image_batch)\n    vali_loss = loss_object(label_batch, predictions)\n\n    vali_mean_loss(vali_loss)\n    vali_accuracy(label_batch, predictions)\n\n\n@tf.function\ndef test_step(image_batch, label_batch):\n    predictions = tiny_vgg(image_batch)\n    test_loss = loss_object(label_batch, predictions)\n\n    test_mean_loss(test_loss)\n    test_accuracy(label_batch, predictions)\n\n\nWIDTH = 64\nHEIGHT = 64\nEPOCHS = 1000\nPATIENCE = 50\nLR = 0.001\nNUM_CLASS = 10\nBATCH_SIZE = 32\n\n# Create training and validation dataset\ntiny_class_dict = load(open('./data/class_dict_10.json', 'r'))\ntiny_val_class_dict = load(open('./data/val_class_dict_10.json', 'r'))\n\ntraining_images = './data/class_10_train/*/images/*.JPEG'\nvali_images = './data/class_10_val/val_images/*.JPEG'\ntest_images = './data/class_10_val/test_images/*.JPEG'\n\n# Create training dataset\ntrain_path_dataset = tf.data.Dataset.list_files(training_images)\n\ntrain_labeld_dataset = train_path_dataset.map(\n    lambda path: tf.py_function(\n        process_path_train,\n        [path],\n        [tf.float32, tf.float32]\n    )\n)\n\n# Create vali dataset\nvali_path_dataset = tf.data.Dataset.list_files(vali_images)\n\nvali_labeld_dataset = vali_path_dataset.map(\n    lambda path: tf.py_function(\n        process_path_test,\n        [path],\n        [tf.float32, tf.float32]\n    )\n)\n\n# Create test dataset\ntest_path_dataset = tf.data.Dataset.list_files(test_images)\n\ntest_labeld_dataset = test_path_dataset.map(\n    lambda path: tf.py_function(\n        process_path_test,\n        [path],\n        [tf.float32, tf.float32]\n    )\n)\n\ntrain_dataset = prepare_for_training(train_labeld_dataset,\n                                     batch_size=BATCH_SIZE)\nvali_dataset = prepare_for_training(vali_labeld_dataset,\n                                    batch_size=BATCH_SIZE)\ntest_dataset = prepare_for_training(test_labeld_dataset,\n                                    batch_size=BATCH_SIZE)\n\n# Create an instance of the model\n# tiny_vgg = TinyVGG()\n\n# Use Keras Sequential API instead, since it is easy to save the model\nfilters = 10\ntiny_vgg = Sequential([\n    Conv2D(filters, (3, 3), input_shape=(64, 64, 3), name='conv_1_1'),\n    Activation('relu', name='relu_1_1'),\n    Conv2D(filters, (3, 3), name='conv_1_2'),\n    Activation('relu', name='relu_1_2'),\n    MaxPool2D((2, 2), name='max_pool_1'),\n\n    Conv2D(filters, (3, 3), name='conv_2_1'),\n    Activation('relu', name='relu_2_1'),\n    Conv2D(filters, (3, 3), name='conv_2_2'),\n    Activation('relu', name='relu_2_2'),\n    MaxPool2D((2, 2), name='max_pool_2'),\n\n    Flatten(name='flatten'),\n    Dense(NUM_CLASS, activation='softmax', name='output')\n])\n\n# \"Compile\" the model with loss function and optimizer\nloss_object = tf.keras.losses.CategoricalCrossentropy()\n# optimizer = tf.keras.optimizers.Adam(learning_rate=LR)\noptimizer = tf.keras.optimizers.SGD(learning_rate=LR)\n\ntrain_mean_loss = tf.keras.metrics.Mean(name='train_mean_loss')\ntrain_accuracy = tf.keras.metrics.CategoricalAccuracy(name='train_accuracy')\n\nvali_mean_loss = tf.keras.metrics.Mean(name='vali_mean_loss')\nvali_accuracy = tf.keras.metrics.CategoricalAccuracy(name='vali_accuracy')\n\n# Initialize early stopping parameters\nno_improvement_epochs = 0\nbest_vali_loss = np.inf\nstart_time = time()\nprint('Start training.\\n')\n\nfor epoch in range(EPOCHS):\n    # Train\n    for image_batch, label_batch in train_dataset:\n        train_step(image_batch, label_batch)\n\n    # Predict on the test dataset\n    for image_batch, label_batch in vali_dataset:\n        vali_step(image_batch, label_batch)\n\n    template = 'epoch: {}, train loss: {:.4f}, train accuracy: {:.4f}, '\n    template += 'vali loss: {:.4f}, vali accuracy: {:.4f}'\n    print(template.format(epoch + 1,\n                          train_mean_loss.result(),\n                          train_accuracy.result() * 100,\n                          vali_mean_loss.result(),\n                          vali_accuracy.result() * 100))\n\n    # Early stopping\n    if vali_mean_loss.result() < best_vali_loss:\n        no_improvement_epochs = 0\n        best_vali_loss = vali_mean_loss.result()\n        # Save the best model\n        tiny_vgg.save('trained_vgg_best.h5')\n    else:\n        no_improvement_epochs += 1\n\n    if no_improvement_epochs >= PATIENCE:\n        print('Early stopping at epoch = {}'.format(epoch))\n        break\n\n    # Reset evaluation metrics\n    train_mean_loss.reset_states()\n    train_accuracy.reset_states()\n    vali_mean_loss.reset_states()\n    vali_accuracy.reset_states()\n\nprint('\\nFinished training, used {:.4f} mins.'.format((time() -\n                                                       start_time) / 60))\n# Save trained model\ntiny_vgg.save('trained_tiny_vgg.h5')\ntiny_vgg = tf.keras.models.load_model('trained_vgg_best.h5')\n\n# Test on hold-out test images\ntest_mean_loss = tf.keras.metrics.Mean(name='test_mean_loss')\ntest_accuracy = tf.keras.metrics.CategoricalAccuracy(name='test_accuracy')\n\nfor image_batch, label_batch in test_dataset:\n    test_step(image_batch, label_batch)\n\ntemplate = '\\ntest loss: {:.4f}, test accuracy: {:.4f}'\nprint(template.format(test_mean_loss.result(),\n                      test_accuracy.result() * 100))\n\n"
  }
]