Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- Dockerfile +8 -1
- LICENSE +3 -0
- README.md +9 -4
- backend/__init__.py +1 -1
- backend/app.py +1 -2
- backend/model.py +3 -2
- backend/utils.py +13 -0
- frontend/helpers/Sanitizer.js +5 -0
- frontend/helpers/barGraphs.js +3 -0
- frontend/helpers/footer.js +2 -0
- frontend/helpers/kSlider.js +3 -0
- frontend/index.html +1 -1
- frontend/main.js +1 -1
- frontend/style.css +2 -0
- requirements.txt +14 -8
Dockerfile
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
|
|
| 1 |
FROM python:3.10-slim
|
|
|
|
|
|
|
| 2 |
WORKDIR /code
|
|
|
|
| 3 |
COPY requirements.txt .
|
| 4 |
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
|
|
|
| 5 |
COPY . .
|
| 6 |
-
|
|
|
|
| 7 |
CMD ["uvicorn", "backend.app:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
|
| 1 |
+
# Python 3.10 (slim) as base
|
| 2 |
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# THhis is the working directory of the container
|
| 5 |
WORKDIR /code
|
| 6 |
+
|
| 7 |
COPY requirements.txt .
|
| 8 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 9 |
+
|
| 10 |
+
# Copy the entire application code into the container
|
| 11 |
COPY . .
|
| 12 |
+
|
| 13 |
+
# Run the FastAPI application using uvicorn on all interfaces at port 7860
|
| 14 |
CMD ["uvicorn", "backend.app:app", "--host", "0.0.0.0", "--port", "7860"]
|
LICENSE
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
© 2026 intelligent-username
|
| 2 |
|
| 3 |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
@@ -5,3 +7,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
| 5 |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
| 6 |
|
| 7 |
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
| 1 |
+
# MIT License
|
| 2 |
+
|
| 3 |
© 2026 intelligent-username
|
| 4 |
|
| 5 |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
|
|
| 7 |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
| 8 |
|
| 9 |
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 10 |
+
|
README.md
CHANGED
|
@@ -6,19 +6,24 @@ colorTo: gray
|
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
---
|
|
|
|
| 9 |
# Optical Character Recognition
|
| 10 |
|
| 11 |

|
| 12 |
|
| 13 |
Use the tool live [here](https://ocr.varak.dev).
|
| 14 |
|
| 15 |
-
This is an optical character recognition (OCR) tool that extracts characters from drawings.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
## Usage
|
| 18 |
|
| 19 |
Once again, the tool is hosted [here](https://ocr.varak.dev) for easy access.
|
| 20 |
|
| 21 |
-
Draw the character you want to recognize on the left canvas. The right canvas will display the top-k predictions, where k can be adjusted using the slider below it. The slider is capped at 15 since, after 15, all of the predictions are basically at 0% probability.
|
| 22 |
|
| 23 |
To run this project locally, take the following steps:
|
| 24 |
|
|
@@ -35,8 +40,8 @@ cd OCR
|
|
| 35 |
|
| 36 |
```bash
|
| 37 |
python -m venv OCR-env
|
| 38 |
-
OCR-env\Scripts\activate
|
| 39 |
-
source OCR-env/bin/activate
|
| 40 |
pip install -r requirements.txt
|
| 41 |
```
|
| 42 |
|
|
|
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
---
|
| 9 |
+
|
| 10 |
# Optical Character Recognition
|
| 11 |
|
| 12 |

|
| 13 |
|
| 14 |
Use the tool live [here](https://ocr.varak.dev).
|
| 15 |
|
| 16 |
+
This is an optical character recognition (OCR) tool that extracts characters from drawings. It's made with FastAPI for the backend, vanilla JS/HTML for the frontend, and the model was trained using PyTorch.
|
| 17 |
+
|
| 18 |
+
The project creates a canvas to draw on, converts the drawing to a usable 28x28 pixel format, and sends it to the backend for prediction.
|
| 19 |
+
|
| 20 |
+
The only two routes for the backend are `/` for the home page and `/predict` for prediction API. The prediction route recognizes all English characters and digits. For more details on the architecture, the dataset, and training process, check out [this writeup](https://github.com/intelligent-username/CNN/tree/main/char), which presents the simpler of two models trained for my [writeup on CNNs](https://github.com/intelligent-username/CNN).
|
| 21 |
|
| 22 |
## Usage
|
| 23 |
|
| 24 |
Once again, the tool is hosted [here](https://ocr.varak.dev) for easy access.
|
| 25 |
|
| 26 |
+
Draw the character you want to recognize on the left canvas. The right canvas will display the top-k predictions, where k can be adjusted using the slider below it. The slider is capped at 15 since, after 15, all of the predictions are basically guaranteed to be at 0% probability.
|
| 27 |
|
| 28 |
To run this project locally, take the following steps:
|
| 29 |
|
|
|
|
| 40 |
|
| 41 |
```bash
|
| 42 |
python -m venv OCR-env
|
| 43 |
+
OCR-env\Scripts\activate # On Windows
|
| 44 |
+
source OCR-env/bin/activate # On mac/Linux
|
| 45 |
pip install -r requirements.txt
|
| 46 |
```
|
| 47 |
|
backend/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
#
|
|
|
|
| 1 |
+
# Modularize
|
backend/app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import torch as t
|
| 2 |
import asyncio
|
| 3 |
import os
|
|
|
|
| 4 |
|
| 5 |
from backend.utils import predict_image
|
| 6 |
from backend.model import EMNIST_VGG
|
|
@@ -54,8 +55,6 @@ if not os.path.exists(MODEL_PATH):
|
|
| 54 |
# This downloads the file and returns the local path
|
| 55 |
downloaded_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME)
|
| 56 |
|
| 57 |
-
# Optional: Move it to your backend folder so your existing load logic works
|
| 58 |
-
import shutil
|
| 59 |
shutil.copy(downloaded_path, MODEL_PATH)
|
| 60 |
print(f"Weights secured at {MODEL_PATH}")
|
| 61 |
|
|
|
|
| 1 |
import torch as t
|
| 2 |
import asyncio
|
| 3 |
import os
|
| 4 |
+
import shutil
|
| 5 |
|
| 6 |
from backend.utils import predict_image
|
| 7 |
from backend.model import EMNIST_VGG
|
|
|
|
| 55 |
# This downloads the file and returns the local path
|
| 56 |
downloaded_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME)
|
| 57 |
|
|
|
|
|
|
|
| 58 |
shutil.copy(downloaded_path, MODEL_PATH)
|
| 59 |
print(f"Weights secured at {MODEL_PATH}")
|
| 60 |
|
backend/model.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
| 1 |
"""
|
| 2 |
VGG-style CNN for EMNIST character classification.
|
| 3 |
-
See the README for a
|
| 4 |
"""
|
| 5 |
|
| 6 |
# The .pth file (weights) for this model will be downloaded from HuggingFace by app.py
|
| 7 |
# It's hosted at https://huggingface.co/compendious/EMNIST-OCR-WEIGHTS/
|
| 8 |
# The file is EMNIST_CNN.pth
|
| 9 |
-
# Go here to download directly:
|
|
|
|
| 10 |
|
| 11 |
import torch
|
| 12 |
import torch.nn as nn
|
|
|
|
| 1 |
"""
|
| 2 |
VGG-style CNN for EMNIST character classification.
|
| 3 |
+
See the README for a more detailed description.
|
| 4 |
"""
|
| 5 |
|
| 6 |
# The .pth file (weights) for this model will be downloaded from HuggingFace by app.py
|
| 7 |
# It's hosted at https://huggingface.co/compendious/EMNIST-OCR-WEIGHTS/
|
| 8 |
# The file is EMNIST_CNN.pth
|
| 9 |
+
# Go here to download directly:
|
| 10 |
+
# https://huggingface.co/compendious/EMNIST-OCR-WEIGHTS/resolve/main/EMNIST_CNN.pth?download=true
|
| 11 |
|
| 12 |
import torch
|
| 13 |
import torch.nn as nn
|
backend/utils.py
CHANGED
|
@@ -4,6 +4,19 @@ import numpy as np
|
|
| 4 |
MAX_CLASSES = 15
|
| 5 |
|
| 6 |
def predict_image(image: list[float], model, device, top_k: int = 10) -> dict:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
# RESHAPE
|
| 8 |
# Input comes in as flat 784 list -> (1 batch, 1 channel, 28 height, 28 width)
|
| 9 |
x = t.tensor(image, dtype=t.float32).view(1, 1, 28, 28)
|
|
|
|
| 4 |
MAX_CLASSES = 15
|
| 5 |
|
| 6 |
def predict_image(image: list[float], model, device, top_k: int = 10) -> dict:
|
| 7 |
+
"""
|
| 8 |
+
Docstring for predict_image
|
| 9 |
+
|
| 10 |
+
:param image: Description
|
| 11 |
+
:type image: list[float]
|
| 12 |
+
:param model: Description
|
| 13 |
+
:param device: Description
|
| 14 |
+
:param top_k: Description
|
| 15 |
+
:type top_k: int
|
| 16 |
+
:return: Description
|
| 17 |
+
:rtype: dict
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
# RESHAPE
|
| 21 |
# Input comes in as flat 784 list -> (1 batch, 1 channel, 28 height, 28 width)
|
| 22 |
x = t.tensor(image, dtype=t.float32).view(1, 1, 28, 28)
|
frontend/helpers/Sanitizer.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
const blockSize = 15; // 420 / 28 = 15
|
| 2 |
|
| 3 |
// Input Processor
|
|
|
|
| 1 |
+
// Take Canvas input (left)
|
| 2 |
+
// Convert 420x420 to 28x28
|
| 3 |
+
// Then convert to Binary
|
| 4 |
+
// So it can be sent to the API
|
| 5 |
+
|
| 6 |
const blockSize = 15; // 420 / 28 = 15
|
| 7 |
|
| 8 |
// Input Processor
|
frontend/helpers/barGraphs.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
function drawPredictionGraph(canvas, predictions) {
|
| 2 |
const ctx = canvas.getContext("2d");
|
| 3 |
const w = canvas.width;
|
|
|
|
| 1 |
+
// Take response from API
|
| 2 |
+
// Draw final output
|
| 3 |
+
|
| 4 |
function drawPredictionGraph(canvas, predictions) {
|
| 5 |
const ctx = canvas.getContext("2d");
|
| 6 |
const w = canvas.width;
|
frontend/helpers/footer.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
function initFooter() {
|
| 2 |
const footer = document.createElement('footer');
|
| 3 |
footer.className = 'footer';
|
|
|
|
| 1 |
+
// Just the footer w/ basic info
|
| 2 |
+
|
| 3 |
function initFooter() {
|
| 4 |
const footer = document.createElement('footer');
|
| 5 |
footer.className = 'footer';
|
frontend/helpers/kSlider.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
|
|
|
|
|
| 1 |
const slider = document.getElementById("k-slider");
|
| 2 |
const output = document.getElementById("k-value");
|
| 3 |
|
| 4 |
// Initialize w/ default
|
|
|
|
| 5 |
output.textContent = slider.value;
|
| 6 |
|
| 7 |
// Update number instantly on drag
|
|
|
|
| 1 |
+
// top-k bar chart display slider
|
| 2 |
+
|
| 3 |
const slider = document.getElementById("k-slider");
|
| 4 |
const output = document.getElementById("k-value");
|
| 5 |
|
| 6 |
// Initialize w/ default
|
| 7 |
+
// 10 rn
|
| 8 |
output.textContent = slider.value;
|
| 9 |
|
| 10 |
// Update number instantly on drag
|
frontend/index.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
-
<title>EMNIST OCR
|
| 6 |
<link rel="stylesheet" href="/static/style.css" />
|
| 7 |
</head>
|
| 8 |
<body>
|
|
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
+
<title>EMNIST OCR Canvases</title>
|
| 6 |
<link rel="stylesheet" href="/static/style.css" />
|
| 7 |
</head>
|
| 8 |
<body>
|
frontend/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
|
| 2 |
|
| 3 |
const canvas = document.getElementById("draw-canvas");
|
| 4 |
// Use willReadFrequently to optimize repeated getImageData readbacks
|
|
|
|
| 1 |
+
|
| 2 |
|
| 3 |
const canvas = document.getElementById("draw-canvas");
|
| 4 |
// Use willReadFrequently to optimize repeated getImageData readbacks
|
frontend/style.css
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
body {
|
| 2 |
font-family: 'Consolas', 'Courier New', monospace;
|
| 3 |
margin: 0;
|
|
|
|
| 1 |
+
/* Each style is basically just for 1 element */
|
| 2 |
+
|
| 3 |
body {
|
| 4 |
font-family: 'Consolas', 'Courier New', monospace;
|
| 5 |
margin: 0;
|
requirements.txt
CHANGED
|
@@ -1,9 +1,15 @@
|
|
| 1 |
-
|
| 2 |
-
uvicorn
|
| 3 |
-
aiofiles
|
| 4 |
-
torch --index-url https://download.pytorch.org/whl/cpu
|
| 5 |
-
torchvision --index-url https://download.pytorch.org/whl/cpu
|
| 6 |
-
numpy
|
| 7 |
-
pydantic
|
| 8 |
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--extra-index-url https://download.pytorch.org/whl/cpu
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
# ML
|
| 4 |
+
torch==2.5.1
|
| 5 |
+
torchvision==0.20.1
|
| 6 |
+
numpy==1.26.4
|
| 7 |
+
|
| 8 |
+
# Frameworx
|
| 9 |
+
fastapi==0.115.6
|
| 10 |
+
pydantic==2.10.3
|
| 11 |
+
uvicorn==0.34.0
|
| 12 |
+
aiofiles==24.1.0
|
| 13 |
+
|
| 14 |
+
# Utilz
|
| 15 |
+
huggingface-hub==0.27.0
|