diff --git a/demos/src/_variables.css b/demos/src/_variables.css
index 4360544..29f3208 100644
--- a/demos/src/_variables.css
+++ b/demos/src/_variables.css
@@ -1,6 +1,7 @@
$color-1: #1BBC9B;
$color-2: #69707a;
$color-3: #393E46;
+$color-4: #E87E04;
$font-1: 'Fira Sans', sans-serif;
$font-2: 'Inconsolata', sans-serif;
diff --git a/demos/src/mnist-vae.css b/demos/src/mnist-vae.css
index d92db3d..1d3b015 100644
--- a/demos/src/mnist-vae.css
+++ b/demos/src/mnist-vae.css
@@ -11,8 +11,7 @@
justify-content: flex-end;
.input-container {
- display: inline-flex;
- flex-direction: column;
+ text-align: right;
margin: 20px;
position: relative;
@@ -32,20 +31,50 @@
}
.canvas-container {
+ position: relative;
display: inline-flex;
justify-content: flex-end;
margin: 10px 0;
+ border: 15px solid rgba(27, 188, 155, 0.3);
+ transition: border-color 0.2s ease-in;
+
+ &:hover {
+ border-color: rgba(27, 188, 155, 0.6);
+ }
canvas {
background: white;
- border: 15px solid rgba(27, 188, 155, 0.3);
- transition: border-color 0.2s ease-in;
&:hover {
- border-color: rgba(27, 188, 155, 0.6);
cursor: crosshair;
}
}
+
+ .axis {
+ position: absolute;
+ cursor: default;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-family: $font-2;
+ font-size: 14px;
+ color: $color-1;
+ }
+
+ .axis.x-axis {
+ right: 0;
+ bottom: -45px;
+ width: 200px;
+ flex-direction: row;
+ }
+
+ .axis.y-axis {
+ top: 0;
+ left: -55px;
+ height: 200px;
+ flex-direction: column;
+ }
}
}
}
@@ -66,6 +95,65 @@
}
.column.output-column {
- justify-content: center;
+ justify-content: flex-start;
+
+ .output {
+ border-radius: 10px;
+ overflow: hidden;
+
+ canvas {
+ background: white;
+ }
+ }
+ }
+
+ .layer-results-container {
+ position: relative;
+
+ .bg-line {
+ position: absolute;
+ z-index: 0;
+ top: 0;
+ left: 50%;
+ background: white;
+ width: 15px;
+ height: 100%;
+ }
+
+ .layer-result {
+ position: relative;
+ z-index: 1;
+ margin: 30px 20px;
+ background: white;
+ border-radius: 10px;
+ padding: 20px;
+ overflow-x: auto;
+
+ .layer-result-heading {
+ font-size: 1rem;
+ color: #999999;
+ margin-bottom: 10px;
+ display: flex;
+ flex-direction: column;
+ font-size: 12px;
+
+ span.layer-class {
+ color: $color-1;
+ font-size: 14px;
+ font-weight: bold;
+ }
+ }
+
+ .layer-result-canvas-container {
+ display: inline-flex;
+ flex-wrap: wrap;
+ background: white;
+
+ canvas {
+ border: 1px solid lightgray;
+ margin: 1px;
+ }
+ }
+ }
}
}
diff --git a/demos/src/mnist-vae.js b/demos/src/mnist-vae.js
index 061e0ba..4843b85 100644
--- a/demos/src/mnist-vae.js
+++ b/demos/src/mnist-vae.js
@@ -2,7 +2,6 @@
import './mnist-vae.css'
-import debounce from 'lodash/debounce'
import * as utils from './utils'
const MODEL_CONFIG = {
@@ -26,13 +25,17 @@ if (process.env.NODE_ENV === 'production') {
const LAYER_DISPLAY_CONFIG = {
'dense_10': {
- heading: 'ReLU activation, output dimensions = 128',
+ heading: 'input dimensions = 2, output dimensions = 128, ReLU activation',
scalingFactor: 2
},
'dense_11': {
heading: 'ReLU activation, output dimensions = 25088 (64 x 14 x 14)',
scalingFactor: 2
},
+ 'reshape_4': {
+ heading: '',
+ scalingFactor: 2
+ },
'deconvolution2d_10': {
heading: '64 3x3 filters, border mode same, 1x1 strides, ReLU activation',
scalingFactor: 2
@@ -66,21 +69,41 @@ export const MnistVae = Vue.extend({
-
+
Use GPU
+
+
x: {{ inputCoordinates[0] < 0 ? inputCoordinates[0].toFixed(2) : inputCoordinates[0].toFixed(3) }}
+
y: {{ inputCoordinates[1] < 0 ? inputCoordinates[1].toFixed(2) : inputCoordinates[1].toFixed(3) }}
+
+
+
@@ -115,10 +138,10 @@ export const MnistVae = Vue.extend({
return {
model: new KerasJS.Model(MODEL_CONFIG),
modelLoading: true,
- input: new Float32Array(2),
output: new Float32Array(27 * 27),
crosshairsActivated: false,
- coordinates: [0, 0],
+ inputCoordinates: [0, 0],
+ position: [100, 100],
layerResultImages: [],
layerDisplayConfig: LAYER_DISPLAY_CONFIG,
useGpu: MODEL_CONFIG.gpu
@@ -140,35 +163,56 @@ export const MnistVae = Vue.extend({
})
},
+ ready: function () {
+ this.drawPosition()
+ },
+
methods: {
+ toggleGpu: function () {
+ this.model.gpu = !this.useGpu
+ },
+
activateCrosshairs: function (e) {
this.crosshairsActivated = true
},
deactivateCrosshairs: function (e) {
this.crosshairsActivated = false
+ this.draw(e)
},
- drawCrosshairs: function (e) {
- if (!this.crosshairsActivated) return
-
+ draw: function (e) {
const [x, y] = this.getEventCanvasCoordinates(e)
const ctx = document.getElementById('input-canvas').getContext('2d')
ctx.clearRect(0, 0, 200, 200)
- ctx.strokeStyle = '#1BBC9B'
+
+ this.drawPosition()
+
+ if (this.crosshairsActivated) {
+ 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()
+ }
+ },
+
+ drawPosition: function () {
+ const ctx = document.getElementById('input-canvas').getContext('2d')
+ ctx.clearRect(0, 0, 200, 200)
+ ctx.fillStyle = '#E87E04'
ctx.beginPath()
- ctx.moveTo(x, 0)
- ctx.lineTo(x, 200)
- ctx.stroke()
- ctx.beginPath()
- ctx.moveTo(0, y)
- ctx.lineTo(200, y)
- ctx.stroke()
+ ctx.arc(...this.position, 5, 0, Math.PI * 2, true)
+ ctx.closePath()
+ ctx.fill()
},
getEventCanvasCoordinates: function (e) {
- const borderSize = 15
let { clientX, clientY } = e
// for touch event
if (e.touches && e.touches.length) {
@@ -178,20 +222,45 @@ export const MnistVae = Vue.extend({
const canvas = document.getElementById('input-canvas')
const { left, top } = canvas.getBoundingClientRect()
- const [x, y] = [clientX - left - borderSize, clientY - top - borderSize]
+ const [x, y] = [clientX - left, clientY - top]
return [x, y]
},
- selectCoordinate: function (e) {
+ selectCoordinates: function (e) {
+ const [x, y] = this.getEventCanvasCoordinates(e)
+ if (!this.model.isRunning) {
+ this.position = [x, y]
+ this.inputCoordinates = [x * 3 / 200 - 1.5, y * 3 / 200 - 1.5]
+ this.draw(e)
+ const inputData = {
+ 'input_4': new Float32Array(this.inputCoordinates)
+ }
+ const outputData = this.model.predict(inputData)
+ this.output = outputData['convolution2d_8']
+ this.drawOutput()
+ this.getIntermediateResults()
+ }
+ },
+ drawOutput: function () {
+ const ctx = document.getElementById('output-canvas').getContext('2d')
+ ctx.putImageData(utils.image2Darray(this.output, 27, 27), 0, 0)
+
+ // scaled up
+ // const ctxScaled = document.getElementById('output-canvas-scaled').getContext('2d')
+ // ctxScaled.save()
+ // ctxScaled.scale(28 / ctxCenterCrop.canvas.width, 28 / ctxCenterCrop.canvas.height)
+ // ctxScaled.clearRect(0, 0, ctxCenterCrop.canvas.width, ctxCenterCrop.canvas.height)
+ // ctxScaled.drawImage(document.getElementById('output-canvas-centercrop'), 0, 0)
+ // const imageDataScaled = ctxScaled.getImageData(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
+ // ctxScaled.restore()
},
getIntermediateResults: function () {
let results = []
for (let [name, layer] of this.model.modelLayersMap.entries()) {
- if (name === 'input') continue
-
const layerClass = layer.layerClass || ''
+ if (layerClass === 'InputLayer') continue
let images = []
if (layer.result && layer.result.tensor.shape.length === 3) {
@@ -240,10 +309,6 @@ export const MnistVae = Vue.extend({
ctxScaled.restore()
})
})
- },
-
- toggleGpu: function () {
- this.model.gpu = !this.useGpu
}
}
})