start demos

This commit is contained in:
Leon Chen
2016-09-18 19:57:07 -04:00
parent 790377e49a
commit b1af2a7751
8 changed files with 474 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Keras.js Demos</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.1.2/css/bulma.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app">
<div class="columns">
<div class="column is-3" style="max-width: 450px;">
<menu></menu>
</div>
<div class="column">
<component :is="currentView"></component>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<script src="/dist/keras.js"></script>
<script src="bundle.js"></script>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
/* global Vue */
export const Home = Vue.extend({
template: `
<div class="demo home">
<div class="title">Home</div>
</div>
`
})
+33
View File
@@ -0,0 +1,33 @@
/* global Vue */
import { Menu } from './menu'
import { Home } from './home'
import { MnistCnn } from './mnist-cnn'
Vue.component('menu', Menu)
Vue.component('home', Home)
Vue.component('mnist-cnn', MnistCnn)
const app = new Vue({
el: '#app',
data: {
currentView: 'home'
}
})
// Simple routing
function matchRoute () {
const routes = ['mnist-cnn']
const { hash } = window.location
const route = hash.substr(2)
if (routes.indexOf(route) > -1) {
app.currentView = route
} else {
app.currentView = 'home'
}
}
window.addEventListener('load', matchRoute)
window.addEventListener('hashchange', matchRoute)
+35
View File
@@ -0,0 +1,35 @@
/* global Vue */
export const Menu = Vue.extend({
template: `
<aside class="menu">
<h1>Keras.js Demos</h1>
<p class="menu-label">
Demos
</p>
<ul class="menu-list">
<li><a href="#/">Home</a></li>
<li><a href="#/mnist-cnn">Basic Convnet - MNIST</a></li>
</ul>
<p class="menu-label">
Links
</p>
<ul class="menu-list">
<li>
<a href="https://github.com/transcranial/keras-js" target="_blank">
<svg height="32" width="32" class="github-logo" viewBox="0 0 16 16" version="1.1" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg>
GitHub repo
</a>
</li>
</ul>
<p class="menu-label">
Contact
</p>
<ul class="menu-list contact">
<li><a href="https://github.com/transcranial" target="_blank">Leon Chen <@transcranial></a></li>
<li></li>
</ul>
</aside>
`
})
+157
View File
@@ -0,0 +1,157 @@
/* global Vue */
import debounce from 'lodash/debounce'
const getMidpoint = (p1, p2) => {
const [x1, y1] = p1
const [x2, y2] = p2
return [
x1 + (x2 - x1) / 2,
y1 + (y2 - y1) / 2
]
}
const getCoordinates = e => {
let { clientX, clientY } = e
// for touch event
if (e.touches && e.touches.length) {
clientX = e.touches[0].clientX
clientY = e.touches[0].clientY
}
const { left, top } = e.target.getBoundingClientRect()
const [x, y] = [clientX - left, clientY - top]
return [x, y]
}
export const MnistCnn = Vue.extend({
template: `
<div class="demo mnist-cnn">
<div class="title">Basic Convnet - MNIST</div>
<div class="loading-progress" v-if="modelLoading && loadingProgress < 100">
Loading...{{ loadingProgress }}%
</div>
<div class="input-container">
<div class="input-label">Draw any digit (0-9) here <span class="arrow">⤸</span></div>
<div class="canvas-container">
<canvas
id="input-canvas" width="240" height="240"
v-on:mousedown="activateDraw"
v-on:mouseup="deactivateDraw"
v-on:mousemove="draw"
v-on:touchstart="activateDraw"
v-on:touchend="deactivateDraw"
v-on:touchmove="draw"
></canvas>
<canvas id="input-canvas-scaled" width="28" height="28" style="display: none;"></canvas>
</div>
<div class="input-clear" v-on:click="clear">
<i class="material-icons">clear</i>CLEAR
</div>
</div>
<div class="columns">
<div class="column">
</div>
<div class="column">
</div>
</div>
</div>
`,
data: function () {
return {
model: new KerasJS.Model({
model: '/demos/mnist_cnn/mnist_cnn.json',
weights: '/demos/mnist_cnn/mnist_cnn_weights.buf',
metadata: '/demos/mnist_cnn/mnist_cnn_metadata.json'
}),
modelLoading: true,
inputData: {
'input': new Float32Array(784)
},
drawing: false,
strokes: []
}
},
computed: {
loadingProgress: function () {
return this.model.getLoadingProgress()
}
},
created: function () {
// initialize KerasJS model
this.model.initialize()
this.model.ready().then(() => {
this.modelLoading = false
})
},
ready: function () {
// initialize scaling helper canvas
const ctxScaled = document.getElementById('input-canvas-scaled').getContext('2d')
ctxScaled.scale(28 / 240, 28 / 240)
},
methods: {
clear: function (e) {
const ctx = document.getElementById('input-canvas').getContext('2d')
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
this.drawing = false
this.strokes = []
},
activateDraw: function (e) {
this.drawing = true
this.strokes.push([])
let points = this.strokes[this.strokes.length - 1]
points.push(getCoordinates(e))
},
deactivateDraw: function (e) {
this.drawing = false
this.processCanvasData()
this.model.predict(this.inputData)
},
draw: function (e) {
if (!this.drawing) return
const ctx = document.getElementById('input-canvas').getContext('2d')
ctx.lineWidth = 12
ctx.lineJoin = ctx.lineCap = 'round'
ctx.strokeStyle = '#393E46'
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
let points = this.strokes[this.strokes.length - 1]
points.push(getCoordinates(e))
// draw individual strokes
for (let s = 0, slen = this.strokes.length; s < slen; s++) {
points = this.strokes[s]
let p1 = points[0]
let p2 = points[1]
ctx.beginPath()
ctx.moveTo(...p1)
// draw points in stroke
// quadratic bezier curve
for (let i = 1, len = points.length; i < len; i++) {
ctx.quadraticCurveTo(...p1, ...getMidpoint(p1, p2))
p1 = points[i]
p2 = points[i + 1]
}
ctx.lineTo(...p1)
ctx.stroke()
}
},
processCanvasData: function () {
const ctxScaled = document.getElementById('input-canvas-scaled').getContext('2d')
ctxScaled.drawImage(document.getElementById('input-canvas'), 0, 0)
const imageDataScaled = ctxScaled.getImageData(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
const { data } = imageDataScaled
for (let i = 0, len = data.length; i < len; i += 4) {
this.inputData['input'][i / 4] = data[i + 3] / 255
}
}
}
})
+144
View File
@@ -0,0 +1,144 @@
@import 'https://fonts.googleapis.com/css?family=Inconsolata';
@import 'https://fonts.googleapis.com/css?family=Fira+Sans';
@import 'https://fonts.googleapis.com/css?family=Nothing+You+Could+Do';
body {
background: #EEEEEE;
color: #393E46;
min-height: 100vh;
font-family: 'Fira Sans', sans-serif;
}
.github-logo {
fill: #69707a;
margin: 3px 8px 3px 0;
}
.title {
width: 100%;
color: #1BBC9B;
display: flex;
flex-direction: row;
align-items: center;
}
.subtitle {
color: #393E46;
}
.menu {
padding: 50px;
margin: 20px;
background: white;
border-radius: 10px;
}
.menu h1 {
color: #CCCCCC;
font-family: 'Fira Sans', sans-serif;
font-size: 1.5rem;
}
.menu-list li {
color: #69707a;
}
.menu-list a {
display: flex;
align-items: center;
}
.menu-list a:hover {
color: #1BBC9B;
background-color: whitesmoke;
}
.menu-list a:hover .github-logo {
fill: #1BBC9B;
}
.menu-list.contact li {
padding: 5px 10px;
}
.menu-list.contact a {
color: #aaaaaa;
padding: 0;
display: inline-flex;
background-color: none;
transition: color 0.2s ease-in;
}
.menu-list.contact a:hover {
color: #1BBC9B;
background: none;
}
.demo {
padding: 50px 30px;
}
.demo .loading-progress {
position: absolute;
top: 0;
right: 0;
padding: 30px;
color: #1BBC9B;
font-size: 18px;
font-family: 'Inconsolata', sans-serif;
padding: 20px 50px;
margin: 20px;
background: rgba(255, 255, 255, 0.5);
border-radius: 10px;
}
.demo.mnist-cnn .input-container {
display: inline-flex;
flex-direction: column;
margin: 20px;
position: relative;
}
.demo.mnist-cnn .input-label {
font-family: 'Nothing You Could Do', cursive;
font-size: 18px;
color: #69707a;
text-align: right;
}
.demo.mnist-cnn .input-label span.arrow {
font-size: 36px;
color: #CCCCCC;
position: absolute;
right: -32px;
top: 8px;
}
.demo.mnist-cnn .canvas-container {
display: inline-flex;
margin: 10px 0;
background: white;
border: 15px solid rgba(27, 188, 155, 0.3);
transition: border-color 0.2s ease-in;
}
.demo.mnist-cnn .canvas-container:hover {
border-color: rgba(27, 188, 155, 0.6);
}
.demo.mnist-cnn .canvas-container canvas:hover {
cursor: crosshair;
}
.demo.mnist-cnn .input-clear {
display: flex;
align-items: center;
justify-content: flex-end;
color: #69707a;
transition: color 0.2s ease-in;
}
.demo.mnist-cnn .input-clear:hover {
color: rgba(27, 188, 155, 0.3);
cursor: pointer;
}
+30
View File
@@ -0,0 +1,30 @@
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: [
path.join(__dirname, 'src/index')
],
output: {
path: __dirname,
filename: 'bundle.js'
},
devtool: 'cheap-module-eval-source-map',
module: {
loaders: [
{
test: /\.js$/,
loaders: ['babel-loader'],
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['', '.js']
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development')
})
]
}
+37
View File
@@ -0,0 +1,37 @@
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: [
path.join(__dirname, 'src/index')
],
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.js$/,
loaders: ['babel-loader'],
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['', '.js']
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
}