update mnist convnet demo

This commit is contained in:
Leon Chen
2016-09-19 15:57:17 -04:00
parent 20a0555cb9
commit 741a5887a1
6 changed files with 137 additions and 52 deletions
+2 -2
View File
@@ -8,5 +8,5 @@ npm-debug.log*
# jupyter
notebooks/**/.ipynb_checkpoints/
# data files
*.hdf5
# data files for demos
demos/data/
+1 -1
View File
@@ -24,6 +24,6 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<script src="/lib/weblas.js"></script>
<script src="/dist/keras.js"></script>
<script src="bundle.js"></script>
<script src="dist/bundle.js"></script>
</body>
</html>
+36 -47
View File
@@ -4,33 +4,14 @@ import './mnist-cnn.css'
import debounce from 'lodash/debounce'
import range from 'lodash/range'
import * as utils from './utils'
/**
* Find mindpoint of two points
*/
const getMidpoint = (p1, p2) => {
const [x1, y1] = p1
const [x2, y2] = p2
return [
x1 + (x2 - x1) / 2,
y1 + (y2 - y1) / 2
]
}
/**
* Gets the (x, y) coordinates of an UI event relative to its target,
* e.g., canvas. Accounts for touch events as well as mouse events.
*/
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 MODEL_CONFIG = {
filepaths: {
model: '/demos/data/mnist_cnn/mnist_cnn.json',
weights: '/demos/data/mnist_cnn/mnist_cnn_weights.buf',
metadata: '/demos/data/mnist_cnn/mnist_cnn_metadata.json'
}
const { left, top } = e.target.getBoundingClientRect()
const [x, y] = [clientX - left, clientY - top]
return [x, y]
}
/**
@@ -60,7 +41,8 @@ export const MnistCnn = Vue.extend({
@touchend="deactivateDrawAndPredict"
@touchmove="draw"
></canvas>
<canvas id="input-canvas-scaled" width="28" height="28" style="display: none;"></canvas>
<canvas id="input-canvas-scaled" width="28" height="28" style="display:none;"></canvas>
<canvas id="input-canvas-centercrop" style="display:none;"></canvas>
</div>
<div class="input-clear" v-on:click="clear">
<i class="material-icons">clear</i>CLEAR
@@ -81,16 +63,15 @@ export const MnistCnn = Vue.extend({
</div>
</div>
</div>
<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'
}),
model: new KerasJS.Model(MODEL_CONFIG),
modelLoading: true,
input: new Float32Array(784),
output: new Float32Array(10),
@@ -120,19 +101,15 @@ export const MnistCnn = Vue.extend({
})
},
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)
const ctxCenterCrop = document.getElementById('input-canvas-centercrop').getContext('2d')
ctxCenterCrop.clearRect(0, 0, ctxCenterCrop.canvas.width, ctxCenterCrop.canvas.height)
const ctxScaled = document.getElementById('input-canvas-scaled').getContext('2d')
ctxScaled.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
this.output = new Float32Array(10)
this.drawing = false
this.strokes = []
@@ -142,7 +119,7 @@ export const MnistCnn = Vue.extend({
this.drawing = true
this.strokes.push([])
let points = this.strokes[this.strokes.length - 1]
points.push(getCoordinates(e))
points.push(utils.getCoordinates(e))
},
draw: function (e) {
@@ -150,14 +127,14 @@ export const MnistCnn = Vue.extend({
const ctx = document.getElementById('input-canvas').getContext('2d')
ctx.lineWidth = 15
ctx.lineWidth = 20
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))
points.push(utils.getCoordinates(e))
// draw individual strokes
for (let s = 0, slen = this.strokes.length; s < slen; s++) {
@@ -171,7 +148,7 @@ export const MnistCnn = Vue.extend({
// draw points in stroke
// quadratic bezier curve
for (let i = 1, len = points.length; i < len; i++) {
ctx.quadraticCurveTo(...p1, ...getMidpoint(p1, p2))
ctx.quadraticCurveTo(...p1, ...utils.getMidpoint(p1, p2))
p1 = points[i]
p2 = points[i + 1]
}
@@ -182,15 +159,27 @@ export const MnistCnn = Vue.extend({
deactivateDrawAndPredict: debounce(function () {
if (!this.drawing) return
this.drawing = false
const ctx = document.getElementById('input-canvas').getContext('2d')
const ctxScaled = document.getElementById('input-canvas-scaled').getContext('2d')
ctxScaled.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
ctxScaled.drawImage(document.getElementById('input-canvas'), 0, 0)
const imageDataScaled = ctxScaled.getImageData(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
// center crop
const imageDataCenterCrop = utils.centerCrop(ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height))
const ctxCenterCrop = document.getElementById('input-canvas-centercrop').getContext('2d')
ctxCenterCrop.canvas.width = imageDataCenterCrop.width
ctxCenterCrop.canvas.height = imageDataCenterCrop.height
ctxCenterCrop.putImageData(imageDataCenterCrop, 0, 0)
// scaled to 28 x 28
const ctxScaled = document.getElementById('input-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('input-canvas-centercrop'), 0, 0)
const imageDataScaled = ctxScaled.getImageData(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height)
ctxScaled.restore()
// process image data for model input
const { data } = imageDataScaled
this.input = new Float32Array(784)
for (let i = 0, len = data.length; i < len; i += 4) {
+96
View File
@@ -0,0 +1,96 @@
/* global ImageData */
/**
* Find mindpoint of two points
*/
export function getMidpoint (p1, p2) {
const [x1, y1] = p1
const [x2, y2] = p2
return [
x1 + (x2 - x1) / 2,
y1 + (y2 - y1) / 2
]
}
/**
* Gets the (x, y) coordinates of an UI event relative to its target,
* e.g., canvas. Accounts for touch events as well as mouse events.
*/
export function 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]
}
/**
* Centers and crops canvas ImageData based on alpha channel.
* @param {ImageData} imageData
* @returns {ImageData}
*/
export function centerCrop (imageData) {
const { data, width, height } = imageData
let [xmin, ymin] = [width, height]
let [xmax, ymax] = [-1, -1]
for (let i = 0; i < width; i++) {
for (let j = 0; j < height; j++) {
const idx = (i + j * width)
if (data[4 * idx + 3] > 0) {
if (i < xmin) xmin = i
if (i > xmax) xmax = i
if (j < ymin) ymin = j
if (j > ymax) ymax = j
}
}
}
// add a little padding
xmin -= 20
xmax += 20
ymin -= 20
ymax += 20
// make bounding box square
let [widthNew, heightNew] = [(xmax - xmin + 1), (ymax - ymin + 1)]
if (widthNew < heightNew) {
// new width < new height
const halfBefore = Math.floor((heightNew - widthNew) / 2)
const halfAfter = heightNew - widthNew - halfBefore
xmax += halfAfter
xmin -= halfBefore
} else if (widthNew > heightNew) {
// new width > new height
const halfBefore = Math.floor((widthNew - heightNew) / 2)
const halfAfter = widthNew - heightNew - halfBefore
ymax += halfAfter
ymin -= halfBefore
}
widthNew = xmax - xmin + 1
heightNew = ymax - ymin + 1
let dataNew = new Uint8ClampedArray(widthNew * heightNew * 4)
for (let i = xmin; i <= xmax; i++) {
for (let j = ymin; j <= ymax; j++) {
if (i >= 0 && i < width && j >= 0 && j < height) {
const idx = (i + j * width)
const idxNew = ((i - xmin) + (j - ymin) * widthNew)
dataNew[4 * idxNew + 3] = data[4 * idx + 3]
}
}
}
return new ImageData(dataNew, widthNew, heightNew)
}
/**
* Takes in a ndarray of shape [x, y, z]
* and creates ImageData layed out as [x*z, y]
*/
export function flatten3DTensor (imageData) {
}
+1 -1
View File
@@ -7,7 +7,7 @@ module.exports = {
path.join(__dirname, 'src/index')
],
output: {
path: __dirname,
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
devtool: 'cheap-module-eval-source-map',
+1 -1
View File
@@ -7,7 +7,7 @@ module.exports = {
path.join(__dirname, 'src/index')
],
output: {
path: __dirname,
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {