UNPKG

handwritten-mathematics-recogniser

Version:

Easy and abstracted way to recognise handwritten mathematics in a browser or in a web view.

193 lines (191 loc) 7.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const converter_1 = require("./converter"); const transformer_1 = require("./transformer"); class Segmenter { static getLayers(image) { const rows = image.length; const columns = image[0].length; const layers = []; for (let y = 0; y < rows; y++) { for (let x = 0; x < columns; x++) { if (image[y][x] === 0) { const layer = this.initialiseArray(columns, rows); layers.push(this.getLayer(x, y, image, layer)); } } } return layers .map((layer) => (Object.assign({}, layer, { image: this.doThinning(layer.image) }))) .sort((layer1, layer2) => layer1.center.x - layer2.center.x); } static getLayer(x, y, image, layer) { layer = this.connect(x, y, image, layer); const center = transformer_1.Transformer.getCenter(layer); const boundingBox = transformer_1.Transformer.getBoundingBox(layer); const width = boundingBox.max.x - boundingBox.min.x + 1; const height = boundingBox.max.y - boundingBox.min.y + 1; const croppedImage = this.cropImage(layer, boundingBox); const size = 128; const scale = size / Math.max(width, height); const canvas = converter_1.Converter.convertImageToCanvas(croppedImage); const context = canvas.getContext('2d'); const canvasCopy = document.createElement('canvas'); canvasCopy.width = size; canvasCopy.height = size; const copyCtx = canvasCopy.getContext('2d'); copyCtx.scale(scale, scale); copyCtx.drawImage(context.canvas, 0, 0); let transformed = converter_1.Converter.convertCanvasToImage(canvasCopy); const boundingBox2 = transformer_1.Transformer.getBoundingBox(transformed); const center2 = transformer_1.Transformer.getCenter(transformed); copyCtx.clearRect(0, 0, 10000, 10000); if (width === Math.max(width, height)) { copyCtx.translate(0, (canvasCopy.height / 2 - center2.y) * (1 / scale)); } else { copyCtx.translate((canvasCopy.width / 2 - center2.x) * (1 / scale), 0); } copyCtx.drawImage(context.canvas, 0, 0); transformed = converter_1.Converter.convertCanvasToImage(canvasCopy); transformed = transformer_1.Transformer.subsample(transformed, size, 32); return { center, boundingBox, width, height, image: transformed }; } static connect(x, y, image, layer) { const columns = image[0].length; const rows = image.length; image[y][x] = 1; layer[y][x] = 0; if (y + 1 < rows && image[y + 1][x] === 0) { this.connect(x, y + 1, image, layer); } if (y - 1 >= 0 && image[y - 1][x] === 0) { this.connect(x, y - 1, image, layer); } if (x + 1 < columns && image[y][x + 1] === 0) { this.connect(x + 1, y, image, layer); } if (x - 1 >= 0 && image[y][x - 1] === 0) { this.connect(x - 1, y, image, layer); } return layer; } static cropImage(layer, boundingBox) { const columns = boundingBox.max.x - boundingBox.min.x + 1; const rows = boundingBox.max.y - boundingBox.min.y + 1; const image = this.initialiseArray(columns, rows); for (let y = 0; y < rows; y++) { for (let x = 0; x < columns; x++) { image[y][x] = layer[boundingBox.min.y + y][boundingBox.min.x + x]; } } return image; } static initialiseArray(columns, rows) { const arr = Array.from({ length: rows }, () => Array.from({ length: columns }, () => 1)); return arr; } static doThinning(givenImage) { const image = []; for (let y = 0; y < givenImage.length; y++) { image[y] = []; for (let x = 0; x < givenImage[y].length; x++) { image[y][x] = 1 - givenImage[y][x]; } } let a; let b; let pointsToChange = []; let hasChange; do { hasChange = false; for (let y = 1; y + 1 < image.length; y++) { for (let x = 1; x + 1 < image[y].length; x++) { a = this.getA(image, y, x); b = this.getB(image, y, x); if (image[y][x] === 1 && 2 <= b && b <= 6 && a === 1 && (image[y - 1][x] * image[y][x + 1] * image[y + 1][x] === 0) && (image[y][x + 1] * image[y + 1][x] * image[y][x - 1] === 0)) { pointsToChange.push({ x, y }); hasChange = true; } } } for (const point of pointsToChange) { image[point.y][point.x] = 0; } pointsToChange = []; for (let y = 1; y + 1 < image.length; y++) { for (let x = 1; x + 1 < image[y].length; x++) { a = this.getA(image, y, x); b = this.getB(image, y, x); if (image[y][x] === 1 && 2 <= b && b <= 6 && a === 1 && (image[y - 1][x] * image[y][x + 1] * image[y][x - 1] === 0) && (image[y - 1][x] * image[y + 1][x] * image[y][x - 1] === 0)) { pointsToChange.push({ x, y }); hasChange = true; } } } for (const point of pointsToChange) { image[point.y][point.x] = 0; } pointsToChange = []; } while (hasChange); for (let y = 0; y < image.length; y++) { for (let x = 0; x < image[y].length; x++) { image[y][x] = 1 - image[y][x]; } } return image; } static getA(image, y, x) { let count = 0; if (y - 1 >= 0 && x + 1 < image[y].length && image[y - 1][x] === 0 && image[y - 1][x + 1] === 1) { count++; } if (y - 1 >= 0 && x + 1 < image[y].length && image[y - 1][x + 1] === 0 && image[y][x + 1] === 1) { count++; } if (y + 1 < image.length && x + 1 < image[y].length && image[y][x + 1] === 0 && image[y + 1][x + 1] === 1) { count++; } if (y + 1 < image.length && x + 1 < image[y].length && image[y + 1][x + 1] == 0 && image[y + 1][x] === 1) { count++; } if (y + 1 < image.length && x - 1 >= 0 && image[y + 1][x] === 0 && image[y + 1][x - 1] == 1) { count++; } if (y + 1 < image.length && x - 1 >= 0 && image[y + 1][x - 1] === 0 && image[y][x - 1] == 1) { count++; } if (y - 1 >= 0 && x - 1 >= 0 && image[y][x - 1] === 0 && image[y - 1][x - 1] == 1) { count++; } if (y - 1 >= 0 && x - 1 >= 0 && image[y - 1][x - 1] === 0 && image[y - 1][x] == 1) { count++; } return count; } static getB(image, y, x) { return image[y - 1][x] + image[y - 1][x + 1] + image[y][x + 1] + image[y + 1][x + 1] + image[y + 1][x] + image[y + 1][x - 1] + image[y][x - 1] + image[y - 1][x - 1]; } } exports.Segmenter = Segmenter; //# sourceMappingURL=segmenter.js.map