diff --git a/demos/src/index.css b/demos/src/index.css
index 9ea5ad5..5f14d57 100644
--- a/demos/src/index.css
+++ b/demos/src/index.css
@@ -60,3 +60,19 @@ body {
background: rgba(255, 255, 255, 0.5);
border-radius: 10px;
}
+
+/****************************************************************/
+/* MDL overrides */
+
+.demo .mdl-switch__label {
+ font-size: 14px !important;
+ color: #69707a;
+}
+
+.demo .mdl-switch.is-checked .mdl-switch__thumb {
+ background: rgba(27, 188, 155, 0.6);
+}
+
+.demo .mdl-switch.is-checked .mdl-switch__track {
+ background: rgba(27, 188, 155, 0.3);
+}
diff --git a/demos/src/index.js b/demos/src/index.js
index 31c38e8..26e02e9 100644
--- a/demos/src/index.js
+++ b/demos/src/index.js
@@ -5,10 +5,12 @@ import './index.css'
import { Menu } from './menu'
import { Home } from './home'
import { MnistCnn } from './mnist-cnn'
+import { MnistVae } from './mnist-vae'
Vue.component('menu', Menu)
Vue.component('home', Home)
Vue.component('mnist-cnn', MnistCnn)
+Vue.component('mnist-vae', MnistVae)
Vue.use(VueMdl.default)
@@ -22,7 +24,10 @@ const app = new Vue({
// Simple routing
function matchRoute () {
- const routes = ['mnist-cnn']
+ const routes = [
+ 'mnist-cnn',
+ 'mnist-vae'
+ ]
const { hash } = window.location
const route = hash.substr(2)
diff --git a/demos/src/menu.js b/demos/src/menu.js
index c9c4592..54fc9a6 100644
--- a/demos/src/menu.js
+++ b/demos/src/menu.js
@@ -12,6 +12,7 @@ export const Menu = Vue.extend({
-
Basic Convnet - MNIST
+
Basic Convnet for MNIST
Loading...{{ loadingProgress }}%
diff --git a/demos/src/mnist-vae.css b/demos/src/mnist-vae.css
new file mode 100644
index 0000000..fd9c33c
--- /dev/null
+++ b/demos/src/mnist-vae.css
@@ -0,0 +1,58 @@
+.demo.mnist-vae .column {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.demo.mnist-vae .column.input-column {
+ justify-content: flex-end;
+}
+
+.demo.mnist-vae .column.controls-column {
+ align-items: flex-start;
+ justify-content: flex-start;
+ padding-top: 80px;
+}
+
+.demo.mnist-vae .column.output-column {
+ justify-content: center;
+}
+
+.demo.mnist-vae .input-container {
+ display: inline-flex;
+ flex-direction: column;
+ margin: 20px;
+ position: relative;
+}
+
+.demo.mnist-vae .input-label {
+ font-family: 'Nothing You Could Do', cursive;
+ font-size: 18px;
+ color: #69707a;
+ text-align: right;
+}
+
+.demo.mnist-vae .input-label span.arrow {
+ font-size: 36px;
+ color: #CCCCCC;
+ position: absolute;
+ right: -32px;
+ top: 8px;
+}
+
+.demo.mnist-vae .canvas-container {
+ display: inline-flex;
+ justify-content: flex-end;
+ margin: 10px 0;
+}
+
+.demo.mnist-vae .canvas-container canvas {
+ background: white;
+ border: 15px solid rgba(27, 188, 155, 0.3);
+ transition: border-color 0.2s ease-in;
+}
+
+.demo.mnist-vae .canvas-container canvas:hover {
+ border-color: rgba(27, 188, 155, 0.6);
+ cursor: crosshair;
+}
diff --git a/demos/src/mnist-vae.js b/demos/src/mnist-vae.js
new file mode 100644
index 0000000..061e0ba
--- /dev/null
+++ b/demos/src/mnist-vae.js
@@ -0,0 +1,249 @@
+/* global Vue */
+
+import './mnist-vae.css'
+
+import debounce from 'lodash/debounce'
+import * as utils from './utils'
+
+const MODEL_CONFIG = {
+ filepaths: {
+ model: '/demos/data/mnist_vae/mnist_vae.json',
+ weights: '/demos/data/mnist_vae/mnist_vae_weights.buf',
+ metadata: '/demos/data/mnist_vae/mnist_vae_metadata.json'
+ },
+ gpu: false
+}
+
+if (process.env.NODE_ENV === 'production') {
+ Object.assign(MODEL_CONFIG, {
+ filepaths: {
+ model: 'demos/data/mnist_vae/mnist_vae.json',
+ weights: 'https://transcranial.github.io/keras-js-demos-data/mnist_vae/mnist_vae_weights.buf',
+ metadata: 'demos/data/mnist_vae/mnist_vae_metadata.json'
+ }
+ })
+}
+
+const LAYER_DISPLAY_CONFIG = {
+ 'dense_10': {
+ heading: 'ReLU activation, output dimensions = 128',
+ scalingFactor: 2
+ },
+ 'dense_11': {
+ heading: 'ReLU activation, output dimensions = 25088 (64 x 14 x 14)',
+ scalingFactor: 2
+ },
+ 'deconvolution2d_10': {
+ heading: '64 3x3 filters, border mode same, 1x1 strides, ReLU activation',
+ scalingFactor: 2
+ },
+ 'deconvolution2d_11': {
+ heading: '64 3x3 filters, border mode same, 1x1 strides, ReLU activation',
+ scalingFactor: 2
+ },
+ 'deconvolution2d_12': {
+ heading: '64 2x2 filters, border mode valid, 2x2 strides, ReLU activation',
+ scalingFactor: 2
+ },
+ 'convolution2d_8': {
+ heading: '1 2x2 filters, border mode valid, 1x1 strides, sigmoid activation',
+ scalingFactor: 2
+ }
+}
+
+/**
+ *
+ * VUE COMPONENT
+ *
+ */
+export const MnistVae = Vue.extend({
+ template: `
+
+
Convolutional Variational Autoencoder, trained on MNIST
+
+ Loading...{{ loadingProgress }}%
+
+
+
+
+
+
+ {{ layerResult.layerClass }}
+ {{ layerDisplayConfig[layerResult.name].heading }}
+
+
+
+
+
+
+
+
+ `,
+
+ data: function () {
+ return {
+ model: new KerasJS.Model(MODEL_CONFIG),
+ modelLoading: true,
+ input: new Float32Array(2),
+ output: new Float32Array(27 * 27),
+ crosshairsActivated: false,
+ coordinates: [0, 0],
+ layerResultImages: [],
+ layerDisplayConfig: LAYER_DISPLAY_CONFIG,
+ useGpu: MODEL_CONFIG.gpu
+ }
+ },
+
+ computed: {
+ loadingProgress: function () {
+ return this.model.getLoadingProgress()
+ }
+ },
+
+ created: function () {
+ // initialize KerasJS model
+ this.model.initialize()
+ this.model.ready().then(() => {
+ this.modelLoading = false
+ this.getIntermediateResults()
+ })
+ },
+
+ methods: {
+
+ activateCrosshairs: function (e) {
+ this.crosshairsActivated = true
+ },
+
+ deactivateCrosshairs: function (e) {
+ this.crosshairsActivated = false
+ },
+
+ drawCrosshairs: function (e) {
+ if (!this.crosshairsActivated) return
+
+ const [x, y] = this.getEventCanvasCoordinates(e)
+ const ctx = document.getElementById('input-canvas').getContext('2d')
+ ctx.clearRect(0, 0, 200, 200)
+ ctx.strokeStyle = '#1BBC9B'
+ ctx.beginPath()
+ ctx.moveTo(x, 0)
+ ctx.lineTo(x, 200)
+ ctx.stroke()
+ ctx.beginPath()
+ ctx.moveTo(0, y)
+ ctx.lineTo(200, y)
+ ctx.stroke()
+ },
+
+ getEventCanvasCoordinates: function (e) {
+ const borderSize = 15
+ let { clientX, clientY } = e
+ // for touch event
+ if (e.touches && e.touches.length) {
+ clientX = e.touches[0].clientX
+ clientY = e.touches[0].clientY
+ }
+
+ const canvas = document.getElementById('input-canvas')
+ const { left, top } = canvas.getBoundingClientRect()
+ const [x, y] = [clientX - left - borderSize, clientY - top - borderSize]
+ return [x, y]
+ },
+
+ selectCoordinate: function (e) {
+
+ },
+
+ getIntermediateResults: function () {
+ let results = []
+ for (let [name, layer] of this.model.modelLayersMap.entries()) {
+ if (name === 'input') continue
+
+ const layerClass = layer.layerClass || ''
+
+ let images = []
+ if (layer.result && layer.result.tensor.shape.length === 3) {
+ images = utils.unroll3Dtensor(layer.result.tensor)
+ } else if (layer.result && layer.result.tensor.shape.length === 2) {
+ images = [utils.image2Dtensor(layer.result.tensor)]
+ } else if (layer.result && layer.result.tensor.shape.length === 1) {
+ images = [utils.image1Dtensor(layer.result.tensor)]
+ }
+ results.push({
+ name,
+ layerClass,
+ images
+ })
+ }
+ this.layerResultImages = results
+ setTimeout(() => {
+ this.showIntermediateResults()
+ }, 0)
+ },
+
+ showIntermediateResults: function () {
+ this.layerResultImages.forEach((result, layerNum) => {
+ const scalingFactor = this.layerDisplayConfig[result.name].scalingFactor
+ result.images.forEach((image, imageNum) => {
+ let ctx = document.getElementById(`intermediate-result-${layerNum}-${imageNum}`).getContext('2d')
+ ctx.putImageData(image, 0, 0)
+ let ctxScaled = document.getElementById(`intermediate-result-${layerNum}-${imageNum}-scaled`).getContext('2d')
+ ctxScaled.save()
+ ctxScaled.scale(scalingFactor, scalingFactor)
+ ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
+ ctxScaled.drawImage(document.getElementById(`intermediate-result-${layerNum}-${imageNum}`), 0, 0)
+ ctxScaled.restore()
+ })
+ })
+ },
+
+ clearIntermediateResults: function () {
+ this.layerResultImages.forEach((result, layerNum) => {
+ const scalingFactor = this.layerDisplayConfig[result.name].scalingFactor
+ result.images.forEach((image, imageNum) => {
+ let ctxScaled = document.getElementById(`intermediate-result-${layerNum}-${imageNum}-scaled`).getContext('2d')
+ ctxScaled.save()
+ ctxScaled.scale(scalingFactor, scalingFactor)
+ ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
+ ctxScaled.restore()
+ })
+ })
+ },
+
+ toggleGpu: function () {
+ this.model.gpu = !this.useGpu
+ }
+ }
+})