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
JavaScript
;
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