From 40f0f158da4eb1720767f8101cd8a7e99be1ef10 Mon Sep 17 00:00:00 2001
From: Yuriy Maksymets <iurii.maksymets@gmail.com>
Date: Tue, 12 Nov 2019 22:41:00 +0000
Subject: [PATCH] Matrix coefficient base for rect detection

---
 __tests__/shape.test.js |  17 ++++++-
 src/shapes.js           | 103 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 118 insertions(+), 2 deletions(-)

diff --git a/__tests__/shape.test.js b/__tests__/shape.test.js
index 917d617..9ce3fa2 100644
--- a/__tests__/shape.test.js
+++ b/__tests__/shape.test.js
@@ -1,4 +1,7 @@
-import recognizeFromPoints, { Shapes } from "../src/shapes"
+import recognizeFromPoints, {
+  Shapes,
+  computeMatrixCoefficients,
+} from "../src/shapes"
 
 describe("shape recognition", () => {
   describe("general", () => {
@@ -9,6 +12,18 @@ describe("shape recognition", () => {
     })
   })
 
+  describe("matrix coefficients", () => {
+    test("should compute coefficients correctly", () => {
+      const points = [[0, 0], [0, 5], [0, 10], [10, 5]]
+      const coefficients = computeMatrixCoefficients(points)
+      expect(coefficients).toStrictEqual([
+        [1 / 4, 1 / 4, 1 / 4],
+        [0, 0, 0],
+        [0, 1 / 4, 0],
+      ])
+    })
+  })
+
   describe("lines", () => {
     test("should recognize a simple horizontal line", () => {
       const points = [[0, 0], [100, 0]]
diff --git a/src/shapes.js b/src/shapes.js
index c5a37bf..af78778 100644
--- a/src/shapes.js
+++ b/src/shapes.js
@@ -56,7 +56,98 @@ function constructHoughAccumulator(accumulator, x, y) {
   }
 }
 
-function recognizeFromPoints(points) {
+function boundingCoords(points) {
+  const xs = points.map((p) => p[0])
+  const ys = points.map((p) => p[1])
+  return {
+    maxX: Math.max(...xs),
+    minX: Math.min(...xs),
+    maxY: Math.max(...ys),
+    minY: Math.min(...ys),
+  }
+}
+
+const MATRIX_SIZE = 3
+
+function mArray(min, max) {
+  const step = (max - min) / MATRIX_SIZE
+  return Array(MATRIX_SIZE)
+    .fill(min)
+    .map((x, i) => x + step * (i + 1))
+}
+
+function getCluster([x, y], xBounds, yBounds) {
+  return {
+    x: xBounds.findIndex((bound) => x <= bound),
+    y: yBounds.findIndex((bound) => y <= bound),
+  }
+}
+
+function computeClusters(points, xBounds, yBounds) {
+  const clusters = Array(MATRIX_SIZE)
+    .fill(0)
+    .map(() =>
+      Array(MATRIX_SIZE)
+        .fill()
+        .map(() => []),
+    )
+  points.forEach((point) => {
+    const { x, y } = getCluster(point, xBounds, yBounds)
+    clusters[x][y].push(point)
+  })
+  return clusters
+}
+
+function clusterCoefficients(clusters, points) {
+  return clusters.map((rowCluster) =>
+    rowCluster.map((cluster) => cluster.length / points.length),
+  )
+}
+
+export function computeMatrixCoefficients(points) {
+  const { maxX, minX, maxY, minY } = boundingCoords(points)
+  const xBounds = mArray(minX, maxX)
+  const yBounds = mArray(minY, maxY)
+  const clusters = computeClusters(points, xBounds, yBounds)
+  const coefficients = clusterCoefficients(clusters, points)
+  return coefficients
+}
+
+function couldBeLine(points) {
+  return points.length >= 2
+}
+
+const RECT_THRESHOLD_CENTER = 0.05
+const RECT_THRESHOLD_SIDE_VARIANCE = 0.2
+
+function couldBeRect(points) {
+  if (points.length < 4) return false
+  const matrixCoefficients = computeMatrixCoefficients(points)
+
+  let [maxC, minC] = [0, 1]
+  for (let i = 0; i < 3; i++) {
+    for (let j = 0; j < 3; j++) {
+      if (!(i === j && j === 1)) {
+        maxC = Math.max(maxC, matrixCoefficients[i][j])
+        minC = Math.min(minC, matrixCoefficients[i][j])
+      }
+    }
+  }
+
+  if (
+    matrixCoefficients[1][1] < RECT_THRESHOLD_CENTER &&
+    maxC - minC < RECT_THRESHOLD_SIDE_VARIANCE
+  ) {
+    return true
+  }
+  return false
+}
+
+function recognizeRect(points) {
+  return { points }
+}
+
+function recognizeLine(points) {
   if (!(points && points.length)) return {}
   const accum = Array(numAngleCells)
   points.forEach((x) => constructHoughAccumulator(accum, ...x))
@@ -74,6 +165,16 @@ function recognizeFromPoints(points) {
   return {}
 }
 
+function recognizeFromPoints(points) {
+  if (couldBeRect(points)) {
+    return recognizeRect(points)
+  } else if (couldBeLine(points)) {
+    return recognizeLine(points)
+  }
+
+  return {}
+}
+
 export const Shapes = {
   rectangle: "rect",
   line: "line",
-- 
GitLab