{"version":3,"sources":["augmentedReality/imageRecognition/tensorflow.ts","augmentedReality/solver/sudokuSolver.ts","augmentedReality/imageProcessing/getLargestConnectedComponent.ts","augmentedReality/imageProcessing/findHomographicTransform.ts","augmentedReality/imageProcessing/Image.ts","augmentedReality/imageProcessing/boxBlur.ts","augmentedReality/imageProcessing/getCornerPoints.ts","augmentedReality/imageProcessing/applyHomographicTransform.ts","augmentedReality/Processor.ts","augmentedReality/imageProcessing/captureImage.ts","augmentedReality/imageProcessing/adaptiveThreshold.ts","augmentedReality/imageProcessing/extractBoxes.ts","components/StatsPanel.tsx","App.tsx","serviceWorker.ts","index.tsx"],"names":["setWasmPath","process","MODEL_URL","CLASSES","_model","undefined","modelLoadingPromise","async","loadModel","Promise","resolve","reject","tf","fillInPrediction","boxes","model","logits","images","map","box","img","fromPixels","numberImage","toImageData","resizeBilinear","toFloat","mean","std","variance","sqrt","sub","div","reshape","input","predict","batchSize","length","array","values","maxProb","maxIndex","forEach","value","index","getClasses","className","contents","then","console","log","error","Data","constructor","column","guess","left","right","up","down","this","insertRight","node","insertLeft","insertUp","insertDown","Column","super","size","Guess","x","y","entry","isKnown","SudokuSolver","columnRoot","columnLookup","rowLookup","solution","col","push","entryColIdx","entryColumn","entryConstraint","rowColIdx","rowColumn","rowConstraint","colColIdx","colCol","columnConstraint","boxX","Math","floor","boxColumnIndex","boxColumn","boxConstraint","setNumber","row","cover","getSmallestColumn","smallestSize","smallestColumn","search","depth","Error","uncover","pop","ConnectedRegion","points","topLeft","bottomRight","bounds","width","height","getConnectedComponent","image","bytes","minX","minY","maxX","maxY","frontier","seed","min","max","dy","dx","transformPoint","point","tranform","a","b","c","d","e","f","g","h","sxPre1","sxPre2","syPre1","syPre2","Image","Uint8ClampedArray","clone","subImage","x1","y1","x2","y2","imageData","ImageData","data","readP","precomputed","w","boxBlur","src","boxw","boxh","result","Array","dst","tot","precompute","mul","getNearestPoint","closestPoint","minDistance","Number","MAX_SAFE_INTEGER","distance","abs","extractSquareFromRegion","source","withSize","sx","sy","Processor","EventEmitter","video","isVideoRunning","isProcessing","corners","gridLines","solvedPuzzle","captureTime","thresholdTime","connectedComponentTime","cornerPointTime","extractPuzzleTime","extractBoxesTime","neuralNetTime","solveTime","stream","navigator","mediaDevices","getUserMedia","facingMode","audio","canPlayListener","removeEventListener","emit","videoWidth","videoHeight","processFrame","addEventListener","srcObject","play","createGridLines","transform","l","p1","PROCESSING_SIZE","p2","getTextDetailsForBox","digit","textPosition","digitRotation","atan2","digitHeight","position","createSolvedPuzzle","solver","results","sol","sanityCheckCorners","topRight","bottomLeft","topLineLength","leftLineLength","rightLineLength","bottomLineLength","startTime","performance","now","canvas","document","createElement","context","getContext","drawImage","getImageData","captureImage","thresholded","threshold","blurSize","blurredBytes","adaptiveThreshold","largestConnectedComponent","minAspectRatio","maxAspectRatio","minSize","maxSize","maxRegion","tmp","region","aspectRatio","getLargestConnectedComponent","potentialCorners","getCornerPoints","A","math","set","B","A_t","lamda","get","findHomographicTransform","extractedImageGreyScale","extractedImageThresholded","greyScale","boxSize","searchSize","pointsCount","searchX1","searchX2","searchY2","searchY","searchX","component","foundWidth","foundHeight","extractBoxes","setTimeout","StatsPanel","imageCaptureTime","getCornerPointsTime","extractImageTime","ocrTime","round","processor","capture","getElementById","App","videoRef","useRef","previewCanvasRef","setVideoWidth","useState","setVideoHeight","setImageCaptureTime","setThresholdTime","setConnectedComponentTime","setGetCornerPOintsTime","setExtractImageTime","setExtractBoxesTime","setOcrTime","setSolveTime","useEffect","current","startVideo","alert","message","interval","window","setInterval","strokeStyle","fillStyle","lineWidth","beginPath","moveTo","lineTo","closePath","stroke","fill","line","font","translate","rotate","PI","fillText","toString","setTransform","mashed","Object","keys","reduce","acc","key","t","join","style","display","toDataURL","clearInterval","videoReadyListener","on","off","ref","playsInline","muted","Boolean","location","hostname","match","ReactDOM","render","StrictMode","serviceWorker","ready","registration","unregister","catch"],"mappings":"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","file":"static/js/main.a1ba4d47.chunk.js","sourcesContent":["import * as tf from \"@tensorflow/tfjs\";\r\nimport { setWasmPath } from \"@tensorflow/tfjs-backend-wasm\";\r\nimport { PuzzleBox } from \"../imageProcessing/extractBoxes\";\r\n\r\nsetWasmPath(`${process.env.PUBLIC_URL}/tfjs-backend-wasm.wasm`);\r\nconst MODEL_URL = `${process.env.PUBLIC_URL}/tfjs_model/model.json`;\r\n\r\nconst CLASSES = [1, 2, 3, 4, 5, 6, 7, 8, 9];\r\nconst IMAGE_SIZE = 20;\r\nlet _model: tf.LayersModel = undefined;\r\nlet modelLoadingPromise: Promise = undefined;\r\n\r\nasync function loadModel() {\r\n if (_model) {\r\n return _model;\r\n }\r\n if (modelLoadingPromise) {\r\n return modelLoadingPromise;\r\n }\r\n modelLoadingPromise = new Promise(async (resolve, reject) => {\r\n await tf.setBackend(\"wasm\");\r\n _model = await tf.loadLayersModel(MODEL_URL);\r\n resolve(_model);\r\n });\r\n}\r\nloadModel().then(() => console.log(\"Model Loaded\", console.error));\r\n\r\n/**\r\n * Work out what the class should be from the results of the neural network prediction\r\n * @param logits\r\n */\r\nexport async function getClasses(logits: tf.Tensor) {\r\n const logitsArray = (await logits.array()) as number[][];\r\n const classes = logitsArray.map((values) => {\r\n let maxProb = 0;\r\n let maxIndex = 0;\r\n values.forEach((value, index) => {\r\n if (value > maxProb) {\r\n maxProb = value;\r\n maxIndex = index;\r\n }\r\n });\r\n return CLASSES[maxIndex];\r\n });\r\n return classes;\r\n}\r\n\r\n/**\r\n * Apply our neural network to the extracted images\r\n * @param boxes A set of puzzle boxes containing images\r\n */\r\nexport default async function fillInPrediction(boxes: PuzzleBox[]) {\r\n const model = await loadModel();\r\n const logits = tf.tidy(() => {\r\n // convert the images into tensors and process them in the same way we did during training\r\n // if you change the code in the training then update the code here\r\n const images = boxes.map((box) => {\r\n const img = tf.browser\r\n .fromPixels(box.numberImage.toImageData(), 1)\r\n .resizeBilinear([IMAGE_SIZE, IMAGE_SIZE])\r\n .toFloat();\r\n const mean = img.mean();\r\n const std = tf.moments(img).variance.sqrt();\r\n const normalized = img.sub(mean).div(std);\r\n const batched = normalized.reshape([1, IMAGE_SIZE, IMAGE_SIZE, 1]);\r\n return batched;\r\n });\r\n // concatentate all the images for processing all at once\r\n const input = tf.concat(images);\r\n // Make the predictions\r\n return model.predict(input, {\r\n batchSize: boxes.length,\r\n });\r\n });\r\n // Convert logits to probabilities and class names.\r\n const classes = await getClasses(logits as tf.Tensor);\r\n // fill in the boxes with the results\r\n classes.forEach((className, index) => (boxes[index].contents = className));\r\n}\r\n","// cicular linked list element with links up, down, left and right\r\nclass Data {\r\n left: Data;\r\n right: Data;\r\n up: Data;\r\n down: Data;\r\n column: Column | null;\r\n guess: Guess | null;\r\n constructor(column: Column = null, guess: Guess = null) {\r\n this.column = column;\r\n this.guess = guess;\r\n // start of pointing at ourself\r\n this.left = this;\r\n this.right = this;\r\n this.up = this;\r\n this.down = this;\r\n }\r\n insertRight(node: Data) {\r\n node.left = this;\r\n node.right = this.right;\r\n this.right.left = node;\r\n this.right = node;\r\n }\r\n insertLeft(node: Data) {\r\n node.right = this;\r\n node.left = this.left;\r\n this.left.right = node;\r\n this.left = node;\r\n }\r\n insertUp(node: Data) {\r\n node.down = this;\r\n node.up = this.up;\r\n this.up.down = node;\r\n this.up = node;\r\n }\r\n insertDown(node: Data) {\r\n node.up = this;\r\n node.down = this.down;\r\n this.down.up = node;\r\n this.down = node;\r\n }\r\n}\r\n\r\nclass Column extends Data {\r\n size: number;\r\n constructor() {\r\n super();\r\n this.size = 0;\r\n }\r\n}\r\n\r\nclass Guess {\r\n x: number;\r\n y: number;\r\n entry: number;\r\n // flag to indicate if this was a known value - used when displaying the solution\r\n isKnown: boolean = false;\r\n constructor(x: number, y: number, entry: number) {\r\n this.x = x;\r\n this.y = y;\r\n this.entry = entry;\r\n }\r\n}\r\n\r\nexport default class SudokuSolver {\r\n columnRoot: Column; // root column object\r\n columnLookup: Column[] = [];\r\n rowLookup: Data[] = [];\r\n solution: Data[] = []; // the solution\r\n\r\n // Setup the circular lists for the X algorithm to work on\r\n public constructor() {\r\n // construct the rows and columns\r\n // https://en.wikipedia.org/wiki/Exact_cover#Sudoku and https://www.stolaf.edu//people/hansonr/sudoku/exactcovermatrix.htm\r\n // https://www.kth.se/social/files/58861771f276547fe1dbf8d1/HLaestanderMHarrysson_dkand14.pdf\r\n\r\n // create a doubly linked list of column headers\r\n this.columnRoot = new Column();\r\n for (let col = 0; col < 81 * 4; col++) {\r\n const column = new Column();\r\n this.columnRoot.insertRight(column);\r\n // stash the column in a quick lookup\r\n this.columnLookup.push(column);\r\n }\r\n // create a doubly linked list of rows and populate the columns for each row\r\n for (let x = 0; x < 9; x++) {\r\n for (let y = 0; y < 9; y++) {\r\n for (let entry = 0; entry < 9; entry++) {\r\n const guess = new Guess(x, y, entry + 1);\r\n // create a node for the cell entry\r\n const entryColIdx = y * 9 + x;\r\n const entryColumn = this.columnLookup[entryColIdx];\r\n const entryConstraint = new Data(entryColumn, guess);\r\n this.rowLookup[(y * 9 + x) * 9 + entry] = entryConstraint;\r\n // put the entry node in the corresponding column\r\n entryColumn.insertDown(entryConstraint);\r\n entryColumn.size++;\r\n\r\n // create a node for the row constraint\r\n const rowColIdx = 81 + y * 9 + entry;\r\n const rowColumn = this.columnLookup[rowColIdx];\r\n const rowConstraint = new Data(rowColumn, guess);\r\n // and add it to the row\r\n entryConstraint.insertRight(rowConstraint);\r\n // and to the column for this constraint\r\n rowColumn.insertDown(rowConstraint);\r\n rowColumn.size++;\r\n\r\n // create a node for the column constraint\r\n const colColIdx = 81 * 2 + x * 9 + entry;\r\n const colCol = this.columnLookup[colColIdx];\r\n const columnConstraint = new Data(colCol, guess);\r\n // and add it to the row\r\n rowConstraint.insertRight(columnConstraint);\r\n // and to the column for this constraint\r\n colCol.insertDown(columnConstraint);\r\n colCol.size++;\r\n\r\n // create a node for the box constraint\r\n const boxX = Math.floor(x / 3);\r\n const boxY = Math.floor(y / 3);\r\n const boxColumnIndex = 81 * 3 + (boxY * 3 + boxX) * 9 + entry;\r\n const boxColumn = this.columnLookup[boxColumnIndex];\r\n const boxConstraint = new Data(boxColumn, guess);\r\n // add it the row\r\n columnConstraint.insertRight(boxConstraint);\r\n // add it to the column\r\n boxColumn.insertDown(boxConstraint);\r\n boxColumn.size++;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // set a number on the puzzle covering any of the constraints that it satisfies\r\n setNumber(x: number, y: number, entry: number) {\r\n // find the column\r\n const row = this.rowLookup[(y * 9 + x) * 9 + entry];\r\n row.guess.isKnown = true;\r\n this.solution.push(row);\r\n this.cover(row.column);\r\n for (let right = row.right; right !== row; right = right.right) {\r\n this.cover(right.column);\r\n }\r\n }\r\n\r\n // get the column with the smallest number of rows - this should give us the quickest solution\r\n getSmallestColumn() {\r\n let smallestSize = (this.columnRoot.right as Column).size;\r\n let smallestColumn = this.columnRoot.right as Column;\r\n let col = this.columnRoot.right as Column;\r\n while (col !== this.columnRoot) {\r\n if (col.size < smallestSize) {\r\n smallestSize = col.size;\r\n smallestColumn = col;\r\n }\r\n col = col.right as Column;\r\n }\r\n return smallestColumn;\r\n }\r\n\r\n // search for a solution\r\n search(depth: number = 0): boolean {\r\n // give up if weve gone to deep - there probably isn't a solution\r\n if (depth > 100) {\r\n throw new Error(\"too deep - giving up\");\r\n }\r\n // we have no more columns - we have succeeded - send back the results\r\n if (this.columnRoot.right === this.columnRoot) {\r\n return true;\r\n }\r\n // pick the column with the fewest rows\r\n let column = this.getSmallestColumn();\r\n this.cover(column);\r\n for (let row = column.down; row !== column; row = row.down) {\r\n this.solution.push(row);\r\n for (let right = row.right; right !== row; right = right.right) {\r\n this.cover(right.column);\r\n }\r\n if (this.search(depth + 1)) {\r\n return true;\r\n }\r\n // need to backtrack\r\n for (let left = row.left; left !== row; left = left.left) {\r\n this.uncover(left.column);\r\n }\r\n this.solution.pop();\r\n }\r\n // we've failed\r\n this.uncover(column);\r\n return false;\r\n }\r\n\r\n // cover a column - basically unlink the column from the list and unlink any rows from other columns\r\n cover(column: Column) {\r\n column.right.left = column.left;\r\n column.left.right = column.right;\r\n for (let row = column.down; row !== column; row = row.down) {\r\n for (let right = row.right; right !== row; right = right.right) {\r\n right.down.up = right.up;\r\n right.up.down = right.down;\r\n right.column.size--;\r\n }\r\n }\r\n }\r\n\r\n // uncover a column - put the rows back along with the column\r\n uncover(column: Column) {\r\n for (let row = column.up; row !== column; row = row.up) {\r\n for (let left = row.left; left !== row; left = left.left) {\r\n left.down.up = left;\r\n left.up.down = left;\r\n left.column.size++;\r\n }\r\n }\r\n column.right.left = column;\r\n column.left.right = column;\r\n }\r\n}\r\n","import Image from \"./Image\";\r\n\r\nexport interface Point {\r\n x: number;\r\n y: number;\r\n}\r\n\r\nexport class ConnectedRegion {\r\n public points: Point[];\r\n public bounds: { topLeft: Point; bottomRight: Point };\r\n constructor(points: Point[], topLeft: Point, bottomRight: Point) {\r\n this.points = points;\r\n this.bounds = { topLeft, bottomRight };\r\n }\r\n get width() {\r\n return this.bounds.bottomRight.x - this.bounds.topLeft.x;\r\n }\r\n get height() {\r\n return this.bounds.bottomRight.y - this.bounds.topLeft.y;\r\n }\r\n get aspectRatio() {\r\n return this.width / this.height;\r\n }\r\n}\r\n\r\nexport function getConnectedComponent(\r\n image: Image,\r\n x: number,\r\n y: number\r\n): ConnectedRegion {\r\n const { width, height, bytes } = image;\r\n let minX = x;\r\n let minY = y;\r\n let maxX = x;\r\n let maxY = y;\r\n const points: Point[] = [];\r\n const frontier: Point[] = [];\r\n points.push({ x, y });\r\n frontier.push({ x, y });\r\n bytes[y * width + x] = 0;\r\n while (frontier.length > 0) {\r\n const seed = frontier.pop()!;\r\n minX = Math.min(seed.x, minX);\r\n maxX = Math.max(seed.x, maxX);\r\n minY = Math.min(seed.y, minY);\r\n maxY = Math.max(seed.y, maxY);\r\n for (\r\n let dy = Math.max(0, seed.y - 1);\r\n dy < height && dy <= seed.y + 1;\r\n dy++\r\n ) {\r\n for (\r\n let dx = Math.max(0, seed.x - 1);\r\n dx < width && dx <= seed.x + 1;\r\n dx++\r\n ) {\r\n if (bytes[dy * width + dx] === 255) {\r\n points.push({ x: dx, y: dy });\r\n frontier.push({ x: dx, y: dy });\r\n bytes[dy * width + dx] = 0;\r\n }\r\n }\r\n }\r\n }\r\n return new ConnectedRegion(\r\n points,\r\n { x: minX, y: minY },\r\n { x: maxX, y: maxY }\r\n );\r\n}\r\n\r\ntype ConnectedComponentOptions = {\r\n minAspectRatio: number;\r\n maxAspectRatio: number;\r\n minSize: number;\r\n maxSize: number;\r\n};\r\n\r\n/**\r\n *\r\n * @param image Input image\r\n * @param options: Filtering options\r\n */\r\nexport default function getLargestConnectedComponent(\r\n image: Image,\r\n {\r\n minAspectRatio,\r\n maxAspectRatio,\r\n minSize,\r\n maxSize,\r\n }: ConnectedComponentOptions\r\n): ConnectedRegion | null {\r\n let maxRegion: ConnectedRegion | null = null;\r\n // clone the input image as this is a destructive operation\r\n const tmp = image.clone();\r\n const { width, height, bytes } = tmp;\r\n for (let y = 0; y < height; y++) {\r\n const row = y * width;\r\n for (let x = 0; x < width; x++) {\r\n if (bytes[row + x] === 255) {\r\n const region = getConnectedComponent(tmp, x, y);\r\n const width = region.bounds.bottomRight.x - region.bounds.topLeft.x;\r\n const height = region.bounds.bottomRight.y - region.bounds.topLeft.y;\r\n if (\r\n region.aspectRatio >= minAspectRatio &&\r\n region.aspectRatio <= maxAspectRatio &&\r\n height >= minSize &&\r\n width >= minSize &&\r\n height <= maxSize &&\r\n width <= maxSize\r\n ) {\r\n if (!maxRegion || region.points.length > maxRegion.points.length) {\r\n maxRegion = region;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n return maxRegion;\r\n}\r\n","import { Point } from \"./getLargestConnectedComponent\";\r\nimport * as math from \"mathjs\";\r\n\r\nexport interface Transform {\r\n a: number;\r\n b: number;\r\n c: number;\r\n d: number;\r\n e: number;\r\n f: number;\r\n g: number;\r\n h: number;\r\n}\r\n// see here for details http://alumni.media.mit.edu/~cwren/interpolator/\r\n// now available here https://web.archive.org/web/20071214081425/http://alumni.media.mit.edu/~cwren/interpolator/\r\nexport default function findHomographicTransform(\r\n size: number,\r\n corners: {\r\n topLeft: Point;\r\n topRight: Point;\r\n bottomLeft: Point;\r\n bottomRight: Point;\r\n }\r\n): Transform {\r\n const A = math.zeros(8, 8) as math.Matrix;\r\n\r\n A.set([0, 2], 1);\r\n A.set([1, 5], 1);\r\n A.set([2, 0], size);\r\n A.set([2, 2], 1);\r\n A.set([2, 6], -size * corners.topRight.x);\r\n A.set([3, 3], size);\r\n A.set([3, 5], 1);\r\n A.set([3, 6], -size * corners.topRight.y);\r\n A.set([4, 1], size);\r\n A.set([4, 2], 1);\r\n A.set([4, 7], -size * corners.bottomLeft.x);\r\n A.set([5, 4], size);\r\n A.set([5, 5], 1);\r\n A.set([5, 7], -size * corners.bottomLeft.y);\r\n A.set([6, 0], size);\r\n A.set([6, 1], size);\r\n A.set([6, 2], 1);\r\n A.set([6, 6], -size * corners.bottomRight.x);\r\n A.set([6, 7], -size * corners.bottomRight.x);\r\n A.set([7, 3], size);\r\n A.set([7, 4], size);\r\n A.set([7, 5], 1);\r\n A.set([7, 6], -size * corners.bottomRight.y);\r\n A.set([7, 7], -size * corners.bottomRight.y);\r\n\r\n const B = math.matrix([\r\n corners.topLeft.x,\r\n corners.topLeft.y,\r\n corners.topRight.x,\r\n corners.topRight.y,\r\n corners.bottomLeft.x,\r\n corners.bottomLeft.y,\r\n corners.bottomRight.x,\r\n corners.bottomRight.y,\r\n ]);\r\n\r\n const A_t = math.transpose(A);\r\n const lamda = math.multiply(\r\n math.multiply(math.inv(math.multiply(A_t, A)), A_t),\r\n B\r\n );\r\n\r\n const a = lamda.get([0]);\r\n const b = lamda.get([1]);\r\n const c = lamda.get([2]);\r\n const d = lamda.get([3]);\r\n const e = lamda.get([4]);\r\n const f = lamda.get([5]);\r\n const g = lamda.get([6]);\r\n const h = lamda.get([7]);\r\n return { a, b, c, d, e, f, g, h };\r\n}\r\n\r\nexport function transformPoint(point: Point, tranform: Transform) {\r\n const { a, b, c, d, e, f, g, h } = tranform;\r\n const { x, y } = point;\r\n\r\n const sxPre1 = b * y + c;\r\n const sxPre2 = h * y + 1;\r\n const syPre1 = e * y + f;\r\n const syPre2 = h * y + 1;\r\n\r\n const sx = Math.floor((a * x + sxPre1) / (g * x + sxPre2));\r\n const sy = Math.floor((d * x + syPre1) / (g * x + syPre2));\r\n return { x: sx, y: sy };\r\n}\r\n","export default class Image {\r\n public bytes: Uint8ClampedArray;\r\n public width: number;\r\n public height: number;\r\n constructor(bytes: Uint8ClampedArray, width: number, height: number) {\r\n this.bytes = bytes;\r\n this.width = width;\r\n this.height = height;\r\n }\r\n static withSize(width: number, height: number) {\r\n const bytes = new Uint8ClampedArray(width * height);\r\n return new Image(bytes, width, height);\r\n }\r\n public clone(): Image {\r\n return new Image(\r\n new Uint8ClampedArray(this.bytes),\r\n this.width,\r\n this.height\r\n );\r\n }\r\n public subImage(x1: number, y1: number, x2: number, y2: number): Image {\r\n const width = x2 - x1;\r\n const height = y2 - y1;\r\n const bytes = new Uint8ClampedArray(width * height);\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < width; x++) {\r\n bytes[y * width + x] = this.bytes[(y + y1) * this.width + x + x1];\r\n }\r\n }\r\n return new Image(bytes, width, height);\r\n }\r\n public toImageData(): ImageData {\r\n const imageData = new ImageData(this.width, this.height);\r\n for (let y = 0; y < this.height; y++) {\r\n const row = y * this.width;\r\n for (let x = 0; x < this.width; x++) {\r\n const value = this.bytes[row + x];\r\n imageData.data[(row + x) * 4] = value;\r\n imageData.data[(row + x) * 4 + 1] = value;\r\n imageData.data[(row + x) * 4 + 2] = value;\r\n imageData.data[(row + x) * 4 + 3] = 255;\r\n }\r\n }\r\n return imageData;\r\n }\r\n}\r\n","import Image from \"./Image\";\r\n\r\n// Fast box blur algorithm\r\n// see - https://www.gamasutra.com/view/feature/3102/four_tricks_for_fast_blurring_in_.php?print=1\r\n\r\nfunction precompute(\r\n bytes: Uint8ClampedArray,\r\n width: number,\r\n height: number\r\n): number[] {\r\n const result: number[] = new Array(bytes.length);\r\n let dst = 0;\r\n let src = 0;\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < width; x++) {\r\n let tot = bytes[src];\r\n if (x > 0) tot += result[dst - 1];\r\n if (y > 0) tot += result[dst - width];\r\n if (x > 0 && y > 0) tot -= result[dst - width - 1];\r\n result[dst] = tot;\r\n dst++;\r\n src++;\r\n }\r\n }\r\n return result;\r\n}\r\n\r\n// this is a utility function used by DoBoxBlur below\r\nfunction readP(\r\n precomputed: number[],\r\n w: number,\r\n h: number,\r\n x: number,\r\n y: number\r\n): number {\r\n if (x < 0) x = 0;\r\n else if (x >= w) x = w - 1;\r\n if (y < 0) y = 0;\r\n else if (y >= h) y = h - 1;\r\n return precomputed[x + y * w];\r\n}\r\n\r\nexport default function boxBlur(src: Image, boxw: number, boxh: number): Image {\r\n const { width, height, bytes } = src;\r\n const precomputed = precompute(bytes, width, height);\r\n const result = new Uint8ClampedArray(width * height);\r\n let dst = 0;\r\n const mul = 1.0 / ((boxw * 2 + 1) * (boxh * 2 + 1));\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < width; x++) {\r\n const tot =\r\n readP(precomputed, width, height, x + boxw, y + boxh) +\r\n readP(precomputed, width, height, x - boxw, y - boxh) -\r\n readP(precomputed, width, height, x - boxw, y + boxh) -\r\n readP(precomputed, width, height, x + boxw, y - boxh);\r\n result[dst] = tot * mul;\r\n dst++;\r\n }\r\n }\r\n return new Image(result, width, height);\r\n}\r\n","import { Point, ConnectedRegion } from \"./getLargestConnectedComponent\";\r\n\r\n/**\r\n * Finds the nearest point to another point using manhattan distance\r\n * @param points Array of points\r\n * @param x x coordinate of point\r\n * @param y y coordinate of point\r\n */\r\nfunction getNearestPoint(points: Point[], x: number, y: number) {\r\n let closestPoint = points[0];\r\n let minDistance = Number.MAX_SAFE_INTEGER;\r\n points.forEach((point) => {\r\n const dx = Math.abs(point.x - x);\r\n const dy = Math.abs(point.y - y);\r\n const distance = dx + dy;\r\n if (distance < minDistance) {\r\n minDistance = distance;\r\n closestPoint = point;\r\n }\r\n });\r\n return closestPoint;\r\n}\r\n\r\nexport type CornerPoints = {\r\n topLeft: Point;\r\n topRight: Point;\r\n bottomLeft: Point;\r\n bottomRight: Point;\r\n};\r\n\r\n/**\r\n * Locate the corner points of a connected region\r\n * @param region A connected region\r\n */\r\nexport default function getCornerPoints(region: ConnectedRegion): CornerPoints {\r\n // get the extents\r\n const { x: minX, y: minY } = region.bounds.topLeft;\r\n const { x: maxX, y: maxY } = region.bounds.bottomRight;\r\n const { points } = region;\r\n // find the points closest to the topleft, topright, bottomleft, and bottomright\r\n return {\r\n topLeft: getNearestPoint(points, minX, minY),\r\n topRight: getNearestPoint(points, maxX, minY),\r\n bottomLeft: getNearestPoint(points, minX, maxY),\r\n bottomRight: getNearestPoint(points, maxX, maxY),\r\n };\r\n}\r\n","import Image from \"./Image\";\r\nimport { Transform } from \"./findHomographicTransform\";\r\n\r\n/**\r\n * Extracts a square region from the source image using the transform\r\n * @param source Source image\r\n * @param size The size of the square area we want to extract\r\n * @param tranform The homography to apply to map to the source image\r\n */\r\nexport default function extractSquareFromRegion(\r\n source: Image,\r\n size: number,\r\n tranform: Transform\r\n) {\r\n const { a, b, c, d, e, f, g, h } = tranform;\r\n\r\n const result = Image.withSize(size, size);\r\n for (let y = 0; y < size; y++) {\r\n const sxPre1 = b * y + c;\r\n const sxPre2 = h * y + 1;\r\n const syPre1 = e * y + f;\r\n const syPre2 = h * y + 1;\r\n\r\n for (let x = 0; x < size; x++) {\r\n const sx = Math.floor((a * x + sxPre1) / (g * x + sxPre2));\r\n const sy = Math.floor((d * x + syPre1) / (g * x + syPre2));\r\n // TODO - should we interpolate this value?\r\n result.bytes[y * size + x] = source.bytes[sy * source.width + sx];\r\n }\r\n }\r\n return result;\r\n}\r\n","import StrictEventEmitter from \"strict-event-emitter-types\";\r\nimport { EventEmitter } from \"events\";\r\nimport fillInPrediction from \"./imageRecognition/tensorflow\";\r\nimport SudokuSolver from \"./solver/sudokuSolver\";\r\nimport getLargestConnectedComponent, {\r\n Point,\r\n} from \"./imageProcessing/getLargestConnectedComponent\";\r\nimport findHomographicTransform, {\r\n Transform,\r\n transformPoint,\r\n} from \"./imageProcessing/findHomographicTransform\";\r\nimport captureImage from \"./imageProcessing/captureImage\";\r\nimport adaptiveThreshold from \"./imageProcessing/adaptiveThreshold\";\r\nimport getCornerPoints from \"./imageProcessing/getCornerPoints\";\r\nimport extractSquareFromRegion from \"./imageProcessing/applyHomographicTransform\";\r\nimport extractBoxes from \"./imageProcessing/extractBoxes\";\r\nimport { imag } from \"@tensorflow/tfjs\";\r\n\r\n// minimum number of boxes we want before trying to solve the puzzle\r\nconst MIN_BOXES = 15;\r\n// size of image to use for processing\r\nconst PROCESSING_SIZE = 900;\r\n\r\nexport type VideoReadyPayload = { width: number; height: number };\r\n\r\ninterface ProcessorEvents {\r\n videoReady: VideoReadyPayload;\r\n}\r\n\r\ntype ProcessorEventEmitter = StrictEventEmitter;\r\n\r\ntype SolvedBox = {\r\n // was this a known digit?\r\n isKnown: boolean;\r\n // the digit for this box\r\n digit: number;\r\n // a guess at how tall it should be drawn\r\n digitHeight: number;\r\n // a guess at the rotation to draw it at\r\n digitRotation: number;\r\n // where to draw it\r\n position: Point;\r\n};\r\n\r\nexport default class Processor extends (EventEmitter as {\r\n new(): ProcessorEventEmitter;\r\n}) {\r\n // the source for our video\r\n video: HTMLVideoElement;\r\n // is the video actually running?\r\n isVideoRunning: boolean = false;\r\n // are we in the middle of processing a frame?\r\n isProcessing: boolean = false;\r\n // the detected corners of the puzzle in video space\r\n corners: {\r\n topLeft: Point;\r\n topRight: Point;\r\n bottomLeft: Point;\r\n bottomRight: Point;\r\n };\r\n // the calculated grid lines in the video space\r\n gridLines: { p1: Point; p2: Point }[];\r\n // completely solved puzzle\r\n solvedPuzzle: SolvedBox[][];\r\n // performance stats\r\n captureTime: number = 0;\r\n thresholdTime: number = 0;\r\n connectedComponentTime: number = 0;\r\n cornerPointTime: number = 0;\r\n extractPuzzleTime: number = 0;\r\n extractBoxesTime: number = 0;\r\n neuralNetTime: number = 0;\r\n solveTime: number = 0;\r\n\r\n /**\r\n * Start streaming video from the back camera of a phone (or webcam on a computer)\r\n * @param video A video element - needs to be on the page for iOS to work\r\n */\r\n async startVideo(video: HTMLVideoElement) {\r\n this.video = video;\r\n // start up the video feed\r\n const stream = await navigator.mediaDevices.getUserMedia({\r\n video: { facingMode: \"environment\", width: 640 },\r\n audio: false,\r\n });\r\n // grab the video dimensions once it has started up\r\n const canPlayListener = () => {\r\n this.video.removeEventListener(\"canplay\", canPlayListener);\r\n this.emit(\"videoReady\", {\r\n width: this.video.videoWidth,\r\n height: this.video.videoHeight,\r\n });\r\n this.isVideoRunning = true;\r\n // start processing\r\n this.processFrame();\r\n };\r\n this.video.addEventListener(\"canplay\", canPlayListener);\r\n this.video.srcObject = stream;\r\n this.video.play();\r\n }\r\n\r\n /**\r\n * Creates a set of grid lines mapped onto video space\r\n * @param transform The homographic transform to video space\r\n */\r\n createGridLines(transform: Transform) {\r\n const boxSize = PROCESSING_SIZE / 9;\r\n const gridLines = [];\r\n for (let l = 1; l < 9; l++) {\r\n // horizonal line\r\n gridLines.push({\r\n p1: transformPoint({ x: 0, y: l * boxSize }, transform),\r\n p2: transformPoint({ x: PROCESSING_SIZE, y: l * boxSize }, transform),\r\n });\r\n // vertical line\r\n gridLines.push({\r\n p1: transformPoint({ y: 0, x: l * boxSize }, transform),\r\n p2: transformPoint({ y: PROCESSING_SIZE, x: l * boxSize }, transform),\r\n });\r\n }\r\n return gridLines;\r\n }\r\n\r\n /**\r\n * Create a set of cells with coordinates in video space for drawing digits\r\n * @param x Cell X\r\n * @param y Cell Y\r\n * @param digit The digit\r\n * @param isKnown Is it a known digit?\r\n * @param transform The homographic transform to video space\r\n */\r\n getTextDetailsForBox(\r\n x: number,\r\n y: number,\r\n digit: number,\r\n isKnown: boolean,\r\n transform: Transform\r\n ): SolvedBox {\r\n const boxSize = PROCESSING_SIZE / 9;\r\n // work out the line that runs vertically through the box in the original image space\r\n const p1 = transformPoint(\r\n { x: (x + 0.5) * boxSize, y: y * boxSize },\r\n transform\r\n );\r\n const p2 = transformPoint(\r\n { x: (x + 0.5) * boxSize, y: (y + 1) * boxSize },\r\n transform\r\n );\r\n // the center of the box\r\n const textPosition = transformPoint(\r\n { x: (x + 0.5) * boxSize, y: (y + 0.5) * boxSize },\r\n transform\r\n );\r\n // approximate angle of the text in the box\r\n const dx = p1.x - p2.x;\r\n const dy = p1.y - p2.y;\r\n const digitRotation = Math.atan2(dx, dy);\r\n\r\n // appriximate height of the text in the box\r\n const digitHeight = 0.8 * Math.sqrt(dx * dx + dy * dy);\r\n\r\n return {\r\n digit,\r\n digitHeight,\r\n digitRotation,\r\n isKnown: isKnown,\r\n position: textPosition,\r\n };\r\n }\r\n\r\n /**\r\n * Map from the found solution to something that can be displayed in video space\r\n * @param solver The solver with the solution\r\n * @param transform The transform to video space\r\n */\r\n createSolvedPuzzle(solver: SudokuSolver, transform: Transform) {\r\n const results: SolvedBox[][] = new Array(9);\r\n for (let y = 0; y < 9; y++) {\r\n results[y] = new Array(9);\r\n }\r\n solver.solution.forEach((sol) => {\r\n const { x, y, entry, isKnown } = sol.guess;\r\n results[y][x] = this.getTextDetailsForBox(\r\n x,\r\n y,\r\n entry,\r\n isKnown,\r\n transform\r\n );\r\n });\r\n return results;\r\n }\r\n\r\n sanityCheckCorners({\r\n topLeft,\r\n topRight,\r\n bottomLeft,\r\n bottomRight,\r\n }: {\r\n topLeft: Point;\r\n topRight: Point;\r\n bottomLeft: Point;\r\n bottomRight: Point;\r\n }) {\r\n function length(p1: Point, p2: Point) {\r\n const dx = p1.x - p2.x;\r\n const dy = p1.y - p2.y;\r\n return Math.sqrt(dx * dx + dy * dy);\r\n }\r\n const topLineLength = length(topLeft, topRight);\r\n const leftLineLength = length(topLeft, bottomLeft);\r\n const rightLineLength = length(topRight, bottomRight);\r\n const bottomLineLength = length(bottomLeft, bottomRight);\r\n if (\r\n topLineLength < 0.5 * bottomLineLength ||\r\n topLineLength > 1.5 * bottomLineLength\r\n )\r\n return false;\r\n if (\r\n leftLineLength < 0.7 * rightLineLength ||\r\n leftLineLength > 1.3 * rightLineLength\r\n )\r\n return false;\r\n if (\r\n leftLineLength < 0.5 * bottomLineLength ||\r\n leftLineLength > 1.5 * bottomLineLength\r\n )\r\n return false;\r\n return true;\r\n }\r\n /**\r\n * Process a frame of video\r\n */\r\n async processFrame() {\r\n if (!this.isVideoRunning) {\r\n // no video stream so give up immediately\r\n return;\r\n }\r\n if (this.isProcessing) {\r\n // we're already processing a frame. Don't kill the computer!\r\n return;\r\n }\r\n try {\r\n // grab an image from the video camera\r\n let startTime = performance.now();\r\n const image = captureImage(this.video);\r\n\r\n //works\r\n //var canvas = document.createElement('canvas');\r\n //var context = canvas.getContext('2d');\r\n //canvas.height = image.height;\r\n //canvas.width = image.width;\r\n //context.putImageData(image.toImageData(), 0, 0);\r\n //var dataURL = canvas.toDataURL('image/jpeg');\r\n //canvas.remove();\r\n //document.getElementById(\"rest-capture\").innerHTML = document.getElementById(\"rest-capture\").innerHTML + \"\";\r\n\r\n this.captureTime =\r\n 0.1 * (performance.now() - startTime) + this.captureTime * 0.9;\r\n\r\n // apply adaptive thresholding to the image\r\n startTime = performance.now();\r\n const thresholded = adaptiveThreshold(image.clone(), 20, 20);\r\n this.thresholdTime =\r\n 0.1 * (performance.now() - startTime) + this.thresholdTime * 0.9;\r\n\r\n // extract the most likely candidate connected region from the image\r\n startTime = performance.now();\r\n const largestConnectedComponent = getLargestConnectedComponent(\r\n thresholded,\r\n {\r\n minAspectRatio: 0.5,\r\n maxAspectRatio: 1.5,\r\n minSize:\r\n Math.min(this.video.videoWidth, this.video.videoHeight) * 0.3,\r\n maxSize:\r\n Math.min(this.video.videoWidth, this.video.videoHeight) * 0.9,\r\n }\r\n );\r\n this.connectedComponentTime =\r\n 0.1 * (performance.now() - startTime) +\r\n this.connectedComponentTime * 0.9;\r\n\r\n // if we actually found something\r\n if (largestConnectedComponent) {\r\n // make a guess at where the corner points are using manhattan distance\r\n startTime = performance.now();\r\n const potentialCorners = getCornerPoints(largestConnectedComponent);\r\n this.cornerPointTime =\r\n 0.1 * (performance.now() - startTime) + this.cornerPointTime * 0.9;\r\n\r\n if (this.sanityCheckCorners(potentialCorners)) {\r\n this.corners = potentialCorners;\r\n\r\n // compute the transform to go from a square puzzle of size PROCESSING_SIZE to the detected corner points\r\n startTime = performance.now();\r\n const transform = findHomographicTransform(\r\n PROCESSING_SIZE,\r\n this.corners\r\n );\r\n\r\n // we've got the transform so we can show where the gridlines are\r\n this.gridLines = this.createGridLines(transform);\r\n\r\n // extract the square puzzle from the original grey image\r\n const extractedImageGreyScale = extractSquareFromRegion(\r\n image,\r\n PROCESSING_SIZE,\r\n transform\r\n );\r\n // extract the square puzzle from the thresholded image - we'll use the thresholded image for determining where the digits are in the cells\r\n const extractedImageThresholded = extractSquareFromRegion(\r\n thresholded,\r\n PROCESSING_SIZE,\r\n transform\r\n );\r\n this.extractPuzzleTime =\r\n 0.1 * (performance.now() - startTime) +\r\n this.extractPuzzleTime * 0.9;\r\n\r\n // extract the boxes that should contain the numbers\r\n startTime = performance.now();\r\n const boxes = extractBoxes(\r\n extractedImageGreyScale,\r\n extractedImageThresholded\r\n );\r\n this.extractBoxesTime =\r\n 0.1 * (performance.now() - startTime) + this.extractBoxesTime * 0.9;\r\n\r\n // did we find sufficient boxes for a potentially valid sudoku puzzle?\r\n if (boxes.length > MIN_BOXES) {\r\n // apply the neural network to the found boxes and work out what the digits are\r\n startTime = performance.now();\r\n await fillInPrediction(boxes);\r\n this.neuralNetTime =\r\n 0.1 * (performance.now() - startTime) + this.neuralNetTime * 0.9;\r\n\r\n // solve the suoku puzzle using the dancing links and algorithm X - https://en.wikipedia.org/wiki/Knuth%27s_Algorithm_X\r\n startTime = performance.now();\r\n const solver = new SudokuSolver();\r\n // set the known values\r\n boxes.forEach((box) => {\r\n if (box.contents !== 0) {\r\n solver.setNumber(box.x, box.y, box.contents - 1);\r\n }\r\n });\r\n // search for a solution\r\n if (solver.search(0)) {\r\n this.solvedPuzzle = this.createSolvedPuzzle(solver, transform);\r\n } else {\r\n this.solvedPuzzle = null;\r\n }\r\n this.solveTime =\r\n 0.1 * (performance.now() - startTime) + this.solveTime * 0.9;\r\n }\r\n } else {\r\n this.corners = null;\r\n this.gridLines = null;\r\n this.solvedPuzzle = null;\r\n }\r\n } else {\r\n this.corners = null;\r\n this.gridLines = null;\r\n this.solvedPuzzle = null;\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n this.isProcessing = false;\r\n // process again\r\n setTimeout(() => this.processFrame(), 20);\r\n }\r\n}\r\n","import Image from \"./Image\";\r\n\r\nexport default function captureImage(video: HTMLVideoElement) {\r\n const canvas = document.createElement(\"canvas\");\r\n const width = video.videoWidth;\r\n const height = video.videoHeight;\r\n canvas.width = width;\r\n canvas.height = height;\r\n const context = canvas.getContext(\"2d\");\r\n // draw the video to the canvas\r\n context!.drawImage(video, 0, 0, width, height);\r\n // get the raw image bytes\r\n const imageData = context!.getImageData(0, 0, width, height);\r\n // convert to greyscale\r\n const bytes = new Uint8ClampedArray(width * height);\r\n for (let y = 0; y < height; y++) {\r\n const row = y * width;\r\n for (let x = 0; x < width; x++) {\r\n //const r = imageData.data[(y * width + x) * 4];\r\n const g = imageData.data[(row + x) * 4 + 1];\r\n // const b = imageData.data[(y * width + x) * 4 + 2];\r\n // https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale\r\n // const grey = 0.299 * r + 0.587 * g + 0.114 * b;\r\n bytes[row + x] = g;\r\n }\r\n }\r\n return new Image(bytes, width, height);\r\n}\r\n","import Image from \"./Image\";\r\n// import gaussianBlur from \"./gaussianBlur\";\r\nimport boxBlur from \"./boxBlur\";\r\n\r\n/**\r\n * Applies adaptive thresholding to an image. Uses a fast box blur for speed.\r\n * @param image Image to threshold\r\n * @param threshold Threshold value - higher removes noise, lower more noise\r\n */\r\nexport default function adaptiveThreshold(\r\n image: Image,\r\n threshold: number,\r\n blurSize: number\r\n): Image {\r\n const { width, height, bytes } = image;\r\n const blurred = boxBlur(image, blurSize, blurSize);\r\n const blurredBytes = blurred.bytes;\r\n for (let y = 0; y < height; y++) {\r\n const row = y * width;\r\n for (let x = 0; x < width; x++) {\r\n bytes[row + width + x] =\r\n blurredBytes[row + x] - bytes[row + width + x] > threshold ? 255 : 0;\r\n }\r\n }\r\n return image;\r\n}\r\n","import Image from \"./Image\";\r\nimport { getConnectedComponent } from \"./getLargestConnectedComponent\";\r\n\r\nexport interface PuzzleBox {\r\n x: number;\r\n y: number;\r\n minX: number;\r\n maxX: number;\r\n minY: number;\r\n maxY: number;\r\n numberImage: Image;\r\n // filled in later by the OCR code\r\n contents: number;\r\n}\r\n\r\n/**\r\n * Looks through each box in the puzzle image to see if there is a potential digit in it. If there is an image is extracted that should contain the digit.\r\n * @param greyScale The greyscale image of the puzzle\r\n * @param thresholded A thresholded version of the greyscale image\r\n */\r\nexport default function extractBoxes(greyScale: Image, thresholded: Image) {\r\n const results: PuzzleBox[] = [];\r\n const size = greyScale.width;\r\n const boxSize = size / 9;\r\n const searchSize = boxSize / 5;\r\n // go through each box and see if it contains anything, if it does get the bounds of the contents and copy it from the greyScale image\r\n for (let y = 0; y < 9; y++) {\r\n for (let x = 0; x < 9; x++) {\r\n let minX = Number.MAX_SAFE_INTEGER;\r\n let minY = Number.MAX_SAFE_INTEGER;\r\n let maxX = 0;\r\n let maxY = 0;\r\n let pointsCount = 0;\r\n const searchX1 = x * boxSize + searchSize;\r\n const searchY1 = y * boxSize + searchSize;\r\n const searchX2 = x * boxSize + boxSize - searchSize;\r\n const searchY2 = y * boxSize + boxSize - searchSize;\r\n for (let searchY = searchY1; searchY < searchY2; searchY++) {\r\n for (let searchX = searchX1; searchX < searchX2; searchX++) {\r\n if (thresholded.bytes[searchY * size + searchX] === 255) {\r\n const component = getConnectedComponent(\r\n thresholded,\r\n searchX,\r\n searchY\r\n );\r\n const foundWidth =\r\n component.bounds.bottomRight.x - component.bounds.topLeft.x;\r\n const foundHeight =\r\n component.bounds.bottomRight.y - component.bounds.topLeft.y;\r\n if (\r\n component.points.length > 10 &&\r\n foundWidth < boxSize &&\r\n foundHeight < boxSize\r\n ) {\r\n minX = Math.min(minX, component.bounds.topLeft.x);\r\n minY = Math.min(minY, component.bounds.topLeft.y);\r\n maxX = Math.max(maxX, component.bounds.bottomRight.x);\r\n maxY = Math.max(maxY, component.bounds.bottomRight.y);\r\n pointsCount += component.points.length;\r\n }\r\n }\r\n }\r\n }\r\n // sanity check to make sure we actually found something and we didn't get something weird\r\n const foundWidth = maxX - minX;\r\n const foundHeight = maxY - minY;\r\n if (\r\n pointsCount > 10 &&\r\n foundWidth < boxSize &&\r\n foundHeight < boxSize &&\r\n foundWidth > boxSize / 10 &&\r\n foundHeight > boxSize / 3\r\n ) {\r\n const numberImage = greyScale.subImage(\r\n Math.max(0, minX - 2),\r\n Math.max(0, minY - 2),\r\n Math.min(size - 1, maxX + 2),\r\n Math.min(size - 1, maxY + 2)\r\n );\r\n results.push({\r\n x,\r\n y,\r\n minX,\r\n maxX,\r\n minY,\r\n maxY,\r\n numberImage,\r\n contents: 0,\r\n });\r\n }\r\n }\r\n }\r\n return results;\r\n}\r\n","import React from \"react\";\r\n\r\nexport default function StatsPanel({\r\n imageCaptureTime,\r\n thresholdTime,\r\n connectedComponentTime,\r\n getCornerPointsTime,\r\n extractImageTime,\r\n extractBoxesTime,\r\n ocrTime,\r\n solveTime,\r\n}: {\r\n imageCaptureTime: number;\r\n thresholdTime: number;\r\n connectedComponentTime: number;\r\n getCornerPointsTime: number;\r\n extractImageTime: number;\r\n extractBoxesTime: number;\r\n ocrTime: number;\r\n solveTime: number;\r\n}) {\r\n return (\r\n
\r\n
\r\n imageCaptureTime: {Math.round(imageCaptureTime)}\r\n
\r\n
\r\n thresholdTime: {Math.round(thresholdTime)}\r\n
\r\n
\r\n connectedComponentTime: {Math.round(connectedComponentTime)}\r\n
\r\n
\r\n getCornerPointsTime: {Math.round(getCornerPointsTime)}\r\n
\r\n
\r\n extractImageTime: {Math.round(extractImageTime)}\r\n
\r\n
\r\n extractBoxesTime: {Math.round(extractBoxesTime)}\r\n
\r\n
\r\n ocrTime: {Math.round(ocrTime)}\r\n
\r\n
\r\n solveTime: {Math.round(solveTime)}\r\n
\r\n
\r\n );\r\n}\r\n","import { data } from \"@tensorflow/tfjs\";\r\nimport React, { useRef, useState, useEffect } from \"react\";\r\nimport { setTimeout } from \"timers\";\r\nimport \"./App.css\";\r\nimport Processor, { VideoReadyPayload } from \"./augmentedReality/Processor\";\r\nimport StatsPanel from \"./components/StatsPanel\";\r\n\r\n// start processing video\r\nconst processor = new Processor();\r\nconst rest_img = document.getElementById(\"rest-capture\");\r\nconst capture = document.getElementById(\"capture\");\r\nlet last_rest = '';\r\nlet d_url = null;\r\nfunction App() {\r\n const videoRef = useRef(null);\r\n const previewCanvasRef = useRef(null);\r\n\r\n const [videoWidth, setVideoWidth] = useState(100);\r\n const [videoHeight, setVideoHeight] = useState(100);\r\n\r\n const [imageCaptureTime, setImageCaptureTime] = useState(0);\r\n const [thresholdTime, setThresholdTime] = useState(0);\r\n const [connectedComponentTime, setConnectedComponentTime] = useState(0);\r\n const [getCornerPointsTime, setGetCornerPOintsTime] = useState(0);\r\n const [extractImageTime, setExtractImageTime] = useState(0);\r\n const [extractBoxesTime, setExtractBoxesTime] = useState(0);\r\n const [ocrTime, setOcrTime] = useState(0);\r\n const [solveTime, setSolveTime] = useState(0);\r\n\r\n // start the video playing\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (video) {\r\n processor.startVideo(video).then(\r\n () => console.log(\"Video started\"),\r\n (error) => alert(error.message)\r\n );\r\n }\r\n }, [videoRef]);\r\n\r\n // render the overlay\r\n useEffect(() => {\r\n const interval = window.setInterval(() => {\r\n //capture.style.display = 'none';\r\n const canvas = previewCanvasRef.current;\r\n if (canvas && processor.isVideoRunning) {\r\n // update the peformance stats\r\n setImageCaptureTime(processor.captureTime);\r\n setThresholdTime(processor.thresholdTime);\r\n setConnectedComponentTime(processor.connectedComponentTime);\r\n setGetCornerPOintsTime(processor.cornerPointTime);\r\n setExtractImageTime(processor.extractPuzzleTime);\r\n setExtractBoxesTime(processor.extractBoxesTime);\r\n setOcrTime(processor.neuralNetTime);\r\n setSolveTime(processor.solveTime);\r\n // display the output from the processor\r\n const context = canvas.getContext(\"2d\");\r\n if (context) {\r\n context.drawImage(processor.video, 0, 0);\r\n if (processor.corners) {\r\n const {\r\n topLeft,\r\n topRight,\r\n bottomLeft,\r\n bottomRight,\r\n } = processor.corners;\r\n context.strokeStyle = \"rgba(0,200,0,0.5)\";\r\n context.fillStyle = \"rgba(0,0,0,0.3)\";\r\n context.lineWidth = 3;\r\n context.beginPath();\r\n context.moveTo(topLeft.x, topLeft.y);\r\n context.lineTo(topRight.x, topRight.y);\r\n context.lineTo(bottomRight.x, bottomRight.y);\r\n context.lineTo(bottomLeft.x, bottomLeft.y);\r\n context.closePath();\r\n context.stroke();\r\n context.fill();\r\n }\r\n if (processor.gridLines) {\r\n context.strokeStyle = \"rgba(0,200,0,0.5)\";\r\n context.lineWidth = 2;\r\n processor.gridLines.forEach((line) => {\r\n context.moveTo(line.p1.x, line.p1.y);\r\n context.lineTo(line.p2.x, line.p2.y);\r\n });\r\n context.stroke();\r\n }\r\n if (processor.solvedPuzzle) {\r\n context.fillStyle = \"rgba(0,200,0,1)\";\r\n for (let y = 0; y < 9; y++) {\r\n for (let x = 0; x < 9; x++) {\r\n if (processor.solvedPuzzle[y][x]) {\r\n const {\r\n digit,\r\n digitHeight,\r\n digitRotation,\r\n position,\r\n isKnown,\r\n } = processor.solvedPuzzle[y][x];\r\n if (!isKnown) {\r\n context.font = `bold ${digitHeight}px sans-serif`;\r\n context.translate(position.x, position.y);\r\n context.rotate(Math.PI - digitRotation);\r\n context.fillText(\r\n digit.toString(),\r\n -digitHeight / 4,\r\n digitHeight / 3\r\n );\r\n context.setTransform();\r\n }\r\n }\r\n }\r\n }\r\n //console.log('Solved Puzzle' + processor.solvedPuzzle)\r\n //to get unique key so stop reprint the result\r\n var mashed = Object.keys(processor.solvedPuzzle).reduce(function (acc, key) {\r\n return `${acc}_${processor.solvedPuzzle[key].map(t => t.digit).join()}`;\r\n }, '');\r\n console.log('rest match', mashed, last_rest);\r\n capture.style.display = '';\r\n var dataurl = canvas.toDataURL();\r\n ((du) => setTimeout(() => {\r\n capture.style.display = 'none';\r\n }, 1000))(dataurl);\r\n \r\n //disable auto capture and move to long press\r\n //if (last_rest != mashed) {\r\n // rest_img.innerHTML = rest_img.innerHTML + \"\";\r\n // last_rest = mashed;\r\n //}\r\n\r\n //if (last_rest != mashed) {\r\n // if (long_touch) {\r\n // rest_img.innerHTML = rest_img.innerHTML + \"\";\r\n // last_rest = mashed;\r\n // }\r\n //}\r\n }\r\n }\r\n }\r\n }, 100);\r\n return () => {\r\n window.clearInterval(interval);\r\n };\r\n }, [previewCanvasRef]);\r\n\r\n //var timer;\r\n //var touchduration = 500; //length of time we want the user to touch before we do something\r\n\r\n //var long_touch = false;\r\n //var touchstart = () => {\r\n // long_touch = false;\r\n // console.log('long pressed start reset...')\r\n // timer = setTimeout(onlongtouch, touchduration);\r\n //}\r\n //var touchend = () => {\r\n // long_touch = false;\r\n // //stops short touches from firing the event\r\n // console.log('long pressed end reset...')\r\n // if (timer)\r\n // clearTimeout(timer); // clearTimeout, not cleartimeout..\r\n //}\r\n //var onlongtouch = () => {\r\n // console.log('long pressed...')\r\n // long_touch = true;\r\n //};\r\n //window.addEventListener('touchstart', touchstart, false);\r\n //window.addEventListener('touchend', touchend, false);\r\n\r\n // update the video scale as needed\r\n useEffect(() => {\r\n function videoReadyListener({ width, height }: VideoReadyPayload) {\r\n setVideoWidth(width);\r\n setVideoHeight(height);\r\n }\r\n processor.on(\"videoReady\", videoReadyListener);\r\n return () => {\r\n processor.off(\"videoReady\", videoReadyListener);\r\n };\r\n });\r\n\r\n return (\r\n
\r\n {/* need to have a visible video for mobile safari to work */}\r\n \r\n \r\n \r\n
\r\n );\r\n}\r\n\r\nexport default App;\r\n","// This optional code is used to register a service worker.\r\n// register() is not called by default.\r\n\r\n// This lets the app load faster on subsequent visits in production, and gives\r\n// it offline capabilities. However, it also means that developers (and users)\r\n// will only see deployed updates on subsequent visits to a page, after all the\r\n// existing tabs open on the page have been closed, since previously cached\r\n// resources are updated in the background.\r\n\r\n// To learn more about the benefits of this model and instructions on how to\r\n// opt-in, read https://bit.ly/CRA-PWA\r\n\r\nconst isLocalhost = Boolean(\r\n window.location.hostname === 'localhost' ||\r\n // [::1] is the IPv6 localhost address.\r\n window.location.hostname === '[::1]' ||\r\n // 127.0.0.0/8 are considered localhost for IPv4.\r\n window.location.hostname.match(\r\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\r\n )\r\n);\r\n\r\ntype Config = {\r\n onSuccess?: (registration: ServiceWorkerRegistration) => void;\r\n onUpdate?: (registration: ServiceWorkerRegistration) => void;\r\n};\r\n\r\nexport function register(config?: Config) {\r\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\r\n // The URL constructor is available in all browsers that support SW.\r\n const publicUrl = new URL(\r\n process.env.PUBLIC_URL,\r\n window.location.href\r\n );\r\n if (publicUrl.origin !== window.location.origin) {\r\n // Our service worker won't work if PUBLIC_URL is on a different origin\r\n // from what our page is served on. This might happen if a CDN is used to\r\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\r\n return;\r\n }\r\n\r\n window.addEventListener('load', () => {\r\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\r\n\r\n if (isLocalhost) {\r\n // This is running on localhost. Let's check if a service worker still exists or not.\r\n checkValidServiceWorker(swUrl, config);\r\n\r\n // Add some additional logging to localhost, pointing developers to the\r\n // service worker/PWA documentation.\r\n navigator.serviceWorker.ready.then(() => {\r\n console.log(\r\n 'This web app is being served cache-first by a service ' +\r\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\r\n );\r\n });\r\n } else {\r\n // Is not localhost. Just register service worker\r\n registerValidSW(swUrl, config);\r\n }\r\n });\r\n }\r\n}\r\n\r\nfunction registerValidSW(swUrl: string, config?: Config) {\r\n navigator.serviceWorker\r\n .register(swUrl)\r\n .then(registration => {\r\n registration.onupdatefound = () => {\r\n const installingWorker = registration.installing;\r\n if (installingWorker == null) {\r\n return;\r\n }\r\n installingWorker.onstatechange = () => {\r\n if (installingWorker.state === 'installed') {\r\n if (navigator.serviceWorker.controller) {\r\n // At this point, the updated precached content has been fetched,\r\n // but the previous service worker will still serve the older\r\n // content until all client tabs are closed.\r\n console.log(\r\n 'New content is available and will be used when all ' +\r\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\r\n );\r\n\r\n // Execute callback\r\n if (config && config.onUpdate) {\r\n config.onUpdate(registration);\r\n }\r\n } else {\r\n // At this point, everything has been precached.\r\n // It's the perfect time to display a\r\n // \"Content is cached for offline use.\" message.\r\n console.log('Content is cached for offline use.');\r\n\r\n // Execute callback\r\n if (config && config.onSuccess) {\r\n config.onSuccess(registration);\r\n }\r\n }\r\n }\r\n };\r\n };\r\n })\r\n .catch(error => {\r\n console.error('Error during service worker registration:', error);\r\n });\r\n}\r\n\r\nfunction checkValidServiceWorker(swUrl: string, config?: Config) {\r\n // Check if the service worker can be found. If it can't reload the page.\r\n fetch(swUrl, {\r\n headers: { 'Service-Worker': 'script' }\r\n })\r\n .then(response => {\r\n // Ensure service worker exists, and that we really are getting a JS file.\r\n const contentType = response.headers.get('content-type');\r\n if (\r\n response.status === 404 ||\r\n (contentType != null && contentType.indexOf('javascript') === -1)\r\n ) {\r\n // No service worker found. Probably a different app. Reload the page.\r\n navigator.serviceWorker.ready.then(registration => {\r\n registration.unregister().then(() => {\r\n window.location.reload();\r\n });\r\n });\r\n } else {\r\n // Service worker found. Proceed as normal.\r\n registerValidSW(swUrl, config);\r\n }\r\n })\r\n .catch(() => {\r\n console.log(\r\n 'No internet connection found. App is running in offline mode.'\r\n );\r\n });\r\n}\r\n\r\nexport function unregister() {\r\n if ('serviceWorker' in navigator) {\r\n navigator.serviceWorker.ready\r\n .then(registration => {\r\n registration.unregister();\r\n })\r\n .catch(error => {\r\n console.error(error.message);\r\n });\r\n }\r\n}\r\n","import React from \"react\";\r\nimport ReactDOM from \"react-dom\";\r\nimport \"./index.css\";\r\nimport App from \"./App\";\r\nimport * as serviceWorker from \"./serviceWorker\";\r\n\r\nReactDOM.render(\r\n \r\n \r\n ,\r\n document.getElementById(\"root\")\r\n);\r\n\r\n// If you want your app to work offline and load faster, you can change\r\n// unregister() to register() below. Note this comes with some pitfalls.\r\n// Learn more about service workers: https://bit.ly/CRA-PWA\r\nserviceWorker.unregister();\r\n"],"sourceRoot":""}