Spaces:
Sleeping
Sleeping
AKA Math commited on
Commit Β·
c7e9899
1
Parent(s): 2174bb6
updated UI
Browse files- .gitignore +38 -2
- .python-version +1 -0
- CONTRIBUTING.md +273 -0
- Makefile +82 -0
- QUICKSTART.md +99 -0
- README.md +105 -12
- app.py +325 -76
- deploy.sh +69 -0
- pyproject.toml +21 -0
- requirements.txt +1 -0
- run_simple.sh +30 -0
- setup.sh +73 -0
.gitignore
CHANGED
|
@@ -1,2 +1,38 @@
|
|
| 1 |
-
|
| 2 |
-
/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
|
| 8 |
+
# Virtual environments
|
| 9 |
+
.venv/
|
| 10 |
+
venv/
|
| 11 |
+
ENV/
|
| 12 |
+
env/
|
| 13 |
+
|
| 14 |
+
# IDEs
|
| 15 |
+
.vscode/
|
| 16 |
+
.idea/
|
| 17 |
+
*.swp
|
| 18 |
+
*.swo
|
| 19 |
+
*~
|
| 20 |
+
.DS_Store
|
| 21 |
+
|
| 22 |
+
# Streamlit
|
| 23 |
+
.streamlit/secrets.toml
|
| 24 |
+
.streamlit/config.toml
|
| 25 |
+
|
| 26 |
+
# uv
|
| 27 |
+
.uv/
|
| 28 |
+
uv.lock
|
| 29 |
+
|
| 30 |
+
# Testing
|
| 31 |
+
.pytest_cache/
|
| 32 |
+
.coverage
|
| 33 |
+
htmlcov/
|
| 34 |
+
|
| 35 |
+
# Distribution / packaging
|
| 36 |
+
dist/
|
| 37 |
+
build/
|
| 38 |
+
*.egg-info/
|
.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.11.9
|
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CONTRIBUTING.md
|
| 2 |
+
|
| 3 |
+
## π οΈ Development Setup
|
| 4 |
+
|
| 5 |
+
This project uses [uv](https://github.com/astral-sh/uv) for fast, reliable Python package management.
|
| 6 |
+
|
| 7 |
+
### Prerequisites
|
| 8 |
+
|
| 9 |
+
- Python 3.11 or higher
|
| 10 |
+
- Git
|
| 11 |
+
|
| 12 |
+
### Quick Start
|
| 13 |
+
|
| 14 |
+
1. **Clone the repository**
|
| 15 |
+
```bash
|
| 16 |
+
git clone https://github.com/amithjkamath/template-matching.git
|
| 17 |
+
cd template-matching
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
2. **Run setup**
|
| 21 |
+
```bash
|
| 22 |
+
./setup.sh
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
Or if you prefer using Make:
|
| 26 |
+
```bash
|
| 27 |
+
make setup
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
3. **Run the app locally**
|
| 31 |
+
```bash
|
| 32 |
+
./run_local.sh
|
| 33 |
+
# or
|
| 34 |
+
make run
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
### Manual Setup
|
| 38 |
+
|
| 39 |
+
If you prefer to set things up manually:
|
| 40 |
+
|
| 41 |
+
1. **Install uv** (if not already installed)
|
| 42 |
+
```bash
|
| 43 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
2. **Install dependencies**
|
| 47 |
+
```bash
|
| 48 |
+
uv sync --no-build-isolation
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
3. **Run the app**
|
| 52 |
+
```bash
|
| 53 |
+
uv run --no-build-isolation streamlit run app.py
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
### Alternative Simple Method
|
| 57 |
+
|
| 58 |
+
If you encounter build-related issues, you can use a simpler approach:
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
# Install dependencies
|
| 62 |
+
uv pip install -r requirements.txt
|
| 63 |
+
|
| 64 |
+
# Run the app
|
| 65 |
+
uv run streamlit run app.py
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
## π Available Commands
|
| 69 |
+
|
| 70 |
+
### Using Shell Scripts
|
| 71 |
+
|
| 72 |
+
- `./setup.sh` - Initial setup (install uv and dependencies)
|
| 73 |
+
- `./run_local.sh` - Run the app locally
|
| 74 |
+
- `./deploy.sh` - Deploy to HuggingFace Spaces
|
| 75 |
+
|
| 76 |
+
### Using Makefile
|
| 77 |
+
|
| 78 |
+
- `make help` - Show all available commands
|
| 79 |
+
- `make setup` - Initial setup
|
| 80 |
+
- `make install` - Install/update dependencies
|
| 81 |
+
- `make run` - Run the app locally
|
| 82 |
+
- `make deploy` - Deploy to HuggingFace
|
| 83 |
+
- `make clean` - Clean up cache and temporary files
|
| 84 |
+
- `make format` - Format code (requires black)
|
| 85 |
+
- `make lint` - Run linting checks (requires ruff)
|
| 86 |
+
- `make update` - Update all dependencies
|
| 87 |
+
- `make info` - Show project information
|
| 88 |
+
|
| 89 |
+
## π§ͺ Testing Locally
|
| 90 |
+
|
| 91 |
+
Before deploying, always test your changes locally:
|
| 92 |
+
|
| 93 |
+
1. Run the app: `./run_local.sh`
|
| 94 |
+
2. Open your browser to http://localhost:8501
|
| 95 |
+
3. Test all interactive features
|
| 96 |
+
4. Check console for any errors
|
| 97 |
+
|
| 98 |
+
## π Deployment Process
|
| 99 |
+
|
| 100 |
+
The app is deployed to HuggingFace Spaces. To deploy:
|
| 101 |
+
|
| 102 |
+
1. **Ensure all changes are committed**
|
| 103 |
+
```bash
|
| 104 |
+
git status
|
| 105 |
+
git add .
|
| 106 |
+
git commit -m "Your commit message"
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
2. **Deploy**
|
| 110 |
+
```bash
|
| 111 |
+
./deploy.sh
|
| 112 |
+
# or
|
| 113 |
+
make deploy
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
The script will:
|
| 117 |
+
- Check for uncommitted changes
|
| 118 |
+
- Push to the remote repository
|
| 119 |
+
- HuggingFace Spaces will automatically rebuild
|
| 120 |
+
|
| 121 |
+
## π¦ Dependency Management
|
| 122 |
+
|
| 123 |
+
### Adding a new dependency
|
| 124 |
+
|
| 125 |
+
1. **Add to pyproject.toml**
|
| 126 |
+
```toml
|
| 127 |
+
dependencies = [
|
| 128 |
+
"new-package>=1.0.0",
|
| 129 |
+
...
|
| 130 |
+
]
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
2. **Update requirements.txt** (for HuggingFace Spaces)
|
| 134 |
+
```
|
| 135 |
+
new-package>=1.0.0
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
3. **Sync dependencies**
|
| 139 |
+
```bash
|
| 140 |
+
uv sync --no-build-isolation
|
| 141 |
+
# or
|
| 142 |
+
make install
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
### Updating dependencies
|
| 146 |
+
|
| 147 |
+
```bash
|
| 148 |
+
uv sync --upgrade --no-build-isolation
|
| 149 |
+
# or
|
| 150 |
+
make update
|
| 151 |
+
```
|
| 152 |
+
|
| 153 |
+
## π¨ Code Style
|
| 154 |
+
|
| 155 |
+
- Follow PEP 8 guidelines
|
| 156 |
+
- Use meaningful variable names
|
| 157 |
+
- Add docstrings to functions
|
| 158 |
+
- Keep functions focused and small
|
| 159 |
+
|
| 160 |
+
### Formatting (optional)
|
| 161 |
+
|
| 162 |
+
Install formatting tools:
|
| 163 |
+
```bash
|
| 164 |
+
uv add --dev black ruff
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
Format code:
|
| 168 |
+
```bash
|
| 169 |
+
make format
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
Lint code:
|
| 173 |
+
```bash
|
| 174 |
+
make lint
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
## π Making Changes
|
| 178 |
+
|
| 179 |
+
1. Create a new branch for your feature
|
| 180 |
+
```bash
|
| 181 |
+
git checkout -b feature/your-feature-name
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
2. Make your changes
|
| 185 |
+
|
| 186 |
+
3. Test locally
|
| 187 |
+
```bash
|
| 188 |
+
make run
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
4. Commit your changes
|
| 192 |
+
```bash
|
| 193 |
+
git add .
|
| 194 |
+
git commit -m "Description of changes"
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
5. Push and create a pull request (if working with others)
|
| 198 |
+
```bash
|
| 199 |
+
git push origin feature/your-feature-name
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
6. Or merge to main and deploy
|
| 203 |
+
```bash
|
| 204 |
+
git checkout main
|
| 205 |
+
git merge feature/your-feature-name
|
| 206 |
+
make deploy
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
## π Troubleshooting
|
| 210 |
+
|
| 211 |
+
### Module not found error
|
| 212 |
+
|
| 213 |
+
If you see `ModuleNotFoundError`:
|
| 214 |
+
```bash
|
| 215 |
+
uv sync --no-build-isolation # or make install
|
| 216 |
+
# Alternative:
|
| 217 |
+
uv pip install -r requirements.txt
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
### Build backend errors
|
| 221 |
+
|
| 222 |
+
The project is configured to skip build steps. If you still encounter hatchling/build errors:
|
| 223 |
+
```bash
|
| 224 |
+
./run_simple.sh
|
| 225 |
+
# or use pip directly:
|
| 226 |
+
uv pip install -r requirements.txt
|
| 227 |
+
uv run streamlit run app.py
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
### Port already in use
|
| 231 |
+
|
| 232 |
+
If port 8501 is already in use:
|
| 233 |
+
```bash
|
| 234 |
+
uv run --no-build-isolation streamlit run app.py --server.port 8502
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
### uv not found
|
| 238 |
+
|
| 239 |
+
Make sure uv is in your PATH. After installation, restart your terminal or run:
|
| 240 |
+
```bash
|
| 241 |
+
source ~/.cargo/env
|
| 242 |
+
```
|
| 243 |
+
|
| 244 |
+
## π Project Structure
|
| 245 |
+
|
| 246 |
+
```
|
| 247 |
+
template-matching/
|
| 248 |
+
βββ app.py # Main Streamlit application
|
| 249 |
+
βββ requirements.txt # Dependencies for HuggingFace Spaces
|
| 250 |
+
βββ pyproject.toml # Python project configuration (uv)
|
| 251 |
+
βββ Dockerfile # Docker configuration for HF Spaces
|
| 252 |
+
βββ packages.txt # System packages for HF Spaces
|
| 253 |
+
βββ README.md # User-facing documentation
|
| 254 |
+
βββ CONTRIBUTING.md # This file - developer documentation
|
| 255 |
+
βββ LICENSE # MIT License
|
| 256 |
+
βββ setup.sh # Setup script
|
| 257 |
+
βββ run_local.sh # Local run script
|
| 258 |
+
βββ deploy.sh # Deployment script
|
| 259 |
+
βββ Makefile # Make commands for convenience
|
| 260 |
+
βββ .gitignore # Git ignore patterns
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
## π€ Contributing Guidelines
|
| 264 |
+
|
| 265 |
+
1. Keep the educational focus in mind
|
| 266 |
+
2. Maintain interactive and engaging features
|
| 267 |
+
3. Test thoroughly before deploying
|
| 268 |
+
4. Update documentation for any new features
|
| 269 |
+
5. Keep dependencies minimal and up-to-date
|
| 270 |
+
|
| 271 |
+
## π License
|
| 272 |
+
|
| 273 |
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
Makefile
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Makefile for Template Matching Demo
|
| 2 |
+
.PHONY: help setup run deploy clean test format lint install
|
| 3 |
+
|
| 4 |
+
# Default target
|
| 5 |
+
.DEFAULT_GOAL := help
|
| 6 |
+
|
| 7 |
+
# Colors
|
| 8 |
+
BLUE := \033[0;34m
|
| 9 |
+
GREEN := \033[0;32m
|
| 10 |
+
YELLOW := \033[1;33m
|
| 11 |
+
NC := \033[0m
|
| 12 |
+
|
| 13 |
+
help: ## Show this help message
|
| 14 |
+
@echo "$(BLUE)Template Matching Demo - Available Commands$(NC)"
|
| 15 |
+
@echo ""
|
| 16 |
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(GREEN)%-15s$(NC) %s\n", $$1, $$2}'
|
| 17 |
+
@echo ""
|
| 18 |
+
|
| 19 |
+
setup: ## Initial setup - install uv and dependencies
|
| 20 |
+
@echo "$(BLUE)π§ Running setup...$(NC)"
|
| 21 |
+
@chmod +x setup.sh
|
| 22 |
+
@./setup.sh
|
| 23 |
+
|
| 24 |
+
install: ## Install/update dependencies using uv
|
| 25 |
+
@echo "$(BLUE)π¦ Installing dependencies...$(NC)"
|
| 26 |
+
@uv sync --no-build-isolation
|
| 27 |
+
@echo "$(GREEN)β
Dependencies installed$(NC)"
|
| 28 |
+
|
| 29 |
+
run: ## Run the app locally
|
| 30 |
+
@chmod +x run_local.sh
|
| 31 |
+
@./run_local.sh
|
| 32 |
+
|
| 33 |
+
deploy: ## Deploy to HuggingFace Spaces
|
| 34 |
+
@chmod +x deploy.sh
|
| 35 |
+
@./deploy.sh
|
| 36 |
+
|
| 37 |
+
clean: ## Clean up cache and temporary files
|
| 38 |
+
@echo "$(BLUE)π§Ή Cleaning up...$(NC)"
|
| 39 |
+
@find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
| 40 |
+
@find . -type f -name "*.pyc" -delete 2>/dev/null || true
|
| 41 |
+
@find . -type f -name "*.pyo" -delete 2>/dev/null || true
|
| 42 |
+
@find . -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true
|
| 43 |
+
@find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true
|
| 44 |
+
@echo "$(GREEN)β
Cleanup complete$(NC)"
|
| 45 |
+
|
| 46 |
+
format: ## Format code with black and isort (if installed)
|
| 47 |
+
@echo "$(BLUE)π¨ Formatting code...$(NC)"
|
| 48 |
+
@if command -v black > /dev/null; then \
|
| 49 |
+
uv run black app.py; \
|
| 50 |
+
else \
|
| 51 |
+
echo "$(YELLOW)β οΈ black not installed, skipping$(NC)"; \
|
| 52 |
+
fi
|
| 53 |
+
|
| 54 |
+
lint: ## Run linting checks
|
| 55 |
+
@echo "$(BLUE)π Running linting...$(NC)"
|
| 56 |
+
@if command -v ruff > /dev/null; then \
|
| 57 |
+
uv run ruff check app.py; \
|
| 58 |
+
else \
|
| 59 |
+
echo "$(YELLOW)β οΈ ruff not installed, skipping$(NC)"; \
|
| 60 |
+
fi
|
| 61 |
+
|
| 62 |
+
test: ## Run tests (if any)
|
| 63 |
+
@echo "$(BLUE)π§ͺ Running tests...$(NC)"
|
| 64 |
+
@echo "$(YELLOW)β οΈ No tests configured yet$(NC)"
|
| 65 |
+
|
| 66 |
+
check: format lint ## Run all checks (format + lint)
|
| 67 |
+
@echo "$(GREEN)β
All checks complete$(NC)"
|
| 68 |
+
|
| 69 |
+
update: ## Update all dependencies
|
| 70 |
+
@echo "$(BLUE)β¬οΈ Updating dependencies...$(NC)"
|
| 71 |
+
@uv sync --upgrade --no-build-isolation
|
| 72 |
+
@echo "$(GREEN)β
Dependencies updated$(NC)"
|
| 73 |
+
|
| 74 |
+
info: ## Show project information
|
| 75 |
+
@echo "$(BLUE)π Project Information$(NC)"
|
| 76 |
+
@echo ""
|
| 77 |
+
@echo " Name: Template Matching Demo"
|
| 78 |
+
@echo " Python: $$(python3 --version | awk '{print $$2}')"
|
| 79 |
+
@echo " UV: $$(uv --version 2>/dev/null || echo 'not installed')"
|
| 80 |
+
@echo " Repository: https://github.com/amithjkamath/template-matching"
|
| 81 |
+
@echo " HF Space: https://huggingface.co/spaces/amithjkamath/template-matching"
|
| 82 |
+
@echo ""
|
QUICKSTART.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# π Quick Start Guide
|
| 2 |
+
|
| 3 |
+
## First Time Setup
|
| 4 |
+
|
| 5 |
+
1. **Run the setup script:**
|
| 6 |
+
```bash
|
| 7 |
+
./setup.sh
|
| 8 |
+
```
|
| 9 |
+
|
| 10 |
+
This will:
|
| 11 |
+
- Check Python version (3.11+ required)
|
| 12 |
+
- Install uv package manager if needed
|
| 13 |
+
- Install all dependencies
|
| 14 |
+
- Set script permissions
|
| 15 |
+
|
| 16 |
+
## Running the App
|
| 17 |
+
|
| 18 |
+
### Simple Script
|
| 19 |
+
If you encounter build issues, use the simpler version:
|
| 20 |
+
```bash
|
| 21 |
+
./run_simple.sh
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### Option 3: Make Command
|
| 25 |
+
```bash
|
| 26 |
+
make run
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
### Option 4: Direct Command
|
| 30 |
+
```bash
|
| 31 |
+
uv run --no-build-isolation streamlit run app.py
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
The app will open in your browser at `http://localhost:8501`
|
| 35 |
+
|
| 36 |
+
## Deploying to HuggingFace Spaces
|
| 37 |
+
|
| 38 |
+
### Option 1: Shell Script
|
| 39 |
+
```bash
|
| 40 |
+
./deploy.sh
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### Option 2: Make Command
|
| 44 |
+
```bash
|
| 45 |
+
make deploy
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
The script will:
|
| 49 |
+
- Check for uncommitted changes
|
| 50 |
+
- Prompt you to commit if needed
|
| 51 |
+
- Push to the repository
|
| 52 |
+
- HuggingFace Spaces will automatically rebuild
|
| 53 |
+
|
| 54 |
+
## Troubleshooting
|
| 55 |
+
|
| 56 |
+
### "Build backend error" or hatchling issues
|
| 57 |
+
The project is now configured to skip the build step. If you still see build errors:
|
| 58 |
+
```bash
|
| 59 |
+
./run_simple.sh
|
| 60 |
+
# or
|
| 61 |
+
uv pip install -r requirements.txt
|
| 62 |
+
uv run streamlit run app.py
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
### "Module not found" error
|
| 66 |
+
```bash
|
| 67 |
+
uv sync --no-build-isolation
|
| 68 |
+
# or
|
| 69 |
+
uv pip install -r requirements.txt
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
### Port 8501 already in use
|
| 73 |
+
```bash
|
| 74 |
+
uv run --no-build-isolation streamlit run app.py --server.port 8502
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
### Script permission denied
|
| 78 |
+
```bash
|
| 79 |
+
chmod +x setup.sh run_local.sh run_simple.sh deploy.sh
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## Available Commands
|
| 83 |
+
|
| 84 |
+
Run `make help` to see all available commands:
|
| 85 |
+
|
| 86 |
+
```bash
|
| 87 |
+
make help # Show all commands
|
| 88 |
+
make setup # Initial setup
|
| 89 |
+
make install # Install dependencies
|
| 90 |
+
make run # Run locally
|
| 91 |
+
make deploy # Deploy to HuggingFace
|
| 92 |
+
make clean # Clean cache files
|
| 93 |
+
make update # Update dependencies
|
| 94 |
+
make info # Show project info
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
## Need Help?
|
| 98 |
+
|
| 99 |
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed documentation.
|
README.md
CHANGED
|
@@ -6,27 +6,120 @@ colorTo: green
|
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
license: mit
|
| 9 |
-
short_description:
|
| 10 |
---
|
| 11 |
|
| 12 |
-
#
|
| 13 |
|
| 14 |
-
|
| 15 |
|
| 16 |
-
|
| 17 |
|
| 18 |
-
|
| 19 |
|
| 20 |
-
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
##
|
| 23 |
|
| 24 |
-
|
| 25 |
|
| 26 |
-
##
|
| 27 |
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
##
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
license: mit
|
| 9 |
+
short_description: Interactive demonstration of template matching with Waldo
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# π Template Matching Demo
|
| 13 |
|
| 14 |
+
An interactive educational tool that demonstrates how template matching works using computer vision. Learn by doing - click and drag to explore how computers find objects in images!
|
| 15 |
|
| 16 |
+
## π― What is Template Matching?
|
| 17 |
|
| 18 |
+
Template Matching is a simple yet powerful computer vision method that finds the location of a template image within a larger scene by:
|
| 19 |
|
| 20 |
+
1. **Sliding** the template across all possible positions in the scene
|
| 21 |
+
2. **Computing** a similarity score at each position
|
| 22 |
+
3. **Identifying** the location with the highest similarity score
|
| 23 |
|
| 24 |
+
## π Try It Live
|
| 25 |
|
| 26 |
+
**[Launch the Interactive Demo](https://huggingface.co/spaces/amithjkamath/template-matching)**
|
| 27 |
|
| 28 |
+
## β¨ Features
|
| 29 |
|
| 30 |
+
- π±οΈ **Interactive Template Placement**: Click anywhere on the image to test template matching
|
| 31 |
+
- π **Real-time Correlation Scores**: See quantitative match scores (0.0-1.0) with color coding
|
| 32 |
+
- π¬ **Zoomed Comparison View**: Side-by-side visualization of template vs. current patch
|
| 33 |
+
- π¨ **Educational Heatmaps**: Understand how correlation works across the entire scene
|
| 34 |
+
- π― **Instant Feedback**: Learn through exploration and immediate visual feedback
|
| 35 |
|
| 36 |
+
## π» Run Locally
|
| 37 |
|
| 38 |
+
### Quick Start
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
# Clone the repository
|
| 42 |
+
git clone https://github.com/amithjkamath/template-matching.git
|
| 43 |
+
cd template-matching
|
| 44 |
+
|
| 45 |
+
# Run setup (installs uv and dependencies)
|
| 46 |
+
./setup.sh
|
| 47 |
+
|
| 48 |
+
# Run the app
|
| 49 |
+
./run_local.sh
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
### Using Make (Alternative)
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
make setup # Initial setup
|
| 56 |
+
make run # Run the app
|
| 57 |
+
make deploy # Deploy to HuggingFace
|
| 58 |
+
make help # See all commands
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
### Manual Setup
|
| 62 |
+
|
| 63 |
+
```bash
|
| 64 |
+
# Install uv (if not already installed)
|
| 65 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 66 |
+
|
| 67 |
+
# Install dependencies
|
| 68 |
+
uv sync
|
| 69 |
+
|
| 70 |
+
# Run the app
|
| 71 |
+
uv run streamlit run app.py
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
## π Requirements
|
| 75 |
+
|
| 76 |
+
- Python 3.11 or higher
|
| 77 |
+
- [uv](https://github.com/astral-sh/uv) package manager (installed by setup.sh)
|
| 78 |
+
|
| 79 |
+
## π οΈ Development
|
| 80 |
+
|
| 81 |
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed development setup and guidelines.
|
| 82 |
+
|
| 83 |
+
## π What situations could this method be applied to?
|
| 84 |
+
|
| 85 |
+
Template matching works best when:
|
| 86 |
+
- β
The template is exactly replicated in the scene
|
| 87 |
+
- β
Same scale (size) and orientation
|
| 88 |
+
- β
No rotation or geometric transformations
|
| 89 |
+
- β
Similar lighting conditions
|
| 90 |
+
|
| 91 |
+
## β οΈ When would it not work?
|
| 92 |
+
|
| 93 |
+
Template matching struggles with:
|
| 94 |
+
- β Scale changes (different sizes)
|
| 95 |
+
- β Rotation or perspective changes
|
| 96 |
+
- β Significant lighting/color differences
|
| 97 |
+
- β Partial occlusions
|
| 98 |
+
|
| 99 |
+
## π¬ Better Alternatives?
|
| 100 |
+
|
| 101 |
+
For scenarios with transformations, consider feature-based matching methods:
|
| 102 |
+
- **SIFT** (Scale-Invariant Feature Transform)
|
| 103 |
+
- **SURF** (Speeded-Up Robust Features)
|
| 104 |
+
- **ORB** (Oriented FAST and Rotated BRIEF)
|
| 105 |
+
- **AKAZE** (Accelerated-KAZE)
|
| 106 |
+
|
| 107 |
+
These methods are more robust to scale, rotation, and illumination changes.
|
| 108 |
+
|
| 109 |
+
## π€ Contributing
|
| 110 |
+
|
| 111 |
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
| 112 |
+
|
| 113 |
+
## π License
|
| 114 |
+
|
| 115 |
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
| 116 |
+
|
| 117 |
+
## π¨ Image Attribution
|
| 118 |
+
|
| 119 |
+
Images from "Where's Waldo?" are copyrighted by their original owners and are used here purely for educational purposes.
|
| 120 |
+
|
| 121 |
+
## π Acknowledgments
|
| 122 |
+
|
| 123 |
+
Inspired by:
|
| 124 |
+
- [OpenCV Web App with Streamlit](https://www.loginradius.com/blog/engineering/guest-post/opencv-web-app-with-streamlit/)
|
| 125 |
+
- [Finding Waldo: Feature Matching for OpenCV](https://medium.com/analytics-vidhya/finding-waldo-feature-matching-for-opencv-9bded7f5ab10)
|
app.py
CHANGED
|
@@ -1,13 +1,16 @@
|
|
| 1 |
"""
|
| 2 |
Inspired by https://www.loginradius.com/blog/engineering/guest-post/opencv-web-app-with-streamlit/
|
| 3 |
-
and https://medium.com/analytics-vidhya/finding-waldo-feature-matching-for-opencv-9bded7f5ab10
|
| 4 |
"""
|
|
|
|
| 5 |
import numpy as np
|
| 6 |
import cv2 as cv
|
| 7 |
import streamlit as st
|
| 8 |
from huggingface_hub import hf_hub_download
|
|
|
|
| 9 |
|
| 10 |
|
|
|
|
| 11 |
def compute_correlation(scene: np.array, template: np.array):
|
| 12 |
"""
|
| 13 |
COMPUTE_CORRELATION computes the correlation between the pixels in scene and template
|
|
@@ -24,113 +27,359 @@ def compute_correlation(scene: np.array, template: np.array):
|
|
| 24 |
return res
|
| 25 |
|
| 26 |
|
| 27 |
-
def
|
| 28 |
"""
|
| 29 |
-
|
|
|
|
| 30 |
"""
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
st.title("Template Matching Demo")
|
| 34 |
-
st.subheader(
|
| 35 |
-
"This app demonstrates how a template matching algorithm works: by sliding the template across the scene!")
|
| 36 |
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
| 38 |
template_path = hf_hub_download(
|
| 39 |
repo_id="amithjkamath/exampleimages",
|
| 40 |
filename="waldo-template.jpeg",
|
| 41 |
-
repo_type="dataset"
|
| 42 |
)
|
| 43 |
template_image = cv.imread(template_path)
|
| 44 |
template_image = cv.cvtColor(template_image, cv.COLOR_BGR2RGB)
|
| 45 |
|
| 46 |
-
|
| 47 |
-
"To introduce this method, let's first get introduced to our protagonist - Waldo")
|
| 48 |
-
|
| 49 |
-
st.text("Introducing Waldo!")
|
| 50 |
-
st.image(template_image, width=100)
|
| 51 |
-
|
| 52 |
-
st.markdown(
|
| 53 |
-
"Now for the fun bit - can you find Waldo in the scene below? Most of us will take about 20 seconds, if not more!")
|
| 54 |
-
|
| 55 |
scene_path = hf_hub_download(
|
| 56 |
repo_id="amithjkamath/exampleimages",
|
| 57 |
filename="waldo-scene.jpeg",
|
| 58 |
-
repo_type="dataset"
|
| 59 |
)
|
| 60 |
scene_image = cv.imread(scene_path)
|
| 61 |
scene_image = cv.cvtColor(scene_image, cv.COLOR_BGR2RGB)
|
| 62 |
|
| 63 |
-
|
| 64 |
-
st.image(scene_image, width=1000)
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
st.markdown(
|
| 67 |
-
"
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
st.markdown(
|
| 73 |
-
"
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
cv.rectangle(
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
scene_image = scene_image.astype(np.uint8)
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
cv.BORDER_CONSTANT)
|
| 106 |
-
norm_corr = cv.multiply(255.0, norm_corr)
|
| 107 |
-
norm_corr = np.dstack((norm_corr, norm_corr, norm_corr))
|
| 108 |
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
-
|
| 115 |
-
absolute_loc_y = absolute_loc // (out_shape[1] - template_shape[1])
|
| 116 |
-
absolute_loc_x = absolute_loc % (out_shape[0] - template_shape[2])
|
| 117 |
-
out_image[0:absolute_loc_y, :, :] = norm_corr[0:absolute_loc_y, :, :]
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
# draw rectangle on places where it exceeds threshold
|
| 125 |
-
cv.rectangle(
|
| 126 |
-
out_image, pt, (pt[0] + template_shape[1], pt[1] + template_shape[2]), (0, 255, 0), 2)
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
|
| 131 |
-
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
|
| 134 |
|
| 135 |
-
if __name__ ==
|
| 136 |
main_loop()
|
|
|
|
| 1 |
"""
|
| 2 |
Inspired by https://www.loginradius.com/blog/engineering/guest-post/opencv-web-app-with-streamlit/
|
| 3 |
+
and https://medium.com/analytics-vidhya/finding-waldo-feature-matching-for-opencv-9bded7f5ab10
|
| 4 |
"""
|
| 5 |
+
|
| 6 |
import numpy as np
|
| 7 |
import cv2 as cv
|
| 8 |
import streamlit as st
|
| 9 |
from huggingface_hub import hf_hub_download
|
| 10 |
+
from streamlit_image_coordinates import streamlit_image_coordinates
|
| 11 |
|
| 12 |
|
| 13 |
+
@st.cache_data
|
| 14 |
def compute_correlation(scene: np.array, template: np.array):
|
| 15 |
"""
|
| 16 |
COMPUTE_CORRELATION computes the correlation between the pixels in scene and template
|
|
|
|
| 27 |
return res
|
| 28 |
|
| 29 |
|
| 30 |
+
def compute_patch_correlation(scene_patch: np.array, template: np.array):
|
| 31 |
"""
|
| 32 |
+
Compute correlation score between a scene patch and template.
|
| 33 |
+
Returns a score between 0 and 1, where 1 is a perfect match.
|
| 34 |
"""
|
| 35 |
+
# Ensure patches are the same size
|
| 36 |
+
if scene_patch.shape != template.shape:
|
| 37 |
+
return 0.0
|
| 38 |
+
|
| 39 |
+
gray_patch = cv.cvtColor(scene_patch, cv.COLOR_BGR2GRAY)
|
| 40 |
+
cv.normalize(gray_patch, gray_patch, 0, 255, cv.NORM_MINMAX)
|
| 41 |
+
|
| 42 |
+
gray_template = cv.cvtColor(template, cv.COLOR_BGR2GRAY)
|
| 43 |
+
cv.normalize(gray_template, gray_template, 0, 255, cv.NORM_MINMAX)
|
| 44 |
+
|
| 45 |
+
# Use normalized cross-correlation
|
| 46 |
+
result = cv.matchTemplate(gray_patch, gray_template, cv.TM_CCOEFF_NORMED)
|
| 47 |
+
return float(result[0, 0]) if result.size > 0 else 0.0
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def extract_patch(scene: np.array, x: int, y: int, template_shape):
|
| 51 |
+
"""
|
| 52 |
+
Extract a patch from the scene centered at (x, y) with the same size as the template.
|
| 53 |
+
"""
|
| 54 |
+
h, w = template_shape[0], template_shape[1]
|
| 55 |
+
|
| 56 |
+
# Calculate patch boundaries
|
| 57 |
+
y_start = max(0, y - h // 2)
|
| 58 |
+
y_end = min(scene.shape[0], y_start + h)
|
| 59 |
+
x_start = max(0, x - w // 2)
|
| 60 |
+
x_end = min(scene.shape[1], x_start + w)
|
| 61 |
+
|
| 62 |
+
# Adjust if we hit boundaries
|
| 63 |
+
if y_end - y_start < h:
|
| 64 |
+
y_start = max(0, y_end - h)
|
| 65 |
+
if x_end - x_start < w:
|
| 66 |
+
x_start = max(0, x_end - w)
|
| 67 |
+
|
| 68 |
+
patch = scene[y_start:y_end, x_start:x_end]
|
| 69 |
+
|
| 70 |
+
# Pad if necessary (edge cases)
|
| 71 |
+
if patch.shape[0] < h or patch.shape[1] < w:
|
| 72 |
+
patch = cv.copyMakeBorder(
|
| 73 |
+
patch,
|
| 74 |
+
0,
|
| 75 |
+
h - patch.shape[0],
|
| 76 |
+
0,
|
| 77 |
+
w - patch.shape[1],
|
| 78 |
+
cv.BORDER_CONSTANT,
|
| 79 |
+
value=[0, 0, 0],
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
return patch, (x_start, y_start, x_end, y_end)
|
| 83 |
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
+
@st.cache_resource
|
| 86 |
+
def load_images():
|
| 87 |
+
"""Load and cache the template and scene images."""
|
| 88 |
+
# Load template
|
| 89 |
template_path = hf_hub_download(
|
| 90 |
repo_id="amithjkamath/exampleimages",
|
| 91 |
filename="waldo-template.jpeg",
|
| 92 |
+
repo_type="dataset",
|
| 93 |
)
|
| 94 |
template_image = cv.imread(template_path)
|
| 95 |
template_image = cv.cvtColor(template_image, cv.COLOR_BGR2RGB)
|
| 96 |
|
| 97 |
+
# Load scene
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
scene_path = hf_hub_download(
|
| 99 |
repo_id="amithjkamath/exampleimages",
|
| 100 |
filename="waldo-scene.jpeg",
|
| 101 |
+
repo_type="dataset",
|
| 102 |
)
|
| 103 |
scene_image = cv.imread(scene_path)
|
| 104 |
scene_image = cv.cvtColor(scene_image, cv.COLOR_BGR2RGB)
|
| 105 |
|
| 106 |
+
return template_image, scene_image
|
|
|
|
| 107 |
|
| 108 |
+
|
| 109 |
+
def main_loop():
|
| 110 |
+
"""
|
| 111 |
+
MAIN_LOOP is the main loop (duh) for this streamlit App.
|
| 112 |
+
"""
|
| 113 |
+
st.set_page_config(layout="wide")
|
| 114 |
+
|
| 115 |
+
st.title("π Interactive Template Matching Demo")
|
| 116 |
st.markdown(
|
| 117 |
+
"""
|
| 118 |
+
Welcome! This app teaches you how **template matching** works - a fundamental computer vision technique.
|
| 119 |
+
You'll learn by doing: click and drag the template around to see how computers "find" objects in images!
|
| 120 |
+
"""
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# Load images (cached)
|
| 124 |
+
template_image, scene_image = load_images()
|
| 125 |
+
|
| 126 |
+
# Introduction
|
| 127 |
+
col1, col2 = st.columns([1, 3])
|
| 128 |
+
|
| 129 |
+
with col1:
|
| 130 |
+
st.markdown("### Meet Waldo π")
|
| 131 |
+
st.image(template_image, caption="Our template to find")
|
| 132 |
+
st.markdown(
|
| 133 |
+
"**Template size:** {}Γ{}".format(
|
| 134 |
+
template_image.shape[1], template_image.shape[0]
|
| 135 |
+
)
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
with col2:
|
| 139 |
+
st.markdown("### The Challenge")
|
| 140 |
+
st.markdown(
|
| 141 |
+
"""
|
| 142 |
+
Can you spot Waldo in this busy scene? Most people take 20+ seconds!
|
| 143 |
+
|
| 144 |
+
But computers can do it differently. Instead of using intuition, they use **template matching**:
|
| 145 |
+
- Slide the template over every possible position
|
| 146 |
+
- At each position, compute a **similarity score**
|
| 147 |
+
- The highest score reveals where Waldo is!
|
| 148 |
+
"""
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
# Show the scene
|
| 152 |
+
st.markdown("---")
|
| 153 |
+
st.markdown("### π― Try It Yourself: Interactive Template Matching")
|
| 154 |
st.markdown(
|
| 155 |
+
"""
|
| 156 |
+
**Instructions:** Click anywhere on the image below to place the template there.
|
| 157 |
+
Watch how the correlation score changes! Can you find where Waldo actually is?
|
| 158 |
+
"""
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
# Initialize session state for template position
|
| 162 |
+
if "template_x" not in st.session_state:
|
| 163 |
+
st.session_state.template_x = scene_image.shape[1] // 4
|
| 164 |
+
st.session_state.template_y = scene_image.shape[0] // 4
|
| 165 |
+
|
| 166 |
+
if "last_computed_x" not in st.session_state:
|
| 167 |
+
st.session_state.last_computed_x = st.session_state.template_x
|
| 168 |
+
st.session_state.last_computed_y = st.session_state.template_y
|
| 169 |
+
|
| 170 |
+
if "computed_score" not in st.session_state:
|
| 171 |
+
st.session_state.computed_score = None
|
| 172 |
+
|
| 173 |
+
if "computed_patch" not in st.session_state:
|
| 174 |
+
st.session_state.computed_patch = None
|
| 175 |
+
|
| 176 |
+
# Create interactive image
|
| 177 |
+
col_left, col_right = st.columns([2, 1])
|
| 178 |
+
|
| 179 |
+
with col_left:
|
| 180 |
+
st.markdown("**Click on the image to move the template:**")
|
| 181 |
+
|
| 182 |
+
# Create overlay image with template
|
| 183 |
+
display_image = scene_image.copy()
|
| 184 |
+
t_h, t_w = template_image.shape[0], template_image.shape[1]
|
| 185 |
+
|
| 186 |
+
# Calculate template position (top-left corner)
|
| 187 |
+
x = st.session_state.template_x
|
| 188 |
+
y = st.session_state.template_y
|
| 189 |
+
x_start = max(0, x - t_w // 2)
|
| 190 |
+
y_start = max(0, y - t_h // 2)
|
| 191 |
+
x_end = min(scene_image.shape[1], x_start + t_w)
|
| 192 |
+
y_end = min(scene_image.shape[0], y_start + t_h)
|
| 193 |
+
|
| 194 |
+
# Draw rectangle around template position
|
| 195 |
cv.rectangle(
|
| 196 |
+
display_image, (x_start, y_start), (x_end, y_end), (255, 255, 0), 3
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
# Overlay semi-transparent template
|
| 200 |
+
overlay = display_image.copy()
|
| 201 |
+
if y_end - y_start == t_h and x_end - x_start == t_w:
|
| 202 |
+
overlay[y_start:y_end, x_start:x_end] = cv.addWeighted(
|
| 203 |
+
overlay[y_start:y_end, x_start:x_end], 0.5, template_image, 0.5, 0
|
| 204 |
+
)
|
| 205 |
+
display_image = cv.addWeighted(display_image, 0.7, overlay, 0.3, 0)
|
| 206 |
+
|
| 207 |
+
# Get click coordinates
|
| 208 |
+
value = streamlit_image_coordinates(display_image, key="scene_image")
|
| 209 |
+
|
| 210 |
+
# Update position only if clicked (value changed)
|
| 211 |
+
if value is not None:
|
| 212 |
+
new_x = value["x"]
|
| 213 |
+
new_y = value["y"]
|
| 214 |
+
# Only update if position actually changed
|
| 215 |
+
if (
|
| 216 |
+
new_x != st.session_state.template_x
|
| 217 |
+
or new_y != st.session_state.template_y
|
| 218 |
+
):
|
| 219 |
+
st.session_state.template_x = new_x
|
| 220 |
+
st.session_state.template_y = new_y
|
| 221 |
|
| 222 |
+
with col_right:
|
| 223 |
+
# Show current position
|
| 224 |
+
st.markdown("### π Current Position")
|
| 225 |
+
st.markdown(f"**X:** {st.session_state.template_x}px")
|
| 226 |
+
st.markdown(f"**Y:** {st.session_state.template_y}px")
|
| 227 |
|
| 228 |
+
# Check if position has changed since last computation
|
| 229 |
+
position_changed = (
|
| 230 |
+
st.session_state.template_x != st.session_state.last_computed_x
|
| 231 |
+
or st.session_state.template_y != st.session_state.last_computed_y
|
| 232 |
+
)
|
| 233 |
|
| 234 |
+
# Button to compute match
|
| 235 |
+
if position_changed:
|
| 236 |
+
st.info("π Position changed! Click below to compute match score.")
|
|
|
|
| 237 |
|
| 238 |
+
compute_button = st.button(
|
| 239 |
+
"π Compute Match Score", type="primary", use_container_width=True
|
| 240 |
+
) # Will be updated to width='stretch' in future
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
+
# Compute correlation if button clicked or initial load
|
| 243 |
+
if compute_button or st.session_state.computed_score is None:
|
| 244 |
+
with st.spinner("Computing correlation..."):
|
| 245 |
+
# Extract patch and compute correlation
|
| 246 |
+
patch, _ = extract_patch(
|
| 247 |
+
scene_image,
|
| 248 |
+
st.session_state.template_x,
|
| 249 |
+
st.session_state.template_y,
|
| 250 |
+
template_image.shape[:2],
|
| 251 |
+
)
|
| 252 |
|
| 253 |
+
score = compute_patch_correlation(patch, template_image)
|
|
|
|
|
|
|
|
|
|
| 254 |
|
| 255 |
+
# Store computed values
|
| 256 |
+
st.session_state.computed_score = score
|
| 257 |
+
st.session_state.computed_patch = patch
|
| 258 |
+
st.session_state.last_computed_x = st.session_state.template_x
|
| 259 |
+
st.session_state.last_computed_y = st.session_state.template_y
|
| 260 |
|
| 261 |
+
# Display match score with color coding
|
| 262 |
+
st.markdown("### π Match Score")
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
+
if st.session_state.computed_score is not None:
|
| 265 |
+
score = st.session_state.computed_score
|
| 266 |
|
| 267 |
+
# Determine match quality
|
| 268 |
+
if score >= 0.8:
|
| 269 |
+
quality = "π Excellent Match!"
|
| 270 |
+
color = "green"
|
| 271 |
+
explanation = "This is very likely the correct location!"
|
| 272 |
+
elif score >= 0.6:
|
| 273 |
+
quality = "β
Good Match"
|
| 274 |
+
color = "blue"
|
| 275 |
+
explanation = "Strong similarity, but maybe not perfect."
|
| 276 |
+
elif score >= 0.4:
|
| 277 |
+
quality = "β οΈ Moderate Match"
|
| 278 |
+
color = "orange"
|
| 279 |
+
explanation = "Some similarity, but probably not the right spot."
|
| 280 |
+
else:
|
| 281 |
+
quality = "β Poor Match"
|
| 282 |
+
color = "red"
|
| 283 |
+
explanation = "Very low similarity - keep searching!"
|
| 284 |
+
|
| 285 |
+
# Display score with highlighting
|
| 286 |
+
st.markdown(
|
| 287 |
+
f"""
|
| 288 |
+
<div style="background-color: {color}; padding: 20px; border-radius: 10px; text-align: center;">
|
| 289 |
+
<h2 style="color: white; margin: 0;">{score:.3f}</h2>
|
| 290 |
+
<p style="color: white; margin: 5px 0 0 0; font-size: 18px;"><b>{quality}</b></p>
|
| 291 |
+
</div>
|
| 292 |
+
""",
|
| 293 |
+
unsafe_allow_html=True,
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
st.markdown(f"*{explanation}*")
|
| 297 |
+
else:
|
| 298 |
+
st.warning("Click 'Compute Match Score' to analyze this position.")
|
| 299 |
+
|
| 300 |
+
# Show zoomed comparison
|
| 301 |
+
st.markdown("### π¬ Close-up Comparison")
|
| 302 |
+
|
| 303 |
+
if st.session_state.computed_patch is not None:
|
| 304 |
+
st.markdown("**Template vs Current Patch:**")
|
| 305 |
+
|
| 306 |
+
# Create side-by-side comparison
|
| 307 |
+
comparison = np.hstack([template_image, st.session_state.computed_patch])
|
| 308 |
+
st.image(
|
| 309 |
+
comparison,
|
| 310 |
+
caption="Left: Template | Right: Current patch",
|
| 311 |
+
width="stretch",
|
| 312 |
+
)
|
| 313 |
+
else:
|
| 314 |
+
st.info("Compute match score to see the comparison.")
|
| 315 |
+
|
| 316 |
+
# Educational section: Show the full correlation heatmap
|
| 317 |
+
st.markdown("---")
|
| 318 |
+
st.markdown("### π§ How Does the Computer Find Waldo?")
|
| 319 |
+
|
| 320 |
+
with st.expander("Click here to see the full solution!", expanded=False):
|
| 321 |
+
st.markdown(
|
| 322 |
+
"""
|
| 323 |
+
The computer doesn't guess - it's systematic! It computes the correlation score at **every possible position**.
|
| 324 |
+
Here's the resulting **correlation heatmap** where brighter areas indicate better matches:
|
| 325 |
+
"""
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
corr = compute_correlation(scene_image, template_image)
|
| 329 |
+
norm_corr = (corr - corr.min()) / (corr.max() - corr.min())
|
| 330 |
+
|
| 331 |
+
col1, col2 = st.columns(2)
|
| 332 |
+
|
| 333 |
+
with col1:
|
| 334 |
+
st.image(
|
| 335 |
+
norm_corr,
|
| 336 |
+
caption="Correlation Heatmap (bright = high match)",
|
| 337 |
+
width="stretch",
|
| 338 |
+
)
|
| 339 |
+
st.markdown("Notice the bright spot? That's where Waldo is! π―")
|
| 340 |
+
|
| 341 |
+
with col2:
|
| 342 |
+
# Show result with bounding boxes
|
| 343 |
+
result_image = scene_image.copy()
|
| 344 |
+
threshold = 0.6
|
| 345 |
+
loc = np.where(corr >= threshold)
|
| 346 |
+
template_shape = template_image.shape
|
| 347 |
+
|
| 348 |
+
for pt in zip(*loc[::-1]):
|
| 349 |
+
cv.rectangle(
|
| 350 |
+
result_image,
|
| 351 |
+
pt,
|
| 352 |
+
(pt[0] + template_shape[1], pt[1] + template_shape[0]),
|
| 353 |
+
(0, 255, 0),
|
| 354 |
+
3,
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
st.image(
|
| 358 |
+
result_image,
|
| 359 |
+
caption="Detected locations (green boxes)",
|
| 360 |
+
width="stretch",
|
| 361 |
+
)
|
| 362 |
+
st.markdown("Green boxes show all locations with correlation > 0.6")
|
| 363 |
+
|
| 364 |
+
st.markdown(
|
| 365 |
+
"""
|
| 366 |
+
**Key Insight:** Template matching is a brute-force approach that checks every possible location.
|
| 367 |
+
While simple, it's very effective for finding exact or near-exact matches!
|
| 368 |
+
"""
|
| 369 |
+
)
|
| 370 |
+
|
| 371 |
+
# Footer
|
| 372 |
+
st.markdown("---")
|
| 373 |
+
st.markdown(
|
| 374 |
+
"""
|
| 375 |
+
<small>
|
| 376 |
+
π¨ Image copyrights for "Where's Waldo?" are fully attributed to original owners.
|
| 377 |
+
Used here purely for educational purposes.
|
| 378 |
+
</small>
|
| 379 |
+
""",
|
| 380 |
+
unsafe_allow_html=True,
|
| 381 |
+
)
|
| 382 |
|
| 383 |
|
| 384 |
+
if __name__ == "__main__":
|
| 385 |
main_loop()
|
deploy.sh
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Script to deploy the app to HuggingFace Spaces
|
| 3 |
+
|
| 4 |
+
# Colors for output
|
| 5 |
+
GREEN='\033[0;32m'
|
| 6 |
+
BLUE='\033[0;34m'
|
| 7 |
+
YELLOW='\033[1;33m'
|
| 8 |
+
RED='\033[0;31m'
|
| 9 |
+
NC='\033[0m' # No Color
|
| 10 |
+
|
| 11 |
+
echo -e "${BLUE}π Deploying Template Matching Demo to HuggingFace Spaces...${NC}"
|
| 12 |
+
|
| 13 |
+
# Check if we're in a git repository
|
| 14 |
+
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
|
| 15 |
+
echo -e "${RED}β Not a git repository${NC}"
|
| 16 |
+
exit 1
|
| 17 |
+
fi
|
| 18 |
+
|
| 19 |
+
# Check if there are uncommitted changes
|
| 20 |
+
if ! git diff-index --quiet HEAD --; then
|
| 21 |
+
echo -e "${YELLOW}β οΈ You have uncommitted changes. Please commit them first.${NC}"
|
| 22 |
+
echo ""
|
| 23 |
+
echo "Uncommitted files:"
|
| 24 |
+
git status --short
|
| 25 |
+
echo ""
|
| 26 |
+
read -p "Do you want to commit all changes now? (y/n) " -n 1 -r
|
| 27 |
+
echo
|
| 28 |
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
| 29 |
+
read -p "Enter commit message: " commit_msg
|
| 30 |
+
git add .
|
| 31 |
+
git commit -m "$commit_msg"
|
| 32 |
+
echo -e "${GREEN}β
Changes committed${NC}"
|
| 33 |
+
else
|
| 34 |
+
echo -e "${RED}β Deployment cancelled. Please commit your changes first.${NC}"
|
| 35 |
+
exit 1
|
| 36 |
+
fi
|
| 37 |
+
fi
|
| 38 |
+
|
| 39 |
+
# Get current branch
|
| 40 |
+
current_branch=$(git branch --show-current)
|
| 41 |
+
echo -e "${BLUE}π Current branch: ${current_branch}${NC}"
|
| 42 |
+
|
| 43 |
+
# Check if we're on main/master branch
|
| 44 |
+
if [[ "$current_branch" != "main" && "$current_branch" != "master" ]]; then
|
| 45 |
+
echo -e "${YELLOW}β οΈ Warning: You're not on the main/master branch${NC}"
|
| 46 |
+
read -p "Continue anyway? (y/n) " -n 1 -r
|
| 47 |
+
echo
|
| 48 |
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
| 49 |
+
echo -e "${RED}β Deployment cancelled${NC}"
|
| 50 |
+
exit 1
|
| 51 |
+
fi
|
| 52 |
+
fi
|
| 53 |
+
|
| 54 |
+
# Push to origin
|
| 55 |
+
echo -e "${BLUE}π€ Pushing to remote repository...${NC}"
|
| 56 |
+
git push origin $current_branch
|
| 57 |
+
|
| 58 |
+
if [ $? -eq 0 ]; then
|
| 59 |
+
echo -e "${GREEN}β
Successfully deployed to HuggingFace Spaces!${NC}"
|
| 60 |
+
echo ""
|
| 61 |
+
echo -e "${BLUE}π Your space should update automatically at:${NC}"
|
| 62 |
+
echo -e "${GREEN} https://huggingface.co/spaces/amithjkamath/template-matching${NC}"
|
| 63 |
+
echo ""
|
| 64 |
+
echo -e "${YELLOW}π‘ Note: It may take a few minutes for the changes to appear.${NC}"
|
| 65 |
+
else
|
| 66 |
+
echo -e "${RED}β Failed to push to remote repository${NC}"
|
| 67 |
+
echo "Please check your git configuration and remote settings."
|
| 68 |
+
exit 1
|
| 69 |
+
fi
|
pyproject.toml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "template-matching"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Interactive template matching demo using OpenCV and Streamlit"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.11.9"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"opencv-python-headless>=4.8.0",
|
| 9 |
+
"streamlit>=1.28.0",
|
| 10 |
+
"streamlit-image-coordinates>=0.1.6",
|
| 11 |
+
"pillow>=10.0.0",
|
| 12 |
+
"numpy>=1.24.0",
|
| 13 |
+
"huggingface-hub>=0.17.0",
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
[tool.pylint."MESSAGES CONTROL"]
|
| 17 |
+
disable = ["c-extension-no-member"]
|
| 18 |
+
|
| 19 |
+
[tool.pyright]
|
| 20 |
+
reportGeneralTypeIssues = false
|
| 21 |
+
reportOptionalMemberAccess = false
|
requirements.txt
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
opencv-python-headless
|
| 2 |
streamlit
|
|
|
|
| 3 |
Pillow
|
| 4 |
numpy
|
| 5 |
huggingface_hub
|
|
|
|
| 1 |
opencv-python-headless
|
| 2 |
streamlit
|
| 3 |
+
streamlit-image-coordinates
|
| 4 |
Pillow
|
| 5 |
numpy
|
| 6 |
huggingface_hub
|
run_simple.sh
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Alternative simple run script using uv pip directly
|
| 3 |
+
|
| 4 |
+
# Colors for output
|
| 5 |
+
GREEN='\033[0;32m'
|
| 6 |
+
BLUE='\033[0;34m'
|
| 7 |
+
NC='\033[0m' # No Color
|
| 8 |
+
|
| 9 |
+
echo -e "${BLUE}π Starting Template Matching Demo locally...${NC}"
|
| 10 |
+
|
| 11 |
+
# Check if uv is installed
|
| 12 |
+
if ! command -v uv &> /dev/null; then
|
| 13 |
+
echo "β uv is not installed. Please install it first:"
|
| 14 |
+
echo " curl -LsSf https://astral.sh/uv/install.sh | sh"
|
| 15 |
+
exit 1
|
| 16 |
+
fi
|
| 17 |
+
|
| 18 |
+
# Install dependencies if needed
|
| 19 |
+
echo -e "${BLUE}π¦ Checking dependencies...${NC}"
|
| 20 |
+
uv pip install -q -r requirements.txt
|
| 21 |
+
|
| 22 |
+
# Run the Streamlit app
|
| 23 |
+
echo -e "${GREEN}β
Starting Streamlit app...${NC}"
|
| 24 |
+
echo -e "${BLUE}π The app will open at http://localhost:8501${NC}"
|
| 25 |
+
echo ""
|
| 26 |
+
|
| 27 |
+
uv run streamlit run app.py
|
| 28 |
+
|
| 29 |
+
# If the app exits
|
| 30 |
+
echo -e "${BLUE}π App stopped.${NC}"
|
setup.sh
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Setup script for Template Matching Demo
|
| 3 |
+
|
| 4 |
+
# Colors for output
|
| 5 |
+
GREEN='\033[0;32m'
|
| 6 |
+
BLUE='\033[0;34m'
|
| 7 |
+
YELLOW='\033[1;33m'
|
| 8 |
+
RED='\033[0;31m'
|
| 9 |
+
NC='\033[0m' # No Color
|
| 10 |
+
|
| 11 |
+
echo -e "${BLUE}π§ Setting up Template Matching Demo...${NC}"
|
| 12 |
+
echo ""
|
| 13 |
+
|
| 14 |
+
# Check Python version
|
| 15 |
+
echo -e "${BLUE}π Checking Python version...${NC}"
|
| 16 |
+
python_version=$(python3 --version 2>&1 | awk '{print $2}')
|
| 17 |
+
required_version="3.11.9"
|
| 18 |
+
|
| 19 |
+
if python3 -c "import sys; exit(0 if sys.version_info >= (3, 11) else 1)"; then
|
| 20 |
+
echo -e "${GREEN}β
Python ${python_version} is installed${NC}"
|
| 21 |
+
else
|
| 22 |
+
echo -e "${RED}β Python 3.11+ is required${NC}"
|
| 23 |
+
exit 1
|
| 24 |
+
fi
|
| 25 |
+
|
| 26 |
+
# Check if uv is installed
|
| 27 |
+
echo -e "${BLUE}π¦ Checking for uv package manager...${NC}"
|
| 28 |
+
if ! command -v uv &> /dev/null; then
|
| 29 |
+
echo -e "${YELLOW}β οΈ uv is not installed${NC}"
|
| 30 |
+
echo -e "${BLUE}Installing uv...${NC}"
|
| 31 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 32 |
+
|
| 33 |
+
# Source the shell configuration to make uv available
|
| 34 |
+
if [ -f "$HOME/.cargo/env" ]; then
|
| 35 |
+
source "$HOME/.cargo/env"
|
| 36 |
+
fi
|
| 37 |
+
|
| 38 |
+
if command -v uv &> /dev/null; then
|
| 39 |
+
echo -e "${GREEN}β
uv installed successfully${NC}"
|
| 40 |
+
else
|
| 41 |
+
echo -e "${RED}β Failed to install uv. Please install manually:${NC}"
|
| 42 |
+
echo " curl -LsSf https://astral.sh/uv/install.sh | sh"
|
| 43 |
+
exit 1
|
| 44 |
+
fi
|
| 45 |
+
else
|
| 46 |
+
echo -e "${GREEN}β
uv is already installed${NC}"
|
| 47 |
+
fi
|
| 48 |
+
|
| 49 |
+
# Sync dependencies
|
| 50 |
+
echo ""
|
| 51 |
+
echo -e "${BLUE}π₯ Installing dependencies...${NC}"
|
| 52 |
+
uv sync --no-build-isolation
|
| 53 |
+
|
| 54 |
+
if [ $? -eq 0 ]; then
|
| 55 |
+
echo -e "${GREEN}β
Dependencies installed successfully${NC}"
|
| 56 |
+
else
|
| 57 |
+
echo -e "${RED}β Failed to install dependencies${NC}"
|
| 58 |
+
exit 1
|
| 59 |
+
fi
|
| 60 |
+
|
| 61 |
+
# Make scripts executable
|
| 62 |
+
echo ""
|
| 63 |
+
echo -e "${BLUE}π Setting script permissions...${NC}"
|
| 64 |
+
chmod +x run_local.sh deploy.sh setup.sh
|
| 65 |
+
|
| 66 |
+
echo ""
|
| 67 |
+
echo -e "${GREEN}β
Setup complete!${NC}"
|
| 68 |
+
echo ""
|
| 69 |
+
echo -e "${BLUE}Next steps:${NC}"
|
| 70 |
+
echo " 1. Run locally: ${GREEN}./run_local.sh${NC}"
|
| 71 |
+
echo " 2. Deploy: ${GREEN}./deploy.sh${NC}"
|
| 72 |
+
echo ""
|
| 73 |
+
echo -e "${YELLOW}π‘ Tip: You can also use 'make run' or 'make deploy' if you prefer${NC}"
|