mirror of
https://github.com/wassname/keras-js.git
synced 2026-09-11 12:20:53 +08:00
update mnist convnet demo
This commit is contained in:
+2
-2
@@ -8,5 +8,5 @@ npm-debug.log*
|
||||
# jupyter
|
||||
notebooks/**/.ipynb_checkpoints/
|
||||
|
||||
# data files
|
||||
*.hdf5
|
||||
# data files for demos
|
||||
demos/data/
|
||||
|
||||
+1
-1
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -7,7 +7,7 @@ module.exports = {
|
||||
path.join(__dirname, 'src/index')
|
||||
],
|
||||
output: {
|
||||
path: __dirname,
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
module: {
|
||||
|
||||
Reference in New Issue
Block a user