mirror of
https://github.com/wassname/keras-js.git
synced 2026-09-09 11:25:25 +08:00
start demos
This commit is contained in:
@@ -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>
|
||||
@@ -0,0 +1,9 @@
|
||||
/* global Vue */
|
||||
export const Home = Vue.extend({
|
||||
template: `
|
||||
<div class="demo home">
|
||||
<div class="title">Home</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
})
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
`
|
||||
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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')
|
||||
})
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user