Spaces:
Runtime error
Runtime error
AKA Math commited on
Commit Β·
dead0ef
0
Parent(s):
Update demo
Browse files- .gitignore +37 -0
- .python-version +1 -0
- CONTRIBUTING.md +134 -0
- Dockerfile +30 -0
- GETTING_STARTED.md +353 -0
- LICENSE +21 -0
- Makefile +55 -0
- PROJECT_SUMMARY.md +333 -0
- QUICKSTART.md +123 -0
- README.md +174 -0
- app.py +997 -0
- deploy.sh +67 -0
- packages.txt +2 -0
- pyproject.toml +35 -0
- requirements.txt +5 -0
- run_simple.sh +32 -0
- setup.sh +60 -0
- test_setup.py +159 -0
.gitignore
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# Virtual environments
|
| 7 |
+
venv/
|
| 8 |
+
env/
|
| 9 |
+
ENV/
|
| 10 |
+
|
| 11 |
+
# Streamlit
|
| 12 |
+
.streamlit/
|
| 13 |
+
|
| 14 |
+
# Distribution / packaging
|
| 15 |
+
*.egg-info/
|
| 16 |
+
dist/
|
| 17 |
+
build/
|
| 18 |
+
|
| 19 |
+
# IDE
|
| 20 |
+
.vscode/
|
| 21 |
+
.idea/
|
| 22 |
+
*.swp
|
| 23 |
+
*.swo
|
| 24 |
+
*~
|
| 25 |
+
|
| 26 |
+
# OS files
|
| 27 |
+
.DS_Store
|
| 28 |
+
Thumbs.db
|
| 29 |
+
|
| 30 |
+
# Jupyter Notebook
|
| 31 |
+
.ipynb_checkpoints
|
| 32 |
+
|
| 33 |
+
# pytest
|
| 34 |
+
.pytest_cache/
|
| 35 |
+
|
| 36 |
+
# mypy
|
| 37 |
+
.mypy_cache/
|
.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.11
|
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing to Edge Detection Demo
|
| 2 |
+
|
| 3 |
+
Thank you for your interest in contributing! This educational tool is designed for teaching edge detection concepts.
|
| 4 |
+
|
| 5 |
+
## How to Contribute
|
| 6 |
+
|
| 7 |
+
### Reporting Issues
|
| 8 |
+
|
| 9 |
+
If you find bugs or have suggestions:
|
| 10 |
+
1. Check if the issue already exists in the GitHub Issues
|
| 11 |
+
2. Create a new issue with:
|
| 12 |
+
- Clear description of the problem
|
| 13 |
+
- Steps to reproduce (if it's a bug)
|
| 14 |
+
- Expected vs actual behavior
|
| 15 |
+
- Screenshots if applicable
|
| 16 |
+
|
| 17 |
+
### Suggesting Enhancements
|
| 18 |
+
|
| 19 |
+
We welcome ideas for educational improvements:
|
| 20 |
+
- Additional edge detection algorithms (e.g., Deriche, Shen-Castan)
|
| 21 |
+
- New visualization methods (e.g., 3D surface plots)
|
| 22 |
+
- Better explanations of existing concepts
|
| 23 |
+
- Interactive exercises or quizzes
|
| 24 |
+
- Multi-scale edge detection demonstrations
|
| 25 |
+
|
| 26 |
+
### Code Contributions
|
| 27 |
+
|
| 28 |
+
1. **Fork the repository**
|
| 29 |
+
|
| 30 |
+
2. **Create a feature branch:**
|
| 31 |
+
```bash
|
| 32 |
+
git checkout -b feature/your-feature-name
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
3. **Make your changes:**
|
| 36 |
+
- Follow the existing code style
|
| 37 |
+
- Add comments for complex logic
|
| 38 |
+
- Update documentation if needed
|
| 39 |
+
|
| 40 |
+
4. **Test your changes:**
|
| 41 |
+
```bash
|
| 42 |
+
streamlit run app.py
|
| 43 |
+
```
|
| 44 |
+
- Test all interactive features
|
| 45 |
+
- Verify algorithms are correct
|
| 46 |
+
- Check edge cases
|
| 47 |
+
|
| 48 |
+
5. **Commit your changes:**
|
| 49 |
+
```bash
|
| 50 |
+
git add .
|
| 51 |
+
git commit -m "Add: Brief description of your changes"
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
6. **Push and create a Pull Request:**
|
| 55 |
+
```bash
|
| 56 |
+
git push origin feature/your-feature-name
|
| 57 |
+
```
|
| 58 |
+
Then create a PR on GitHub with a clear description.
|
| 59 |
+
|
| 60 |
+
## Code Style Guidelines
|
| 61 |
+
|
| 62 |
+
### Python Code
|
| 63 |
+
|
| 64 |
+
- Follow PEP 8 style guide
|
| 65 |
+
- Use meaningful variable names
|
| 66 |
+
- Add docstrings to functions:
|
| 67 |
+
```python
|
| 68 |
+
def function_name(param):
|
| 69 |
+
"""
|
| 70 |
+
Brief description.
|
| 71 |
+
|
| 72 |
+
Args:
|
| 73 |
+
param: Description
|
| 74 |
+
|
| 75 |
+
Returns:
|
| 76 |
+
Description of return value
|
| 77 |
+
"""
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### Streamlit UI
|
| 81 |
+
|
| 82 |
+
- Keep UI simple and intuitive
|
| 83 |
+
- Use consistent markdown formatting
|
| 84 |
+
- Add helpful tooltips (help parameter in widgets)
|
| 85 |
+
- Organize content in logical sections
|
| 86 |
+
|
| 87 |
+
### Documentation
|
| 88 |
+
|
| 89 |
+
- Update README.md for major features
|
| 90 |
+
- Keep QUICKSTART.md up to date
|
| 91 |
+
- Add inline comments for complex algorithms
|
| 92 |
+
- Include references to academic sources
|
| 93 |
+
|
| 94 |
+
## Educational Content Guidelines
|
| 95 |
+
|
| 96 |
+
This is an educational tool, so clarity is paramount:
|
| 97 |
+
|
| 98 |
+
1. **Explanations should be:**
|
| 99 |
+
- Accurate and technically correct
|
| 100 |
+
- Easy to understand for graduate students
|
| 101 |
+
- Progressive (simple concepts first)
|
| 102 |
+
- Include visual examples
|
| 103 |
+
|
| 104 |
+
2. **Interactive elements should:**
|
| 105 |
+
- Provide immediate feedback
|
| 106 |
+
- Show clear cause-and-effect
|
| 107 |
+
- Include reasonable default values
|
| 108 |
+
- Have helpful tooltips
|
| 109 |
+
|
| 110 |
+
3. **Mathematical content should:**
|
| 111 |
+
- Use proper LaTeX notation
|
| 112 |
+
- Define all symbols
|
| 113 |
+
- Provide intuitive interpretations
|
| 114 |
+
- Include both equations and words
|
| 115 |
+
|
| 116 |
+
## Testing
|
| 117 |
+
|
| 118 |
+
Before submitting a PR, please verify:
|
| 119 |
+
|
| 120 |
+
- [ ] App runs without errors
|
| 121 |
+
- [ ] All sliders and controls work correctly
|
| 122 |
+
- [ ] Edge detection algorithms produce correct results
|
| 123 |
+
- [ ] Images display properly
|
| 124 |
+
- [ ] Gradient visualizations are accurate
|
| 125 |
+
- [ ] Educational content is clear and accurate
|
| 126 |
+
- [ ] No typos in text
|
| 127 |
+
|
| 128 |
+
## Questions?
|
| 129 |
+
|
| 130 |
+
Feel free to open an issue for discussion before starting major work.
|
| 131 |
+
|
| 132 |
+
## License
|
| 133 |
+
|
| 134 |
+
By contributing, you agree that your contributions will be licensed under the MIT License.
|
Dockerfile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
# Set working directory
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
# Install system dependencies
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
libgl1 \
|
| 9 |
+
libglib2.0-0 \
|
| 10 |
+
curl \
|
| 11 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
# Copy requirements first for better caching
|
| 14 |
+
COPY requirements.txt .
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 18 |
+
|
| 19 |
+
# Copy application files
|
| 20 |
+
COPY app.py .
|
| 21 |
+
COPY README.md .
|
| 22 |
+
|
| 23 |
+
# Expose Streamlit port
|
| 24 |
+
EXPOSE 8501
|
| 25 |
+
|
| 26 |
+
# Health check
|
| 27 |
+
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health || exit 1
|
| 28 |
+
|
| 29 |
+
# Run the application
|
| 30 |
+
ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
GETTING_STARTED.md
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# π Getting Started with the Edge Detection Demo
|
| 2 |
+
|
| 3 |
+
## Prerequisites
|
| 4 |
+
|
| 5 |
+
- **Python 3.11+** (Python 3.9+ should also work)
|
| 6 |
+
- **Git** (for cloning the repository)
|
| 7 |
+
- **pip** (usually comes with Python)
|
| 8 |
+
- **5-10 minutes** of your time
|
| 9 |
+
|
| 10 |
+
Optional:
|
| 11 |
+
- **Make** (for convenient commands)
|
| 12 |
+
- **Docker** (for containerized deployment)
|
| 13 |
+
|
| 14 |
+
## β‘οΈ Quick Start (30 seconds)
|
| 15 |
+
|
| 16 |
+
The absolute fastest way to get started:
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
# 1. Navigate to the project directory
|
| 20 |
+
cd edge-detection
|
| 21 |
+
|
| 22 |
+
# 2. Run the setup script
|
| 23 |
+
./setup.sh
|
| 24 |
+
|
| 25 |
+
# 3. Run the app
|
| 26 |
+
./run_simple.sh
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
That's it! Your browser should open to `http://localhost:8501`
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
## π Detailed Installation
|
| 34 |
+
|
| 35 |
+
### Step 1: Clone the Repository (if needed)
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
git clone <repository-url>
|
| 39 |
+
cd edge-detection
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
### Step 2: Choose Your Setup Method
|
| 43 |
+
|
| 44 |
+
#### Option A: Automated Setup Script (Recommended)
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
chmod +x setup.sh
|
| 48 |
+
./setup.sh
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
This will:
|
| 52 |
+
- Check Python version
|
| 53 |
+
- Create a virtual environment
|
| 54 |
+
- Install all dependencies
|
| 55 |
+
|
| 56 |
+
#### Option B: Manual Setup
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
# Create virtual environment
|
| 60 |
+
python3 -m venv venv
|
| 61 |
+
|
| 62 |
+
# Activate virtual environment
|
| 63 |
+
source venv/bin/activate # On macOS/Linux
|
| 64 |
+
# OR
|
| 65 |
+
venv\Scripts\activate # On Windows
|
| 66 |
+
|
| 67 |
+
# Upgrade pip
|
| 68 |
+
pip install --upgrade pip
|
| 69 |
+
|
| 70 |
+
# Install dependencies
|
| 71 |
+
pip install -r requirements.txt
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
#### Option C: Using Make
|
| 75 |
+
|
| 76 |
+
```bash
|
| 77 |
+
make setup
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### Step 3: Run the Application
|
| 81 |
+
|
| 82 |
+
#### Option A: Quick Run Script
|
| 83 |
+
|
| 84 |
+
```bash
|
| 85 |
+
chmod +x run_simple.sh
|
| 86 |
+
./run_simple.sh
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
#### Option B: Manual Run
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
source venv/bin/activate # Activate virtual environment
|
| 93 |
+
streamlit run app.py
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
#### Option C: Using Make
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
make run
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
### Step 4: Open in Browser
|
| 103 |
+
|
| 104 |
+
The app should automatically open in your default browser at:
|
| 105 |
+
```
|
| 106 |
+
http://localhost:8501
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
If it doesn't open automatically, manually navigate to that URL.
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
## π First Steps in the Demo
|
| 114 |
+
|
| 115 |
+
Once the app is running:
|
| 116 |
+
|
| 117 |
+
1. **Upload an Image** (optional)
|
| 118 |
+
- Look in the sidebar
|
| 119 |
+
- Click "Upload your own image"
|
| 120 |
+
- Or use the default generated image
|
| 121 |
+
|
| 122 |
+
2. **Try Different Methods**
|
| 123 |
+
- Select "Sobel" from the dropdown
|
| 124 |
+
- Observe the edge detection results
|
| 125 |
+
- Try adjusting the kernel size
|
| 126 |
+
- Enable "Show Gradient Components"
|
| 127 |
+
|
| 128 |
+
3. **Explore Prewitt and Roberts**
|
| 129 |
+
- Switch methods and compare results
|
| 130 |
+
- Notice differences in edge quality and noise sensitivity
|
| 131 |
+
|
| 132 |
+
4. **Experiment with Laplacian**
|
| 133 |
+
- Observe the isotropic response
|
| 134 |
+
- See why pre-smoothing is important
|
| 135 |
+
- Try different kernel sizes
|
| 136 |
+
|
| 137 |
+
5. **Master Canny**
|
| 138 |
+
- Adjust low and high thresholds
|
| 139 |
+
- Observe edge connectivity
|
| 140 |
+
- Compare with simpler methods using comparison mode
|
| 141 |
+
|
| 142 |
+
6. **Read Educational Content**
|
| 143 |
+
- Click on the four tabs at the bottom
|
| 144 |
+
- Read the explanations
|
| 145 |
+
- Understand the mathematical foundations
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
## π§ Troubleshooting
|
| 150 |
+
|
| 151 |
+
### Problem: "Python not found"
|
| 152 |
+
|
| 153 |
+
**Solution:**
|
| 154 |
+
```bash
|
| 155 |
+
# Check if Python is installed
|
| 156 |
+
python3 --version
|
| 157 |
+
|
| 158 |
+
# If not, install Python 3.11+ from python.org
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
### Problem: "Permission denied" when running scripts
|
| 162 |
+
|
| 163 |
+
**Solution:**
|
| 164 |
+
```bash
|
| 165 |
+
chmod +x *.sh
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
### Problem: Import errors when running
|
| 169 |
+
|
| 170 |
+
**Solution:**
|
| 171 |
+
```bash
|
| 172 |
+
# Make sure you're in the virtual environment
|
| 173 |
+
source venv/bin/activate
|
| 174 |
+
|
| 175 |
+
# Reinstall requirements
|
| 176 |
+
pip install --force-reinstall -r requirements.txt
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
### Problem: "Port 8501 already in use"
|
| 180 |
+
|
| 181 |
+
**Solution:**
|
| 182 |
+
```bash
|
| 183 |
+
# Find and kill the process using the port
|
| 184 |
+
lsof -ti:8501 | xargs kill
|
| 185 |
+
|
| 186 |
+
# Or run on a different port
|
| 187 |
+
streamlit run app.py --server.port 8502
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
### Problem: Images not displaying
|
| 191 |
+
|
| 192 |
+
**Solution:**
|
| 193 |
+
- Check internet connection (for HuggingFace download)
|
| 194 |
+
- The app will automatically fall back to a generated image
|
| 195 |
+
- Try uploading your own image via the sidebar
|
| 196 |
+
|
| 197 |
+
### Problem: Slow performance
|
| 198 |
+
|
| 199 |
+
**Solution:**
|
| 200 |
+
- Use smaller images (app auto-resizes to 512px)
|
| 201 |
+
- Close other applications
|
| 202 |
+
- Try simpler methods (Roberts, Sobel) instead of Canny
|
| 203 |
+
|
| 204 |
+
---
|
| 205 |
+
|
| 206 |
+
## π― Next Steps
|
| 207 |
+
|
| 208 |
+
### For Students
|
| 209 |
+
- Work through the educational tabs systematically
|
| 210 |
+
- Try uploading different types of images (portraits, landscapes, text)
|
| 211 |
+
- Compare all five methods on the same image
|
| 212 |
+
- Answer the discussion questions in PROJECT_SUMMARY.md
|
| 213 |
+
|
| 214 |
+
### For Instructors
|
| 215 |
+
- Review QUICKSTART.md for teaching tips
|
| 216 |
+
- Customize the app (see CONTRIBUTING.md)
|
| 217 |
+
- Deploy to Hugging Face Spaces (see below)
|
| 218 |
+
- Share with your class
|
| 219 |
+
|
| 220 |
+
### For Developers
|
| 221 |
+
- Read CONTRIBUTING.md for development guidelines
|
| 222 |
+
- Check out PROJECT_SUMMARY.md for technical details
|
| 223 |
+
- Consider adding new features (multi-scale, learning-based methods)
|
| 224 |
+
|
| 225 |
+
---
|
| 226 |
+
|
| 227 |
+
## π Deploying to Hugging Face Spaces
|
| 228 |
+
|
| 229 |
+
Want to share this with others online?
|
| 230 |
+
|
| 231 |
+
### Step 1: Create a Hugging Face Account
|
| 232 |
+
- Go to https://huggingface.co/join
|
| 233 |
+
- Sign up for free
|
| 234 |
+
|
| 235 |
+
### Step 2: Create a New Space
|
| 236 |
+
- Go to https://huggingface.co/new-space
|
| 237 |
+
- Choose "Streamlit" as the SDK
|
| 238 |
+
- Make it public (for educational use)
|
| 239 |
+
|
| 240 |
+
### Step 3: Deploy
|
| 241 |
+
|
| 242 |
+
```bash
|
| 243 |
+
chmod +x deploy.sh
|
| 244 |
+
./deploy.sh
|
| 245 |
+
```
|
| 246 |
+
|
| 247 |
+
Follow the prompts and enter your Space name (e.g., "username/edge-detection-demo").
|
| 248 |
+
|
| 249 |
+
Your demo will be live at: `https://huggingface.co/spaces/username/edge-detection-demo`
|
| 250 |
+
|
| 251 |
+
---
|
| 252 |
+
|
| 253 |
+
## π³ Docker Deployment (Advanced)
|
| 254 |
+
|
| 255 |
+
If you prefer Docker:
|
| 256 |
+
|
| 257 |
+
```bash
|
| 258 |
+
# Build the image
|
| 259 |
+
docker build -t edge-detection .
|
| 260 |
+
|
| 261 |
+
# Run the container
|
| 262 |
+
docker run -p 8501:8501 edge-detection
|
| 263 |
+
|
| 264 |
+
# Access at http://localhost:8501
|
| 265 |
+
```
|
| 266 |
+
|
| 267 |
+
---
|
| 268 |
+
|
| 269 |
+
## π Common Commands Reference
|
| 270 |
+
|
| 271 |
+
```bash
|
| 272 |
+
# Setup and installation
|
| 273 |
+
./setup.sh # Initial setup
|
| 274 |
+
make setup # Alternative using make
|
| 275 |
+
|
| 276 |
+
# Running the app
|
| 277 |
+
./run_simple.sh # Quick run
|
| 278 |
+
make run # Alternative using make
|
| 279 |
+
streamlit run app.py # Direct run
|
| 280 |
+
|
| 281 |
+
# Testing
|
| 282 |
+
python test_setup.py # Verify installation
|
| 283 |
+
make test # Alternative using make
|
| 284 |
+
|
| 285 |
+
# Deployment
|
| 286 |
+
./deploy.sh # Deploy to HuggingFace
|
| 287 |
+
make deploy # Alternative using make
|
| 288 |
+
|
| 289 |
+
# Cleanup
|
| 290 |
+
make clean # Remove virtual environment
|
| 291 |
+
```
|
| 292 |
+
|
| 293 |
+
---
|
| 294 |
+
|
| 295 |
+
## π‘ Tips for Best Experience
|
| 296 |
+
|
| 297 |
+
1. **Image Selection**: Use images with clear edges (buildings, objects, text)
|
| 298 |
+
2. **Pre-processing**: Enable Gaussian blur for noisy images
|
| 299 |
+
3. **Parameter Tuning**: Start with default values, then experiment
|
| 300 |
+
4. **Comparison**: Use comparison mode to understand differences
|
| 301 |
+
5. **Educational Content**: Read tabs in order (What Are Edges? β Gradient Methods β Canny β Practical)
|
| 302 |
+
|
| 303 |
+
---
|
| 304 |
+
|
| 305 |
+
## π Learning Path
|
| 306 |
+
|
| 307 |
+
**Beginner** (1-2 hours):
|
| 308 |
+
1. Read "What Are Edges?" tab
|
| 309 |
+
2. Try Roberts and Sobel
|
| 310 |
+
3. Compare results on sample image
|
| 311 |
+
4. Upload your own image
|
| 312 |
+
|
| 313 |
+
**Intermediate** (2-4 hours):
|
| 314 |
+
1. Study gradient-based methods tab
|
| 315 |
+
2. Experiment with all five methods
|
| 316 |
+
3. Enable gradient visualizations
|
| 317 |
+
4. Try different kernel sizes
|
| 318 |
+
5. Read Canny algorithm tab
|
| 319 |
+
|
| 320 |
+
**Advanced** (4+ hours):
|
| 321 |
+
1. Study practical considerations tab
|
| 322 |
+
2. Tune Canny parameters systematically
|
| 323 |
+
3. Compare methods quantitatively
|
| 324 |
+
4. Test on domain-specific images
|
| 325 |
+
5. Consider implementing improvements
|
| 326 |
+
|
| 327 |
+
---
|
| 328 |
+
|
| 329 |
+
## π Getting Help
|
| 330 |
+
|
| 331 |
+
- **Documentation**: Check README.md, QUICKSTART.md, PROJECT_SUMMARY.md
|
| 332 |
+
- **GitHub Issues**: Report bugs or request features
|
| 333 |
+
- **Discussion Board**: Ask questions about edge detection concepts
|
| 334 |
+
|
| 335 |
+
---
|
| 336 |
+
|
| 337 |
+
## β
Verification
|
| 338 |
+
|
| 339 |
+
To verify everything is working correctly:
|
| 340 |
+
|
| 341 |
+
```bash
|
| 342 |
+
python test_setup.py
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
This will check:
|
| 346 |
+
- All dependencies are installed
|
| 347 |
+
- App structure is correct
|
| 348 |
+
- Edge detection functions work
|
| 349 |
+
- Visualizations render properly
|
| 350 |
+
|
| 351 |
+
---
|
| 352 |
+
|
| 353 |
+
**Ready to explore edge detection?** Run `./run_simple.sh` and start learning! π
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Uni Bern Intro to Image Analysis Course
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
Makefile
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.PHONY: help setup install run clean deploy test
|
| 2 |
+
|
| 3 |
+
help:
|
| 4 |
+
@echo "π Edge Detection Demo - Available Commands:"
|
| 5 |
+
@echo ""
|
| 6 |
+
@echo " make setup - Set up virtual environment and install dependencies"
|
| 7 |
+
@echo " make install - Install/update dependencies only"
|
| 8 |
+
@echo " make run - Run the Streamlit app locally"
|
| 9 |
+
@echo " make clean - Remove virtual environment and cache files"
|
| 10 |
+
@echo " make deploy - Deploy to Hugging Face Spaces"
|
| 11 |
+
@echo " make test - Run basic tests"
|
| 12 |
+
@echo ""
|
| 13 |
+
|
| 14 |
+
setup:
|
| 15 |
+
@echo "π§ Setting up environment..."
|
| 16 |
+
@./setup.sh
|
| 17 |
+
|
| 18 |
+
install:
|
| 19 |
+
@echo "π₯ Installing dependencies..."
|
| 20 |
+
@if [ ! -d "venv" ]; then \
|
| 21 |
+
echo "β Virtual environment not found. Run 'make setup' first."; \
|
| 22 |
+
exit 1; \
|
| 23 |
+
fi
|
| 24 |
+
@. venv/bin/activate && pip install -r requirements.txt
|
| 25 |
+
@echo "β
Dependencies installed"
|
| 26 |
+
|
| 27 |
+
run:
|
| 28 |
+
@echo "π Starting Streamlit app..."
|
| 29 |
+
@if [ ! -d "venv" ]; then \
|
| 30 |
+
echo "β Virtual environment not found. Run 'make setup' first."; \
|
| 31 |
+
exit 1; \
|
| 32 |
+
fi
|
| 33 |
+
@. venv/bin/activate && streamlit run app.py
|
| 34 |
+
|
| 35 |
+
clean:
|
| 36 |
+
@echo "π§Ή Cleaning up..."
|
| 37 |
+
@rm -rf venv
|
| 38 |
+
@rm -rf __pycache__
|
| 39 |
+
@rm -rf .streamlit
|
| 40 |
+
@find . -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true
|
| 41 |
+
@find . -type f -name "*.pyc" -delete
|
| 42 |
+
@echo "β
Cleanup complete"
|
| 43 |
+
|
| 44 |
+
deploy:
|
| 45 |
+
@echo "π Deploying to Hugging Face Spaces..."
|
| 46 |
+
@./deploy.sh
|
| 47 |
+
|
| 48 |
+
test:
|
| 49 |
+
@echo "π§ͺ Running basic import tests..."
|
| 50 |
+
@if [ ! -d "venv" ]; then \
|
| 51 |
+
echo "β Virtual environment not found. Run 'make setup' first."; \
|
| 52 |
+
exit 1; \
|
| 53 |
+
fi
|
| 54 |
+
@. venv/bin/activate && python -c "import app; print('β
App imports successfully')"
|
| 55 |
+
@echo "β
Basic tests passed"
|
PROJECT_SUMMARY.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# π Edge Detection Demo - Project Summary
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
This interactive educational demo teaches computational approaches to edge detection in digital images, a fundamental operation in computer vision. The application is designed for graduate-level image analysis courses and demonstrates:
|
| 6 |
+
|
| 7 |
+
- **Classical Gradient-Based Methods**: Roberts Cross, Prewitt, and Sobel operators
|
| 8 |
+
- **Second-Order Methods**: Laplacian edge detection
|
| 9 |
+
- **Optimal Edge Detection**: The Canny multi-stage algorithm
|
| 10 |
+
- **Comparative Analysis**: Side-by-side method comparisons
|
| 11 |
+
- **Educational Theory**: Comprehensive explanations with mathematical foundations
|
| 12 |
+
|
| 13 |
+
## Key Features
|
| 14 |
+
|
| 15 |
+
### Interactive Edge Detection
|
| 16 |
+
- **Five Detection Methods**: Switch between Sobel, Prewitt, Roberts, Laplacian, and Canny
|
| 17 |
+
- **Real-Time Processing**: See edge detection results as you adjust parameters
|
| 18 |
+
- **Method-Specific Controls**: Kernel sizes, thresholds, and algorithm parameters
|
| 19 |
+
- **Pre-processing Options**: Gaussian blur for noise reduction
|
| 20 |
+
|
| 21 |
+
### Advanced Visualizations
|
| 22 |
+
- **Gradient Components**: View Gx and Gy separately (horizontal and vertical gradients)
|
| 23 |
+
- **Gradient Direction**: Color-coded visualization showing edge orientation and strength
|
| 24 |
+
- **Edge Statistics**: Quantitative metrics (edge pixel count, percentages)
|
| 25 |
+
- **Comparison Mode**: Side-by-side comparison with Canny edge detection
|
| 26 |
+
|
| 27 |
+
### Comprehensive Educational Content
|
| 28 |
+
- **Four Educational Tabs**:
|
| 29 |
+
1. **What Are Edges?**: Foundational concepts and significance
|
| 30 |
+
2. **Gradient-Based Methods**: Detailed explanations of Roberts, Prewitt, Sobel, Laplacian
|
| 31 |
+
3. **Canny Algorithm**: Complete breakdown of the five-stage optimal approach
|
| 32 |
+
4. **Practical Considerations**: Real-world application guidance
|
| 33 |
+
- **Mathematical Formulations**: Kernel definitions, gradient equations (LaTeX formatted)
|
| 34 |
+
- **"Aha!" Moments**: Key insights that crystallize understanding
|
| 35 |
+
- **Historical Context**: Evolution of edge detection from 1963 to present
|
| 36 |
+
|
| 37 |
+
## Educational Objectives
|
| 38 |
+
|
| 39 |
+
Students will learn:
|
| 40 |
+
|
| 41 |
+
1. **Mathematical Foundations**: How convolution kernels approximate derivatives
|
| 42 |
+
2. **First-Order vs. Second-Order**: Differences between gradient and Laplacian operators
|
| 43 |
+
3. **Optimal Edge Detection**: Canny's three criteria and their implementation
|
| 44 |
+
4. **Parameter Sensitivity**: How thresholds and kernel sizes affect results
|
| 45 |
+
5. **Comparative Analysis**: Strengths and weaknesses of different methods
|
| 46 |
+
6. **Practical Skills**: When to use each method in real applications
|
| 47 |
+
|
| 48 |
+
## Project Structure
|
| 49 |
+
|
| 50 |
+
```
|
| 51 |
+
edge-detection/
|
| 52 |
+
βββ app.py # Main Streamlit application (1000+ lines)
|
| 53 |
+
βββ requirements.txt # Python dependencies
|
| 54 |
+
βββ README.md # Main documentation
|
| 55 |
+
βββ QUICKSTART.md # Getting started guide
|
| 56 |
+
βββ CONTRIBUTING.md # Contribution guidelines
|
| 57 |
+
βββ LICENSE # MIT License
|
| 58 |
+
β
|
| 59 |
+
βββ Configuration Files:
|
| 60 |
+
βββ packages.txt # System dependencies for HF Spaces
|
| 61 |
+
βββ .python-version # Python 3.11
|
| 62 |
+
βββ .gitignore # Git ignore rules
|
| 63 |
+
βββ pyproject.toml # Project metadata
|
| 64 |
+
βββ Dockerfile # Docker deployment
|
| 65 |
+
βββ Makefile # Convenient make commands
|
| 66 |
+
β
|
| 67 |
+
βββ Scripts:
|
| 68 |
+
βββ setup.sh # Initial setup with venv
|
| 69 |
+
βββ run_simple.sh # Quick local run
|
| 70 |
+
βββ deploy.sh # Deploy to HuggingFace
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
## Technical Implementation
|
| 74 |
+
|
| 75 |
+
### Algorithms Implemented
|
| 76 |
+
|
| 77 |
+
#### Roberts Cross Operator (1963)
|
| 78 |
+
- 2Γ2 diagonal gradient kernels
|
| 79 |
+
- Simplest gradient operator
|
| 80 |
+
- Computational complexity: O(NΒ²)
|
| 81 |
+
- Best for: Sharp diagonal edges, computational constraints
|
| 82 |
+
|
| 83 |
+
#### Prewitt Operator (1970)
|
| 84 |
+
- 3Γ3 gradient kernels with uniform weighting
|
| 85 |
+
- Incorporates smoothing perpendicular to gradient
|
| 86 |
+
- Separable: can be computed as two 1D convolutions
|
| 87 |
+
- Best for: General-purpose edge detection, educational demonstrations
|
| 88 |
+
|
| 89 |
+
#### Sobel Operator (1968)
|
| 90 |
+
- 3Γ3 gradient kernels with center weighting (2:1)
|
| 91 |
+
- Better approximation of true gradient than Prewitt
|
| 92 |
+
- Industry-standard first-order operator
|
| 93 |
+
- Variable kernel sizes: 1Γ1, 3Γ3, 5Γ5, 7Γ7
|
| 94 |
+
- Best for: Most applications requiring gradient information
|
| 95 |
+
|
| 96 |
+
#### Laplacian Operator
|
| 97 |
+
- Second-order isotropic operator
|
| 98 |
+
- Detects zero-crossings (rapid intensity changes)
|
| 99 |
+
- Highly sensitive to noise (requires pre-smoothing)
|
| 100 |
+
- Often combined with Gaussian β Laplacian of Gaussian (LoG)
|
| 101 |
+
- Best for: Precise edge localization, blob detection
|
| 102 |
+
|
| 103 |
+
#### Canny Edge Detector (1986)
|
| 104 |
+
- Multi-stage optimal algorithm:
|
| 105 |
+
1. Gaussian smoothing (noise reduction)
|
| 106 |
+
2. Gradient calculation (Sobel)
|
| 107 |
+
3. Non-maximum suppression (edge thinning)
|
| 108 |
+
4. Double thresholding (strong/weak edges)
|
| 109 |
+
5. Hysteresis edge tracking (connectivity)
|
| 110 |
+
- Parameters: low/high thresholds, aperture size, L2 gradient
|
| 111 |
+
- Best for: High-quality edge detection, when computational cost is acceptable
|
| 112 |
+
|
| 113 |
+
### Features Highlights
|
| 114 |
+
|
| 115 |
+
**Gradient Visualization:**
|
| 116 |
+
- Displays Gx and Gy components separately
|
| 117 |
+
- Shows which edges each component detects (vertical vs. horizontal)
|
| 118 |
+
- Helps students understand gradient decomposition
|
| 119 |
+
|
| 120 |
+
**Direction Visualization:**
|
| 121 |
+
- HSV color mapping: Hue = direction, Value = magnitude
|
| 122 |
+
- Intuitive understanding of edge orientation
|
| 123 |
+
- Beautiful visualization of gradient fields
|
| 124 |
+
|
| 125 |
+
**Comparison Mode:**
|
| 126 |
+
- Compare any method with Canny
|
| 127 |
+
- Adjustable Canny parameters for fair comparison
|
| 128 |
+
- Quantitative metrics for both methods
|
| 129 |
+
|
| 130 |
+
## Quick Start
|
| 131 |
+
|
| 132 |
+
### For Users (Simplest)
|
| 133 |
+
```bash
|
| 134 |
+
chmod +x run_simple.sh
|
| 135 |
+
./run_simple.sh
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
### Using Make
|
| 139 |
+
```bash
|
| 140 |
+
make setup # First time only
|
| 141 |
+
make run # Start the app
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
### Manual Setup
|
| 145 |
+
```bash
|
| 146 |
+
python3 -m venv venv
|
| 147 |
+
source venv/bin/activate
|
| 148 |
+
pip install -r requirements.txt
|
| 149 |
+
streamlit run app.py
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
## Deployment Options
|
| 153 |
+
|
| 154 |
+
### 1. Hugging Face Spaces (Recommended)
|
| 155 |
+
```bash
|
| 156 |
+
chmod +x deploy.sh
|
| 157 |
+
./deploy.sh
|
| 158 |
+
```
|
| 159 |
+
- Free hosting
|
| 160 |
+
- Automatic builds
|
| 161 |
+
- Share with students via URL
|
| 162 |
+
- No server maintenance
|
| 163 |
+
|
| 164 |
+
### 2. Docker
|
| 165 |
+
```bash
|
| 166 |
+
docker build -t edge-detection .
|
| 167 |
+
docker run -p 8501:8501 edge-detection
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
### 3. Local Server
|
| 171 |
+
```bash
|
| 172 |
+
streamlit run app.py --server.port 8501
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
## Teaching with This Demo
|
| 176 |
+
|
| 177 |
+
### Suggested Lesson Plan
|
| 178 |
+
|
| 179 |
+
**Part 1: Introduction to Edges (15 minutes)**
|
| 180 |
+
1. Start with the "What Are Edges?" tab
|
| 181 |
+
2. Discuss why edges matter in computer vision
|
| 182 |
+
3. Show original image and various edge detection results
|
| 183 |
+
4. Emphasize: edges = local intensity changes
|
| 184 |
+
|
| 185 |
+
**Part 2: Gradient-Based Methods (30 minutes)**
|
| 186 |
+
1. **Roberts Cross**: Introduce the gradient concept
|
| 187 |
+
- Show 2Γ2 kernels
|
| 188 |
+
- Demonstrate on sample image
|
| 189 |
+
- Discuss limitations (noise sensitivity)
|
| 190 |
+
|
| 191 |
+
2. **Prewitt**: Introduce smoothing
|
| 192 |
+
- Compare with Roberts
|
| 193 |
+
- Show how 3Γ3 kernel reduces noise
|
| 194 |
+
|
| 195 |
+
3. **Sobel**: Weighted averaging
|
| 196 |
+
- Compare with Prewitt
|
| 197 |
+
- Show gradient components (Gx, Gy)
|
| 198 |
+
- Demonstrate gradient direction visualization
|
| 199 |
+
- Let students experiment with kernel sizes
|
| 200 |
+
|
| 201 |
+
4. **Laplacian**: Second-order derivatives
|
| 202 |
+
- Explain zero-crossings
|
| 203 |
+
- Show double-edge effect
|
| 204 |
+
- Demonstrate noise sensitivity
|
| 205 |
+
- Discuss why pre-smoothing is essential
|
| 206 |
+
|
| 207 |
+
**Part 3: Canny Algorithm (25 minutes)**
|
| 208 |
+
1. Introduce Canny's three criteria
|
| 209 |
+
2. Walk through each stage:
|
| 210 |
+
- Gaussian smoothing
|
| 211 |
+
- Gradient calculation
|
| 212 |
+
- Non-maximum suppression (show edge thinning)
|
| 213 |
+
- Double thresholding (show strong/weak classification)
|
| 214 |
+
- Hysteresis (explain edge connectivity)
|
| 215 |
+
3. Demonstrate parameter effects:
|
| 216 |
+
- Vary low/high thresholds
|
| 217 |
+
- Show how ratio affects edge continuity
|
| 218 |
+
4. Compare Canny with Sobel using comparison mode
|
| 219 |
+
|
| 220 |
+
**Part 4: Practical Considerations (20 minutes)**
|
| 221 |
+
1. Discuss when to use each method
|
| 222 |
+
2. Show effect of pre-processing (Gaussian blur)
|
| 223 |
+
3. Demonstrate parameter tuning strategies
|
| 224 |
+
4. Discuss computational trade-offs
|
| 225 |
+
5. Show real-world application examples
|
| 226 |
+
|
| 227 |
+
**Part 5: Interactive Exploration (10 minutes)**
|
| 228 |
+
1. Let students upload their own images
|
| 229 |
+
2. Challenge: Find optimal parameters for different image types
|
| 230 |
+
3. Compare results across methods
|
| 231 |
+
|
| 232 |
+
### Discussion Questions
|
| 233 |
+
|
| 234 |
+
1. **Conceptual:**
|
| 235 |
+
- Why are edges important in computer vision?
|
| 236 |
+
- How do derivatives relate to edges?
|
| 237 |
+
- What makes an edge detector "optimal"?
|
| 238 |
+
|
| 239 |
+
2. **Analytical:**
|
| 240 |
+
- Why does Sobel weight the center row/column more heavily?
|
| 241 |
+
- Why is the Laplacian isotropic while Sobel is directional?
|
| 242 |
+
- How does non-maximum suppression achieve single-pixel-wide edges?
|
| 243 |
+
|
| 244 |
+
3. **Practical:**
|
| 245 |
+
- When would you prefer Sobel over Canny?
|
| 246 |
+
- How do you choose Canny's threshold values?
|
| 247 |
+
- Why pre-smooth before applying Laplacian?
|
| 248 |
+
|
| 249 |
+
### Assignments
|
| 250 |
+
|
| 251 |
+
1. **Comparative Analysis**: Compare all five methods on the same image. Discuss strengths/weaknesses.
|
| 252 |
+
2. **Parameter Exploration**: Find optimal Canny thresholds for three different image types.
|
| 253 |
+
3. **Gradient Analysis**: Analyze gradient direction patterns in natural vs. man-made scenes.
|
| 254 |
+
4. **Noise Robustness**: Compare methods on images with different noise levels.
|
| 255 |
+
5. **Computational Efficiency**: Measure and compare execution times of different methods.
|
| 256 |
+
|
| 257 |
+
## Performance
|
| 258 |
+
|
| 259 |
+
- **Load Time**: < 2 seconds on first load (with caching)
|
| 260 |
+
- **Interactive Response**: Real-time (< 100ms per parameter change)
|
| 261 |
+
- **Memory Usage**: ~150-250 MB (depends on image size)
|
| 262 |
+
- **Supported Image Sizes**: Up to 4K (auto-resized to 512px for demo)
|
| 263 |
+
|
| 264 |
+
## Known Limitations
|
| 265 |
+
|
| 266 |
+
1. Very large images (>10MB) may be slow - auto-resized to 512px
|
| 267 |
+
2. Gradient direction visualization requires some color vision
|
| 268 |
+
3. Mathematical content assumes calculus background
|
| 269 |
+
4. Advanced topics (multi-scale, learning-based) not covered
|
| 270 |
+
|
| 271 |
+
## Educational Resources
|
| 272 |
+
|
| 273 |
+
### Foundational Papers
|
| 274 |
+
- Canny, J. (1986). "A Computational Approach to Edge Detection." IEEE TPAMI.
|
| 275 |
+
- Marr, D., & Hildreth, E. (1980). "Theory of Edge Detection." Proc. Royal Society of London B.
|
| 276 |
+
|
| 277 |
+
### Textbooks
|
| 278 |
+
- Gonzalez & Woods. "Digital Image Processing" (4th Edition).
|
| 279 |
+
- Szeliski, R. "Computer Vision: Algorithms and Applications."
|
| 280 |
+
- Forsyth & Ponce. "Computer Vision: A Modern Approach."
|
| 281 |
+
|
| 282 |
+
### Online Resources
|
| 283 |
+
- [OpenCV Edge Detection Tutorial](https://docs.opencv.org/master/da/d22/tutorial_py_canny.html)
|
| 284 |
+
- [Stanford CS231n: Convolutional Neural Networks](http://cs231n.stanford.edu/)
|
| 285 |
+
|
| 286 |
+
## Future Enhancements
|
| 287 |
+
|
| 288 |
+
Potential additions for future versions:
|
| 289 |
+
- **Multi-Scale Detection**: Edge detection at multiple scales
|
| 290 |
+
- **Learning-Based Methods**: Structured forests, HED (CNN-based)
|
| 291 |
+
- **Oriented Filters**: Gabor filters, steerable pyramids
|
| 292 |
+
- **Performance Benchmarks**: Quantitative evaluation with ground truth
|
| 293 |
+
- **3D Visualization**: Surface plots of gradient fields
|
| 294 |
+
- **Video Processing**: Edge detection on video streams
|
| 295 |
+
|
| 296 |
+
## Contributing
|
| 297 |
+
|
| 298 |
+
We welcome contributions! See `CONTRIBUTING.md` for:
|
| 299 |
+
- Bug reports
|
| 300 |
+
- Feature requests
|
| 301 |
+
- Code contributions
|
| 302 |
+
- Documentation improvements
|
| 303 |
+
- Educational content enhancements
|
| 304 |
+
|
| 305 |
+
## License
|
| 306 |
+
|
| 307 |
+
MIT License - Free for educational and commercial use
|
| 308 |
+
|
| 309 |
+
## Acknowledgments
|
| 310 |
+
|
| 311 |
+
- Inspired by classical computer vision courses at leading universities
|
| 312 |
+
- Built with Streamlit for rapid prototyping and deployment
|
| 313 |
+
- OpenCV for efficient image processing
|
| 314 |
+
- HuggingFace for free hosting
|
| 315 |
+
|
| 316 |
+
## Support
|
| 317 |
+
|
| 318 |
+
- **Issues**: GitHub Issues for bugs and features
|
| 319 |
+
- **Questions**: Discussion board for educational questions
|
| 320 |
+
- **Documentation**: See README.md, QUICKSTART.md, CONTRIBUTING.md
|
| 321 |
+
|
| 322 |
+
## Success Stories
|
| 323 |
+
|
| 324 |
+
Perfect for:
|
| 325 |
+
- Graduate image analysis courses
|
| 326 |
+
- Computer vision fundamentals
|
| 327 |
+
- Self-paced online learning
|
| 328 |
+
- Workshop demonstrations
|
| 329 |
+
- Research group tutorials
|
| 330 |
+
|
| 331 |
+
---
|
| 332 |
+
|
| 333 |
+
**Ready to start?** Run `./run_simple.sh` and explore computational edge detection!
|
QUICKSTART.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quick Start Guide
|
| 2 |
+
|
| 3 |
+
## For Students/Users
|
| 4 |
+
|
| 5 |
+
### Online Usage
|
| 6 |
+
Simply visit the Hugging Face Space URL (provided by your instructor) to use the demo directly in your browser. No installation needed!
|
| 7 |
+
|
| 8 |
+
### Local Usage
|
| 9 |
+
|
| 10 |
+
1. **Clone the repository:**
|
| 11 |
+
```bash
|
| 12 |
+
git clone <repository-url>
|
| 13 |
+
cd edge-detection
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
2. **Run the setup script:**
|
| 17 |
+
```bash
|
| 18 |
+
chmod +x setup.sh
|
| 19 |
+
./setup.sh
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
3. **Start the demo:**
|
| 23 |
+
```bash
|
| 24 |
+
chmod +x run_simple.sh
|
| 25 |
+
./run_simple.sh
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
4. **Open your browser** to `http://localhost:8501`
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## For Instructors/Developers
|
| 33 |
+
|
| 34 |
+
### Customizing the Demo
|
| 35 |
+
|
| 36 |
+
The main application is in `app.py`. Key functions you can modify:
|
| 37 |
+
|
| 38 |
+
- `generate_sample_image()`: Customize the default sample image
|
| 39 |
+
- `apply_sobel_filter()`, `apply_prewitt_filter()`, etc.: Modify filter implementations
|
| 40 |
+
- `main_loop()`: Change the UI layout and educational content
|
| 41 |
+
|
| 42 |
+
### Using Your Own Images
|
| 43 |
+
|
| 44 |
+
You have two options:
|
| 45 |
+
|
| 46 |
+
1. **Upload at runtime**: Users can upload images via the sidebar
|
| 47 |
+
2. **Default image**: Modify the `load_sample_image()` function to load from a URL or local path
|
| 48 |
+
|
| 49 |
+
To use images from your own HuggingFace dataset:
|
| 50 |
+
|
| 51 |
+
```python
|
| 52 |
+
image_path = hf_hub_download(
|
| 53 |
+
repo_id="your-username/your-dataset",
|
| 54 |
+
filename="your-image.jpg",
|
| 55 |
+
repo_type="dataset",
|
| 56 |
+
)
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
### Deploying to Hugging Face Spaces
|
| 60 |
+
|
| 61 |
+
1. **Create a Space:**
|
| 62 |
+
- Go to https://huggingface.co/new-space
|
| 63 |
+
- Choose a name (e.g., "edge-detection-demo")
|
| 64 |
+
- Select "Streamlit" as the SDK
|
| 65 |
+
- Choose "Public" for educational use
|
| 66 |
+
|
| 67 |
+
2. **Deploy:**
|
| 68 |
+
```bash
|
| 69 |
+
chmod +x deploy.sh
|
| 70 |
+
./deploy.sh
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
3. **Enter your Space name** when prompted (e.g., "username/edge-detection-demo")
|
| 74 |
+
|
| 75 |
+
### Troubleshooting
|
| 76 |
+
|
| 77 |
+
**"Import errors" when running locally:**
|
| 78 |
+
- Make sure you ran `setup.sh` first
|
| 79 |
+
- Activate the virtual environment: `source venv/bin/activate`
|
| 80 |
+
- Reinstall requirements: `pip install -r requirements.txt`
|
| 81 |
+
|
| 82 |
+
**App not loading on HuggingFace:**
|
| 83 |
+
- Check the "Logs" tab in your Space
|
| 84 |
+
- Verify `packages.txt` includes all system dependencies
|
| 85 |
+
- Ensure Python version in `.python-version` is supported
|
| 86 |
+
|
| 87 |
+
**Images not displaying:**
|
| 88 |
+
- Check if the HuggingFace dataset is public
|
| 89 |
+
- Verify the repo_id and filename in `load_sample_image()`
|
| 90 |
+
- The app will fall back to a generated image if download fails
|
| 91 |
+
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
## Educational Tips
|
| 95 |
+
|
| 96 |
+
### For Teaching
|
| 97 |
+
|
| 98 |
+
1. **Start Simple**: Begin with Roberts Cross to introduce the gradient concept
|
| 99 |
+
2. **Progress Gradually**: Move to Sobel/Prewitt, then Laplacian
|
| 100 |
+
3. **Culminate with Canny**: Show how optimal edge detection builds on simpler methods
|
| 101 |
+
4. **Use Comparisons**: Enable side-by-side comparisons to highlight differences
|
| 102 |
+
|
| 103 |
+
### Discussion Points
|
| 104 |
+
|
| 105 |
+
- **Gradient Operators**: Why do we need 2D convolution kernels?
|
| 106 |
+
- **First vs. Second Order**: When is Laplacian better than Sobel?
|
| 107 |
+
- **Canny's Criteria**: What makes an edge detector "optimal"?
|
| 108 |
+
- **Trade-offs**: Speed vs. quality, simplicity vs. robustness
|
| 109 |
+
|
| 110 |
+
### Suggested Exercises
|
| 111 |
+
|
| 112 |
+
1. Compare edge detection on images with different noise levels
|
| 113 |
+
2. Find optimal Canny thresholds for different types of images
|
| 114 |
+
3. Analyze how kernel size affects edge localization
|
| 115 |
+
4. Examine gradient direction patterns in natural images
|
| 116 |
+
5. Compare computational complexity of different methods
|
| 117 |
+
|
| 118 |
+
## Resources
|
| 119 |
+
|
| 120 |
+
- [OpenCV Edge Detection Tutorial](https://docs.opencv.org/master/da/d22/tutorial_py_canny.html)
|
| 121 |
+
- [Canny's Original Paper (1986)](https://ieeexplore.ieee.org/document/4767851)
|
| 122 |
+
- [Digital Image Processing (Gonzalez & Woods)](http://www.imageprocessingplace.com/)
|
| 123 |
+
- [Computer Vision: Algorithms and Applications (Szeliski)](http://szeliski.org/Book/)
|
README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Interactive Edge Detection Demo
|
| 3 |
+
colorFrom: red
|
| 4 |
+
colorTo: orange
|
| 5 |
+
sdk: streamlit
|
| 6 |
+
sdk_version: 1.39.0
|
| 7 |
+
app_file: app.py
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# π Interactive Edge Detection Demo
|
| 13 |
+
|
| 14 |
+
An educational demonstration of classical and advanced edge detection algorithms for graduate-level image analysis courses.
|
| 15 |
+
|
| 16 |
+
## What You'll Learn
|
| 17 |
+
|
| 18 |
+
This interactive demo explores the computational foundations of edge detection:
|
| 19 |
+
|
| 20 |
+
- **Classical Gradient-Based Methods**: Sobel, Prewitt, Roberts Cross operators
|
| 21 |
+
- **Second-Order Methods**: Laplacian edge detection
|
| 22 |
+
- **Optimal Edge Detection**: The Canny algorithm and its multi-stage pipeline
|
| 23 |
+
- **Comparative Analysis**: Understanding trade-offs between different approaches
|
| 24 |
+
- **Mathematical Foundations**: Gradients, derivatives, and convolution kernels
|
| 25 |
+
- **Practical Considerations**: Pre-processing, parameter tuning, and method selection
|
| 26 |
+
|
| 27 |
+
## Features
|
| 28 |
+
|
| 29 |
+
### Interactive Edge Detection
|
| 30 |
+
- **Five Detection Methods**: Sobel, Prewitt, Roberts, Laplacian, and Canny
|
| 31 |
+
- **Real-Time Visualization**: See edges detected as you adjust parameters
|
| 32 |
+
- **Gradient Analysis**: Visualize gradient components (Gx, Gy) and directions
|
| 33 |
+
- **Side-by-Side Comparison**: Compare any method with Canny edge detection
|
| 34 |
+
|
| 35 |
+
### Educational Content
|
| 36 |
+
- **Four Educational Tabs**: Comprehensive explanations of theory and practice
|
| 37 |
+
- **Mathematical Formulations**: Kernel definitions and gradient equations
|
| 38 |
+
- **"Aha!" Moments**: Key insights that make concepts click
|
| 39 |
+
- **Practical Guidance**: When to use each method and how to tune parameters
|
| 40 |
+
|
| 41 |
+
### Customization
|
| 42 |
+
- **Upload Your Images**: Test algorithms on your own data
|
| 43 |
+
- **Pre-processing Options**: Apply Gaussian blur to reduce noise
|
| 44 |
+
- **Method-Specific Parameters**: Kernel sizes, thresholds, and more
|
| 45 |
+
- **Gradient Visualizations**: Color-coded direction and magnitude maps
|
| 46 |
+
|
| 47 |
+
## Quick Start
|
| 48 |
+
|
| 49 |
+
### Online Usage
|
| 50 |
+
Visit the Hugging Face Space (URL provided by your instructor) to use the demo directly in your browser.
|
| 51 |
+
|
| 52 |
+
### Local Development
|
| 53 |
+
|
| 54 |
+
#### Setup
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
# Clone the repository
|
| 58 |
+
git clone <your-repo-url>
|
| 59 |
+
cd edge-detection
|
| 60 |
+
|
| 61 |
+
# Install dependencies
|
| 62 |
+
pip install -r requirements.txt
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
#### Run Locally
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
# Simple run
|
| 69 |
+
streamlit run app.py
|
| 70 |
+
|
| 71 |
+
# Or use the provided script
|
| 72 |
+
chmod +x run_simple.sh
|
| 73 |
+
./run_simple.sh
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
The app will be available at `http://localhost:8501`
|
| 77 |
+
|
| 78 |
+
## Deployment to Hugging Face Spaces
|
| 79 |
+
|
| 80 |
+
This app is designed to be deployed to Hugging Face Spaces:
|
| 81 |
+
|
| 82 |
+
1. Create a new Space on [Hugging Face](https://huggingface.co/spaces)
|
| 83 |
+
2. Choose "Streamlit" as the SDK
|
| 84 |
+
3. Push this repository to your Space
|
| 85 |
+
|
| 86 |
+
Or use the deployment script:
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
chmod +x deploy.sh
|
| 90 |
+
./deploy.sh
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
## Educational Use
|
| 94 |
+
|
| 95 |
+
This demo is designed for:
|
| 96 |
+
- Graduate image analysis courses
|
| 97 |
+
- Computer vision fundamentals
|
| 98 |
+
- Digital image processing tutorials
|
| 99 |
+
- Self-paced learning about edge detection
|
| 100 |
+
|
| 101 |
+
### Key Concepts Covered
|
| 102 |
+
|
| 103 |
+
- **Image Gradients**: First-order derivatives and directional information
|
| 104 |
+
- **Convolution Kernels**: How discrete filters approximate derivatives
|
| 105 |
+
- **Multi-Scale Analysis**: The role of smoothing in edge detection
|
| 106 |
+
- **Optimal Edge Detection**: Canny's three criteria and their implementation
|
| 107 |
+
- **Practical Trade-offs**: Speed vs. quality, simplicity vs. robustness
|
| 108 |
+
|
| 109 |
+
### Suggested Learning Path
|
| 110 |
+
|
| 111 |
+
1. **Start with Roberts**: Understand the basic concept of gradient operators
|
| 112 |
+
2. **Progress to Sobel/Prewitt**: See how larger kernels improve robustness
|
| 113 |
+
3. **Explore Laplacian**: Learn about second-order derivatives
|
| 114 |
+
4. **Master Canny**: Understand the multi-stage optimal approach
|
| 115 |
+
5. **Compare Methods**: Use the comparison feature to see differences
|
| 116 |
+
|
| 117 |
+
## Project Structure
|
| 118 |
+
|
| 119 |
+
```
|
| 120 |
+
edge-detection/
|
| 121 |
+
βββ app.py # Main Streamlit application
|
| 122 |
+
βββ requirements.txt # Python dependencies
|
| 123 |
+
βββ README.md # This file
|
| 124 |
+
βββ packages.txt # System dependencies for HF Spaces
|
| 125 |
+
βββ .python-version # Python version specification
|
| 126 |
+
βββ Dockerfile # Docker deployment
|
| 127 |
+
βββ setup.sh # Setup script
|
| 128 |
+
βββ run_simple.sh # Local run script
|
| 129 |
+
βββ deploy.sh # Deployment helper script
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
## Technical Details
|
| 133 |
+
|
| 134 |
+
### Implemented Algorithms
|
| 135 |
+
|
| 136 |
+
**Roberts Cross (1963)**
|
| 137 |
+
- 2Γ2 diagonal gradient kernels
|
| 138 |
+
- Fastest, simplest method
|
| 139 |
+
- Good for sharp, diagonal edges
|
| 140 |
+
|
| 141 |
+
**Prewitt (1970)**
|
| 142 |
+
- 3Γ3 gradient kernels with equal weighting
|
| 143 |
+
- Incorporates smoothing perpendicular to gradient
|
| 144 |
+
- Good balance of simplicity and robustness
|
| 145 |
+
|
| 146 |
+
**Sobel (1968)**
|
| 147 |
+
- 3Γ3 gradient kernels with center weighting
|
| 148 |
+
- Most widely used first-order operator
|
| 149 |
+
- Better approximation of true gradient
|
| 150 |
+
|
| 151 |
+
**Laplacian**
|
| 152 |
+
- Second-order isotropic operator
|
| 153 |
+
- Detects zero-crossings
|
| 154 |
+
- Requires careful noise handling
|
| 155 |
+
|
| 156 |
+
**Canny (1986)**
|
| 157 |
+
- Five-stage optimal edge detector
|
| 158 |
+
- Combines smoothing, gradients, NMS, and hysteresis
|
| 159 |
+
- Industry standard for quality edge detection
|
| 160 |
+
|
| 161 |
+
## License
|
| 162 |
+
|
| 163 |
+
MIT License - Free for educational and commercial use
|
| 164 |
+
|
| 165 |
+
## Credits
|
| 166 |
+
|
| 167 |
+
Created for the Intro to Image Analysis course at the University of Bern.
|
| 168 |
+
Inspired by interactive teaching tools for computer vision education.
|
| 169 |
+
|
| 170 |
+
## References
|
| 171 |
+
|
| 172 |
+
- Canny, J. (1986). "A Computational Approach to Edge Detection." IEEE TPAMI.
|
| 173 |
+
- Gonzalez & Woods. "Digital Image Processing" (4th Edition).
|
| 174 |
+
- Szeliski, R. "Computer Vision: Algorithms and Applications."
|
app.py
ADDED
|
@@ -0,0 +1,997 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Interactive Edge Detection Demo
|
| 3 |
+
An educational tool for graduate-level image analysis courses
|
| 4 |
+
|
| 5 |
+
This demo explores computational approaches to edge detection, focusing on:
|
| 6 |
+
- Classical gradient-based filters (Sobel, Prewitt, Roberts, Laplacian)
|
| 7 |
+
- Advanced Canny edge detection
|
| 8 |
+
- Comparative analysis of different methods
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import io
|
| 12 |
+
import numpy as np
|
| 13 |
+
import cv2 as cv
|
| 14 |
+
import streamlit as st
|
| 15 |
+
from PIL import Image
|
| 16 |
+
from huggingface_hub import hf_hub_download
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@st.cache_resource
|
| 20 |
+
def load_sample_image():
|
| 21 |
+
"""Load a sample image for the demo. Falls back to generated image if download fails."""
|
| 22 |
+
try:
|
| 23 |
+
# Try to download from HuggingFace
|
| 24 |
+
image_path = hf_hub_download(
|
| 25 |
+
repo_id="amithjkamath/exampleimages",
|
| 26 |
+
filename="sample-image.jpg",
|
| 27 |
+
repo_type="dataset",
|
| 28 |
+
)
|
| 29 |
+
img = cv.imread(image_path)
|
| 30 |
+
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
|
| 31 |
+
except Exception:
|
| 32 |
+
# Generate a sample image with edges
|
| 33 |
+
img = generate_sample_image()
|
| 34 |
+
|
| 35 |
+
return img
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def generate_sample_image(size=512):
|
| 39 |
+
"""Generate a sample image with interesting edge features for demonstration."""
|
| 40 |
+
img = np.zeros((size, size, 3), dtype=np.uint8)
|
| 41 |
+
|
| 42 |
+
# Create a gradient background (soft edges)
|
| 43 |
+
for i in range(size):
|
| 44 |
+
img[i, :, :] = int(100 * i / size)
|
| 45 |
+
|
| 46 |
+
# Add geometric shapes with different edge characteristics
|
| 47 |
+
# Circle with smooth edges
|
| 48 |
+
cv.circle(img, (size//4, size//4), size//8, (255, 255, 255), -1)
|
| 49 |
+
|
| 50 |
+
# Rectangle with sharp corners
|
| 51 |
+
cv.rectangle(img, (size//2, size//2), (3*size//4, 3*size//4), (200, 50, 50), -1)
|
| 52 |
+
|
| 53 |
+
# Triangle (sharp edges at different orientations)
|
| 54 |
+
pts = np.array([[3*size//4, size//4], [size-50, size//4 + size//8],
|
| 55 |
+
[3*size//4 + size//16, 50]], np.int32)
|
| 56 |
+
cv.fillPoly(img, [pts], (50, 200, 200))
|
| 57 |
+
|
| 58 |
+
# Add some thin lines (challenging for edge detection)
|
| 59 |
+
cv.line(img, (50, 3*size//4), (size//3, 3*size//4), (255, 255, 255), 2)
|
| 60 |
+
|
| 61 |
+
# Add text (multiple edge orientations)
|
| 62 |
+
cv.putText(img, "EDGES", (size//8, size-80),
|
| 63 |
+
cv.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3)
|
| 64 |
+
|
| 65 |
+
return img
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def apply_sobel_filter(img, ksize=3):
|
| 69 |
+
"""
|
| 70 |
+
Apply Sobel edge detection filter.
|
| 71 |
+
|
| 72 |
+
Sobel computes the gradient using separable kernels:
|
| 73 |
+
- Gx: horizontal gradient (vertical edges)
|
| 74 |
+
- Gy: vertical gradient (horizontal edges)
|
| 75 |
+
- Magnitude: sqrt(GxΒ² + GyΒ²)
|
| 76 |
+
|
| 77 |
+
Args:
|
| 78 |
+
img: Input grayscale image
|
| 79 |
+
ksize: Kernel size (1, 3, 5, 7)
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
edges: Edge magnitude image
|
| 83 |
+
gx, gy: Gradient components
|
| 84 |
+
"""
|
| 85 |
+
# Compute gradients in both directions
|
| 86 |
+
gx = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=ksize)
|
| 87 |
+
gy = cv.Sobel(img, cv.CV_64F, 0, 1, ksize=ksize)
|
| 88 |
+
|
| 89 |
+
# Compute magnitude
|
| 90 |
+
magnitude = np.sqrt(gx**2 + gy**2)
|
| 91 |
+
|
| 92 |
+
# Normalize to 0-255 range
|
| 93 |
+
magnitude = np.uint8(255 * magnitude / np.max(magnitude)) if np.max(magnitude) > 0 else magnitude.astype(np.uint8)
|
| 94 |
+
|
| 95 |
+
return magnitude, gx, gy
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def apply_prewitt_filter(img):
|
| 99 |
+
"""
|
| 100 |
+
Apply Prewitt edge detection filter.
|
| 101 |
+
|
| 102 |
+
Prewitt is similar to Sobel but uses simpler kernels:
|
| 103 |
+
Gx = [[-1, 0, 1], Gy = [[-1, -1, -1],
|
| 104 |
+
[-1, 0, 1], [ 0, 0, 0],
|
| 105 |
+
[-1, 0, 1]] [ 1, 1, 1]]
|
| 106 |
+
|
| 107 |
+
Args:
|
| 108 |
+
img: Input grayscale image
|
| 109 |
+
|
| 110 |
+
Returns:
|
| 111 |
+
edges: Edge magnitude image
|
| 112 |
+
gx, gy: Gradient components
|
| 113 |
+
"""
|
| 114 |
+
# Define Prewitt kernels
|
| 115 |
+
kernel_x = np.array([[-1, 0, 1],
|
| 116 |
+
[-1, 0, 1],
|
| 117 |
+
[-1, 0, 1]], dtype=np.float32)
|
| 118 |
+
|
| 119 |
+
kernel_y = np.array([[-1, -1, -1],
|
| 120 |
+
[ 0, 0, 0],
|
| 121 |
+
[ 1, 1, 1]], dtype=np.float32)
|
| 122 |
+
|
| 123 |
+
# Apply convolution
|
| 124 |
+
gx = cv.filter2D(img, cv.CV_64F, kernel_x)
|
| 125 |
+
gy = cv.filter2D(img, cv.CV_64F, kernel_y)
|
| 126 |
+
|
| 127 |
+
# Compute magnitude
|
| 128 |
+
magnitude = np.sqrt(gx**2 + gy**2)
|
| 129 |
+
|
| 130 |
+
# Normalize to 0-255 range
|
| 131 |
+
magnitude = np.uint8(255 * magnitude / np.max(magnitude)) if np.max(magnitude) > 0 else magnitude.astype(np.uint8)
|
| 132 |
+
|
| 133 |
+
return magnitude, gx, gy
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def apply_roberts_filter(img):
|
| 137 |
+
"""
|
| 138 |
+
Apply Roberts Cross edge detection filter.
|
| 139 |
+
|
| 140 |
+
Roberts uses 2x2 diagonal gradient kernels:
|
| 141 |
+
Gx = [[ 1, 0], Gy = [[ 0, 1],
|
| 142 |
+
[ 0, -1]] [-1, 0]]
|
| 143 |
+
|
| 144 |
+
This is the simplest gradient operator, sensitive to diagonal edges.
|
| 145 |
+
|
| 146 |
+
Args:
|
| 147 |
+
img: Input grayscale image
|
| 148 |
+
|
| 149 |
+
Returns:
|
| 150 |
+
edges: Edge magnitude image
|
| 151 |
+
gx, gy: Gradient components
|
| 152 |
+
"""
|
| 153 |
+
# Define Roberts Cross kernels
|
| 154 |
+
kernel_x = np.array([[ 1, 0],
|
| 155 |
+
[ 0, -1]], dtype=np.float32)
|
| 156 |
+
|
| 157 |
+
kernel_y = np.array([[ 0, 1],
|
| 158 |
+
[-1, 0]], dtype=np.float32)
|
| 159 |
+
|
| 160 |
+
# Apply convolution
|
| 161 |
+
gx = cv.filter2D(img, cv.CV_64F, kernel_x)
|
| 162 |
+
gy = cv.filter2D(img, cv.CV_64F, kernel_y)
|
| 163 |
+
|
| 164 |
+
# Compute magnitude
|
| 165 |
+
magnitude = np.sqrt(gx**2 + gy**2)
|
| 166 |
+
|
| 167 |
+
# Normalize to 0-255 range
|
| 168 |
+
magnitude = np.uint8(255 * magnitude / np.max(magnitude)) if np.max(magnitude) > 0 else magnitude.astype(np.uint8)
|
| 169 |
+
|
| 170 |
+
return magnitude, gx, gy
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def apply_laplacian_filter(img, ksize=3):
|
| 174 |
+
"""
|
| 175 |
+
Apply Laplacian edge detection filter.
|
| 176 |
+
|
| 177 |
+
The Laplacian is a second-order derivative operator that detects
|
| 178 |
+
regions of rapid intensity change. Unlike gradient-based methods,
|
| 179 |
+
it's isotropic (rotation-invariant) but more sensitive to noise.
|
| 180 |
+
|
| 181 |
+
Common kernel (ksize=3):
|
| 182 |
+
[[ 0, 1, 0],
|
| 183 |
+
[ 1, -4, 1],
|
| 184 |
+
[ 0, 1, 0]]
|
| 185 |
+
|
| 186 |
+
Args:
|
| 187 |
+
img: Input grayscale image
|
| 188 |
+
ksize: Kernel size (1, 3, 5, 7)
|
| 189 |
+
|
| 190 |
+
Returns:
|
| 191 |
+
edges: Edge response image
|
| 192 |
+
"""
|
| 193 |
+
# Apply Laplacian
|
| 194 |
+
laplacian = cv.Laplacian(img, cv.CV_64F, ksize=ksize)
|
| 195 |
+
|
| 196 |
+
# Take absolute value (edges can be positive or negative)
|
| 197 |
+
laplacian = np.abs(laplacian)
|
| 198 |
+
|
| 199 |
+
# Normalize to 0-255 range
|
| 200 |
+
laplacian = np.uint8(255 * laplacian / np.max(laplacian)) if np.max(laplacian) > 0 else laplacian.astype(np.uint8)
|
| 201 |
+
|
| 202 |
+
return laplacian
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def apply_canny_edge_detector(img, low_threshold=50, high_threshold=150, aperture_size=3, use_l2=True):
|
| 206 |
+
"""
|
| 207 |
+
Apply Canny edge detection algorithm.
|
| 208 |
+
|
| 209 |
+
The Canny edge detector is a multi-stage algorithm:
|
| 210 |
+
1. Noise reduction (Gaussian blur)
|
| 211 |
+
2. Gradient calculation (Sobel)
|
| 212 |
+
3. Non-maximum suppression (thin edges)
|
| 213 |
+
4. Double thresholding (strong and weak edges)
|
| 214 |
+
5. Edge tracking by hysteresis (connect weak edges to strong ones)
|
| 215 |
+
|
| 216 |
+
Args:
|
| 217 |
+
img: Input grayscale image
|
| 218 |
+
low_threshold: Lower threshold for hysteresis
|
| 219 |
+
high_threshold: Upper threshold for hysteresis
|
| 220 |
+
aperture_size: Sobel kernel size (3, 5, 7)
|
| 221 |
+
use_l2: Use L2 norm for gradient magnitude (more accurate but slower)
|
| 222 |
+
|
| 223 |
+
Returns:
|
| 224 |
+
edges: Binary edge map
|
| 225 |
+
"""
|
| 226 |
+
edges = cv.Canny(img, low_threshold, high_threshold,
|
| 227 |
+
apertureSize=aperture_size, L2gradient=use_l2)
|
| 228 |
+
|
| 229 |
+
return edges
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def compute_gradient_direction(gx, gy):
|
| 233 |
+
"""
|
| 234 |
+
Compute gradient direction from gradient components.
|
| 235 |
+
|
| 236 |
+
Args:
|
| 237 |
+
gx, gy: Gradient components in x and y directions
|
| 238 |
+
|
| 239 |
+
Returns:
|
| 240 |
+
direction: Gradient direction in degrees (0-360)
|
| 241 |
+
"""
|
| 242 |
+
direction = np.arctan2(gy, gx) * 180 / np.pi
|
| 243 |
+
direction = (direction + 360) % 360 # Ensure positive angles
|
| 244 |
+
|
| 245 |
+
return direction
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def create_gradient_visualization(gx, gy):
|
| 249 |
+
"""
|
| 250 |
+
Create a color-coded visualization of gradient direction.
|
| 251 |
+
|
| 252 |
+
Uses HSV color space where:
|
| 253 |
+
- Hue represents direction
|
| 254 |
+
- Saturation is constant
|
| 255 |
+
- Value represents magnitude
|
| 256 |
+
|
| 257 |
+
Args:
|
| 258 |
+
gx, gy: Gradient components
|
| 259 |
+
|
| 260 |
+
Returns:
|
| 261 |
+
RGB image showing gradient direction and magnitude
|
| 262 |
+
"""
|
| 263 |
+
# Compute magnitude and direction
|
| 264 |
+
magnitude = np.sqrt(gx**2 + gy**2)
|
| 265 |
+
direction = np.arctan2(gy, gx)
|
| 266 |
+
|
| 267 |
+
# Normalize magnitude
|
| 268 |
+
magnitude_norm = magnitude / np.max(magnitude) if np.max(magnitude) > 0 else magnitude
|
| 269 |
+
|
| 270 |
+
# Create HSV image
|
| 271 |
+
hsv = np.zeros((*gx.shape, 3), dtype=np.uint8)
|
| 272 |
+
hsv[..., 0] = ((direction + np.pi) / (2 * np.pi) * 180).astype(np.uint8) # Hue: 0-180
|
| 273 |
+
hsv[..., 1] = 255 # Saturation: full
|
| 274 |
+
hsv[..., 2] = (magnitude_norm * 255).astype(np.uint8) # Value: magnitude
|
| 275 |
+
|
| 276 |
+
# Convert to RGB
|
| 277 |
+
rgb = cv.cvtColor(hsv, cv.COLOR_HSV2RGB)
|
| 278 |
+
|
| 279 |
+
return rgb
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def main_loop():
|
| 283 |
+
"""Main application loop."""
|
| 284 |
+
st.set_page_config(layout="wide", page_title="Edge Detection Demo")
|
| 285 |
+
|
| 286 |
+
st.title("π Interactive Edge Detection Demo")
|
| 287 |
+
st.markdown("""
|
| 288 |
+
Welcome to an educational exploration of edge detection in digital image processing.
|
| 289 |
+
This demo demonstrates how computational methods identify boundaries and discontinuities in imagesβ
|
| 290 |
+
a fundamental task in computer vision with applications from medical imaging to autonomous systems.
|
| 291 |
+
""")
|
| 292 |
+
|
| 293 |
+
# Load sample image
|
| 294 |
+
sample_img = load_sample_image()
|
| 295 |
+
|
| 296 |
+
# Sidebar: Image input
|
| 297 |
+
st.sidebar.header("Image Input")
|
| 298 |
+
uploaded_file = st.sidebar.file_uploader("Upload your own image (optional)",
|
| 299 |
+
type=['png', 'jpg', 'jpeg'])
|
| 300 |
+
|
| 301 |
+
if uploaded_file is not None:
|
| 302 |
+
# Use uploaded image
|
| 303 |
+
file_bytes = np.asarray(bytearray(uploaded_file.read()), dtype=np.uint8)
|
| 304 |
+
img = cv.imdecode(file_bytes, cv.IMREAD_COLOR)
|
| 305 |
+
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
|
| 306 |
+
|
| 307 |
+
# Resize if too large
|
| 308 |
+
max_size = 512
|
| 309 |
+
h, w = img.shape[:2]
|
| 310 |
+
if max(h, w) > max_size:
|
| 311 |
+
scale = max_size / max(h, w)
|
| 312 |
+
new_w, new_h = int(w * scale), int(h * scale)
|
| 313 |
+
img = cv.resize(img, (new_w, new_h), interpolation=cv.INTER_AREA)
|
| 314 |
+
else:
|
| 315 |
+
img = sample_img
|
| 316 |
+
|
| 317 |
+
# Convert to grayscale for edge detection
|
| 318 |
+
gray = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
|
| 319 |
+
|
| 320 |
+
# Sidebar: Pre-processing options
|
| 321 |
+
st.sidebar.header("Pre-processing")
|
| 322 |
+
|
| 323 |
+
apply_blur = st.sidebar.checkbox("Apply Gaussian Blur", value=False,
|
| 324 |
+
help="Reduces noise before edge detection")
|
| 325 |
+
|
| 326 |
+
if apply_blur:
|
| 327 |
+
blur_ksize = st.sidebar.slider("Blur Kernel Size",
|
| 328 |
+
min_value=3, max_value=15, value=5, step=2,
|
| 329 |
+
help="Larger kernels = more smoothing")
|
| 330 |
+
gray = cv.GaussianBlur(gray, (blur_ksize, blur_ksize), 0)
|
| 331 |
+
|
| 332 |
+
# Sidebar: Select edge detection method
|
| 333 |
+
st.sidebar.header("Edge Detection Method")
|
| 334 |
+
|
| 335 |
+
method = st.sidebar.selectbox(
|
| 336 |
+
"Select Method",
|
| 337 |
+
["Sobel", "Prewitt", "Roberts", "Laplacian", "Canny"],
|
| 338 |
+
help="Choose which edge detection algorithm to apply"
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
# Method-specific parameters
|
| 342 |
+
st.sidebar.subheader("Method Parameters")
|
| 343 |
+
|
| 344 |
+
if method == "Sobel":
|
| 345 |
+
sobel_ksize = st.sidebar.select_slider(
|
| 346 |
+
"Sobel Kernel Size",
|
| 347 |
+
options=[1, 3, 5, 7],
|
| 348 |
+
value=3,
|
| 349 |
+
help="Larger kernels detect coarser edges"
|
| 350 |
+
)
|
| 351 |
+
edges, gx, gy = apply_sobel_filter(gray, ksize=sobel_ksize)
|
| 352 |
+
show_gradients = st.sidebar.checkbox("Show Gradient Components", value=False)
|
| 353 |
+
show_direction = st.sidebar.checkbox("Show Gradient Direction", value=False)
|
| 354 |
+
|
| 355 |
+
elif method == "Prewitt":
|
| 356 |
+
edges, gx, gy = apply_prewitt_filter(gray)
|
| 357 |
+
show_gradients = st.sidebar.checkbox("Show Gradient Components", value=False)
|
| 358 |
+
show_direction = st.sidebar.checkbox("Show Gradient Direction", value=False)
|
| 359 |
+
|
| 360 |
+
elif method == "Roberts":
|
| 361 |
+
edges, gx, gy = apply_roberts_filter(gray)
|
| 362 |
+
show_gradients = st.sidebar.checkbox("Show Gradient Components", value=False)
|
| 363 |
+
show_direction = st.sidebar.checkbox("Show Gradient Direction", value=False)
|
| 364 |
+
|
| 365 |
+
elif method == "Laplacian":
|
| 366 |
+
laplacian_ksize = st.sidebar.select_slider(
|
| 367 |
+
"Laplacian Kernel Size",
|
| 368 |
+
options=[1, 3, 5, 7],
|
| 369 |
+
value=3,
|
| 370 |
+
help="Larger kernels = smoother response"
|
| 371 |
+
)
|
| 372 |
+
edges = apply_laplacian_filter(gray, ksize=laplacian_ksize)
|
| 373 |
+
show_gradients = False
|
| 374 |
+
show_direction = False
|
| 375 |
+
|
| 376 |
+
elif method == "Canny":
|
| 377 |
+
st.sidebar.markdown("**Threshold Values**")
|
| 378 |
+
low_threshold = st.sidebar.slider(
|
| 379 |
+
"Low Threshold",
|
| 380 |
+
min_value=0, max_value=255, value=50,
|
| 381 |
+
help="Pixels below this are definitely not edges"
|
| 382 |
+
)
|
| 383 |
+
high_threshold = st.sidebar.slider(
|
| 384 |
+
"High Threshold",
|
| 385 |
+
min_value=0, max_value=255, value=150,
|
| 386 |
+
help="Pixels above this are definitely edges"
|
| 387 |
+
)
|
| 388 |
+
aperture_size = st.sidebar.select_slider(
|
| 389 |
+
"Sobel Kernel Size",
|
| 390 |
+
options=[3, 5, 7],
|
| 391 |
+
value=3,
|
| 392 |
+
help="Kernel size for internal gradient computation"
|
| 393 |
+
)
|
| 394 |
+
use_l2 = st.sidebar.checkbox("Use L2 Gradient", value=True,
|
| 395 |
+
help="More accurate but slower magnitude calculation")
|
| 396 |
+
|
| 397 |
+
edges = apply_canny_edge_detector(gray, low_threshold, high_threshold,
|
| 398 |
+
aperture_size, use_l2)
|
| 399 |
+
show_gradients = False
|
| 400 |
+
show_direction = False
|
| 401 |
+
|
| 402 |
+
# Display: Original and detected edges
|
| 403 |
+
st.markdown("---")
|
| 404 |
+
st.markdown("## Visual Results")
|
| 405 |
+
|
| 406 |
+
col1, col2 = st.columns(2)
|
| 407 |
+
|
| 408 |
+
with col1:
|
| 409 |
+
st.markdown("### Original Image")
|
| 410 |
+
st.image(img, use_column_width=True, caption="Input image")
|
| 411 |
+
st.caption(f"Size: {img.shape[1]}Γ{img.shape[0]} pixels")
|
| 412 |
+
|
| 413 |
+
with col2:
|
| 414 |
+
st.markdown(f"### {method} Edge Detection")
|
| 415 |
+
if method == "Canny":
|
| 416 |
+
# Canny produces binary edges, display with inverted colormap for visibility
|
| 417 |
+
st.image(edges, use_column_width=True, caption=f"{method} edges", clamp=True)
|
| 418 |
+
else:
|
| 419 |
+
st.image(edges, use_column_width=True, caption=f"{method} edge magnitude", clamp=True)
|
| 420 |
+
|
| 421 |
+
# Show edge statistics
|
| 422 |
+
edge_pixels = np.sum(edges > 0)
|
| 423 |
+
total_pixels = edges.shape[0] * edges.shape[1]
|
| 424 |
+
edge_percentage = (edge_pixels / total_pixels) * 100
|
| 425 |
+
st.caption(f"Edge pixels: {edge_pixels:,} ({edge_percentage:.2f}%)")
|
| 426 |
+
|
| 427 |
+
# Display gradient components if requested
|
| 428 |
+
if show_gradients and method in ["Sobel", "Prewitt", "Roberts"]:
|
| 429 |
+
st.markdown("---")
|
| 430 |
+
st.markdown("### Gradient Components")
|
| 431 |
+
st.markdown("""
|
| 432 |
+
The gradient has two components:
|
| 433 |
+
- **Gx** (horizontal gradient): responds to vertical edges
|
| 434 |
+
- **Gy** (vertical gradient): responds to horizontal edges
|
| 435 |
+
""")
|
| 436 |
+
|
| 437 |
+
col1, col2 = st.columns(2)
|
| 438 |
+
|
| 439 |
+
with col1:
|
| 440 |
+
st.markdown("**Gx: Horizontal Gradient**")
|
| 441 |
+
gx_display = np.abs(gx)
|
| 442 |
+
gx_display = np.uint8(255 * gx_display / np.max(gx_display)) if np.max(gx_display) > 0 else gx_display.astype(np.uint8)
|
| 443 |
+
st.image(gx_display, use_column_width=True, caption="Vertical edges", clamp=True)
|
| 444 |
+
|
| 445 |
+
with col2:
|
| 446 |
+
st.markdown("**Gy: Vertical Gradient**")
|
| 447 |
+
gy_display = np.abs(gy)
|
| 448 |
+
gy_display = np.uint8(255 * gy_display / np.max(gy_display)) if np.max(gy_display) > 0 else gy_display.astype(np.uint8)
|
| 449 |
+
st.image(gy_display, use_column_width=True, caption="Horizontal edges", clamp=True)
|
| 450 |
+
|
| 451 |
+
# Display gradient direction if requested
|
| 452 |
+
if show_direction and method in ["Sobel", "Prewitt", "Roberts"]:
|
| 453 |
+
st.markdown("---")
|
| 454 |
+
st.markdown("### Gradient Direction Visualization")
|
| 455 |
+
st.markdown("""
|
| 456 |
+
This color-coded visualization shows:
|
| 457 |
+
- **Hue (color)**: Direction of the gradient (edge orientation)
|
| 458 |
+
- **Brightness**: Magnitude of the gradient (edge strength)
|
| 459 |
+
""")
|
| 460 |
+
|
| 461 |
+
gradient_viz = create_gradient_visualization(gx, gy)
|
| 462 |
+
st.image(gradient_viz, use_column_width=True, caption="Gradient direction and magnitude")
|
| 463 |
+
|
| 464 |
+
# Add color legend
|
| 465 |
+
st.markdown("""
|
| 466 |
+
**Color Legend:**
|
| 467 |
+
- π΄ Red: Rightward gradient
|
| 468 |
+
- π‘ Yellow: Upward-right gradient
|
| 469 |
+
- π’ Green: Upward gradient
|
| 470 |
+
- π΅ Cyan: Upward-left gradient
|
| 471 |
+
- π£ Blue: Leftward gradient
|
| 472 |
+
- π£ Magenta: Downward-left gradient
|
| 473 |
+
""")
|
| 474 |
+
|
| 475 |
+
# Comparison with Canny
|
| 476 |
+
if method != "Canny":
|
| 477 |
+
st.sidebar.markdown("---")
|
| 478 |
+
st.sidebar.header("Compare with Canny")
|
| 479 |
+
show_comparison = st.sidebar.checkbox("Show Canny Comparison", value=False)
|
| 480 |
+
|
| 481 |
+
if show_comparison:
|
| 482 |
+
st.markdown("---")
|
| 483 |
+
st.markdown(f"## Comparison: {method} vs. Canny")
|
| 484 |
+
|
| 485 |
+
col1, col2 = st.columns(2)
|
| 486 |
+
|
| 487 |
+
# Default Canny parameters for comparison
|
| 488 |
+
canny_low = st.sidebar.slider("Canny Low Threshold (Comparison)",
|
| 489 |
+
min_value=0, max_value=255, value=50)
|
| 490 |
+
canny_high = st.sidebar.slider("Canny High Threshold (Comparison)",
|
| 491 |
+
min_value=0, max_value=255, value=150)
|
| 492 |
+
|
| 493 |
+
canny_edges = apply_canny_edge_detector(gray, canny_low, canny_high)
|
| 494 |
+
|
| 495 |
+
with col1:
|
| 496 |
+
st.markdown(f"### {method}")
|
| 497 |
+
st.image(edges, use_column_width=True, clamp=True)
|
| 498 |
+
edge_pixels_method = np.sum(edges > 0)
|
| 499 |
+
st.caption(f"Edge pixels: {edge_pixels_method:,}")
|
| 500 |
+
|
| 501 |
+
with col2:
|
| 502 |
+
st.markdown("### Canny")
|
| 503 |
+
st.image(canny_edges, use_column_width=True, clamp=True)
|
| 504 |
+
edge_pixels_canny = np.sum(canny_edges > 0)
|
| 505 |
+
st.caption(f"Edge pixels: {edge_pixels_canny:,}")
|
| 506 |
+
|
| 507 |
+
# Educational content
|
| 508 |
+
st.markdown("---")
|
| 509 |
+
st.markdown("## π Educational Insights")
|
| 510 |
+
|
| 511 |
+
tab1, tab2, tab3, tab4 = st.tabs([
|
| 512 |
+
"What Are Edges?",
|
| 513 |
+
"Gradient-Based Methods",
|
| 514 |
+
"Canny Algorithm",
|
| 515 |
+
"Practical Considerations"
|
| 516 |
+
])
|
| 517 |
+
|
| 518 |
+
with tab1:
|
| 519 |
+
st.markdown("""
|
| 520 |
+
### What Are Edges in Digital Images?
|
| 521 |
+
|
| 522 |
+
**Definition:**
|
| 523 |
+
Edges correspond to significant local changes in image intensity. They typically occur at:
|
| 524 |
+
- **Object boundaries**: Where one object ends and another begins
|
| 525 |
+
- **Surface orientation changes**: Corners, creases, and ridges
|
| 526 |
+
- **Material property changes**: Reflectance, color, or texture discontinuities
|
| 527 |
+
- **Illumination boundaries**: Shadows and highlights
|
| 528 |
+
|
| 529 |
+
**Why Edge Detection Matters:**
|
| 530 |
+
Edge detection is foundational to computer vision because edges:
|
| 531 |
+
- Reduce data dimensionality while preserving structural information
|
| 532 |
+
- Are relatively invariant to illumination changes
|
| 533 |
+
- Enable higher-level tasks: object recognition, segmentation, tracking
|
| 534 |
+
- Form the basis for feature extraction in many applications
|
| 535 |
+
|
| 536 |
+
**The Computational Challenge:**
|
| 537 |
+
Distinguishing true edges from noise requires balancing:
|
| 538 |
+
- **Sensitivity**: Detecting all significant edges
|
| 539 |
+
- **Specificity**: Avoiding false positives from noise
|
| 540 |
+
- **Localization**: Accurately positioning detected edges
|
| 541 |
+
|
| 542 |
+
**The "Aha!" Moment:**
|
| 543 |
+
Edges are fundamentally about **derivatives**. Just as derivatives in calculus identify rates of change,
|
| 544 |
+
image gradients identify spatial rates of intensity change. This mathematical insight transforms
|
| 545 |
+
a perceptual concept (edges) into a computational operation (differentiation).
|
| 546 |
+
""")
|
| 547 |
+
|
| 548 |
+
with tab2:
|
| 549 |
+
st.markdown("""
|
| 550 |
+
### Gradient-Based Edge Detection Methods
|
| 551 |
+
|
| 552 |
+
**Fundamental Principle:**
|
| 553 |
+
All gradient-based methods approximate the first derivative of image intensity.
|
| 554 |
+
The gradient is a vector pointing in the direction of greatest intensity increase:
|
| 555 |
+
|
| 556 |
+
$$\\nabla I = \\begin{bmatrix} \\frac{\\partial I}{\\partial x} \\\\ \\frac{\\partial I}{\\partial y} \\end{bmatrix} = \\begin{bmatrix} G_x \\\\ G_y \\end{bmatrix}$$
|
| 557 |
+
|
| 558 |
+
The **edge magnitude** is: $|\\nabla I| = \\sqrt{G_x^2 + G_y^2}$
|
| 559 |
+
|
| 560 |
+
The **edge direction** is: $\\theta = \\arctan\\left(\\frac{G_y}{G_x}\\right)$
|
| 561 |
+
|
| 562 |
+
---
|
| 563 |
+
|
| 564 |
+
#### Roberts Cross Operator (1963)
|
| 565 |
+
|
| 566 |
+
**Kernels (2Γ2):**
|
| 567 |
+
$$G_x = \\begin{bmatrix} +1 & 0 \\\\ 0 & -1 \\end{bmatrix}, \\quad
|
| 568 |
+
G_y = \\begin{bmatrix} 0 & +1 \\\\ -1 & 0 \\end{bmatrix}$$
|
| 569 |
+
|
| 570 |
+
**Characteristics:**
|
| 571 |
+
- Simplest gradient operator, computationally efficient
|
| 572 |
+
- Computes diagonal differences (45Β° rotated gradients)
|
| 573 |
+
- Sensitive to noise due to small kernel
|
| 574 |
+
- Good for images with sharp, diagonal features
|
| 575 |
+
- Historical significance: one of the earliest edge detectors
|
| 576 |
+
|
| 577 |
+
**The "Aha!" Moment:**
|
| 578 |
+
Roberts showed that a simple 2Γ2 difference operation could capture edgesβ
|
| 579 |
+
proving that edge detection doesn't require complex computation.
|
| 580 |
+
|
| 581 |
+
---
|
| 582 |
+
|
| 583 |
+
#### Prewitt Operator (1970)
|
| 584 |
+
|
| 585 |
+
**Kernels (3Γ3):**
|
| 586 |
+
$$G_x = \\begin{bmatrix} -1 & 0 & +1 \\\\ -1 & 0 & +1 \\\\ -1 & 0 & +1 \\end{bmatrix}, \\quad
|
| 587 |
+
G_y = \\begin{bmatrix} -1 & -1 & -1 \\\\ 0 & 0 & 0 \\\\ +1 & +1 & +1 \\end{bmatrix}$$
|
| 588 |
+
|
| 589 |
+
**Characteristics:**
|
| 590 |
+
- Uses 3Γ3 neighborhood for gradient estimation
|
| 591 |
+
- Incorporates implicit averaging (smoothing) perpendicular to gradient direction
|
| 592 |
+
- More robust to noise than Roberts
|
| 593 |
+
- Equal weighting of all pixels in each row/column
|
| 594 |
+
- Separable: can be computed as two 1D convolutions
|
| 595 |
+
|
| 596 |
+
**Key Insight:**
|
| 597 |
+
Prewitt demonstrates the value of **spatial averaging**βsmoothing in one direction
|
| 598 |
+
while differentiating in the other reduces noise sensitivity.
|
| 599 |
+
|
| 600 |
+
---
|
| 601 |
+
|
| 602 |
+
#### Sobel Operator (1968)
|
| 603 |
+
|
| 604 |
+
**Kernels (3Γ3):**
|
| 605 |
+
$$G_x = \\begin{bmatrix} -1 & 0 & +1 \\\\ -2 & 0 & +2 \\\\ -1 & 0 & +1 \\end{bmatrix}, \\quad
|
| 606 |
+
G_y = \\begin{bmatrix} -1 & -2 & -1 \\\\ 0 & 0 & 0 \\\\ +1 & +2 & +1 \\end{bmatrix}$$
|
| 607 |
+
|
| 608 |
+
**Characteristics:**
|
| 609 |
+
- Similar to Prewitt but with **weighted averaging** (2:1 center weight)
|
| 610 |
+
- Better approximation of the true gradient
|
| 611 |
+
- More isotropic response (similar sensitivity to all edge orientations)
|
| 612 |
+
- Most widely used first-order edge detector
|
| 613 |
+
- Can extend to larger kernel sizes (5Γ5, 7Γ7) for coarser features
|
| 614 |
+
|
| 615 |
+
**The "Aha!" Moment:**
|
| 616 |
+
Sobel's weighting scheme approximates a Gaussian smoothing perpendicular to the gradient.
|
| 617 |
+
This is an early recognition that optimal edge detection combines **smoothing and differentiation**β
|
| 618 |
+
a principle later formalized by Canny.
|
| 619 |
+
|
| 620 |
+
---
|
| 621 |
+
|
| 622 |
+
#### Laplacian Operator (Second-Order)
|
| 623 |
+
|
| 624 |
+
**Mathematical Form:**
|
| 625 |
+
$$\\nabla^2 I = \\frac{\\partial^2 I}{\\partial x^2} + \\frac{\\partial^2 I}{\\partial y^2}$$
|
| 626 |
+
|
| 627 |
+
**Common Kernel (3Γ3):**
|
| 628 |
+
$$\\nabla^2 = \\begin{bmatrix} 0 & 1 & 0 \\\\ 1 & -4 & 1 \\\\ 0 & 1 & 0 \\end{bmatrix}$$
|
| 629 |
+
|
| 630 |
+
**Characteristics:**
|
| 631 |
+
- Second-order derivative: detects zero-crossings (rapid intensity changes)
|
| 632 |
+
- **Isotropic**: rotationally invariant, no directional bias
|
| 633 |
+
- Produces double edges (both sides of intensity transitions)
|
| 634 |
+
- Very sensitive to noise (second derivative amplifies high frequencies)
|
| 635 |
+
- Often combined with Gaussian smoothing β **Laplacian of Gaussian (LoG)**
|
| 636 |
+
|
| 637 |
+
**Key Insight:**
|
| 638 |
+
The Laplacian identifies edge locations as zero-crossingsβwhere the second derivative
|
| 639 |
+
changes sign. This gives **precise localization** but at the cost of noise sensitivity.
|
| 640 |
+
|
| 641 |
+
---
|
| 642 |
+
|
| 643 |
+
#### Comparison Summary
|
| 644 |
+
|
| 645 |
+
| Method | Kernel Size | Order | Noise Sensitivity | Directionality | Computational Cost |
|
| 646 |
+
|--------|-------------|-------|-------------------|----------------|-------------------|
|
| 647 |
+
| Roberts | 2Γ2 | 1st | High | Diagonal | Very Low |
|
| 648 |
+
| Prewitt | 3Γ3 | 1st | Medium | Separable (H/V) | Low |
|
| 649 |
+
| Sobel | 3Γ3+ | 1st | Medium-Low | Separable (H/V) | Low |
|
| 650 |
+
| Laplacian | 3Γ3+ | 2nd | Very High | Isotropic | Low |
|
| 651 |
+
|
| 652 |
+
**Practical Recommendation:**
|
| 653 |
+
- **Sobel**: Best general-purpose gradient operator
|
| 654 |
+
- **Roberts**: When speed is critical and images are low-noise
|
| 655 |
+
- **Prewitt**: Similar to Sobel, historical interest
|
| 656 |
+
- **Laplacian**: When precise localization matters, always pre-smooth
|
| 657 |
+
""")
|
| 658 |
+
|
| 659 |
+
if method in ["Sobel", "Prewitt", "Roberts"]:
|
| 660 |
+
st.info(f"""
|
| 661 |
+
**Current Method: {method}**
|
| 662 |
+
|
| 663 |
+
You're currently using the {method} operator. Notice how it responds to different edge orientations
|
| 664 |
+
in your image. Try toggling "Show Gradient Components" to see how Gx and Gy separately capture
|
| 665 |
+
vertical and horizontal edges.
|
| 666 |
+
""")
|
| 667 |
+
|
| 668 |
+
with tab3:
|
| 669 |
+
st.markdown("""
|
| 670 |
+
### The Canny Edge Detector: Optimal Edge Detection
|
| 671 |
+
|
| 672 |
+
**Historical Context:**
|
| 673 |
+
In 1986, John Canny published a landmark paper deriving an "optimal" edge detector
|
| 674 |
+
from first principles. He defined three criteria for good edge detection:
|
| 675 |
+
|
| 676 |
+
1. **Good Detection**: Minimize false positives and false negatives
|
| 677 |
+
2. **Good Localization**: Detected edges should be close to true edges
|
| 678 |
+
3. **Single Response**: One detector response per edge (no double edges)
|
| 679 |
+
|
| 680 |
+
**The Canny Algorithm: A Multi-Stage Pipeline**
|
| 681 |
+
|
| 682 |
+
---
|
| 683 |
+
|
| 684 |
+
#### Stage 1: Noise Reduction (Gaussian Smoothing)
|
| 685 |
+
|
| 686 |
+
$$G(x, y) = \\frac{1}{2\\pi\\sigma^2} e^{-\\frac{x^2 + y^2}{2\\sigma^2}}$$
|
| 687 |
+
|
| 688 |
+
- Convolve image with Gaussian filter to reduce noise
|
| 689 |
+
- $\\sigma$ controls smoothing scale
|
| 690 |
+
- Trade-off: larger $\\sigma$ removes more noise but also blurs edges
|
| 691 |
+
|
| 692 |
+
**Insight:** Canny recognized that edge detection and noise suppression are inherently coupled.
|
| 693 |
+
The Gaussian is optimal for smoothing while preserving edge localization.
|
| 694 |
+
|
| 695 |
+
---
|
| 696 |
+
|
| 697 |
+
#### Stage 2: Gradient Calculation
|
| 698 |
+
|
| 699 |
+
- Apply Sobel operator (or similar) to compute $G_x$ and $G_y$
|
| 700 |
+
- Calculate magnitude: $M = \\sqrt{G_x^2 + G_y^2}$
|
| 701 |
+
- Calculate direction: $\\theta = \\arctan(G_y / G_x)$
|
| 702 |
+
|
| 703 |
+
---
|
| 704 |
+
|
| 705 |
+
#### Stage 3: Non-Maximum Suppression (Edge Thinning)
|
| 706 |
+
|
| 707 |
+
**The Problem:** Gradient operators produce thick edges (multiple pixels respond to a single edge).
|
| 708 |
+
|
| 709 |
+
**The Solution:**
|
| 710 |
+
- For each pixel, examine magnitude along gradient direction
|
| 711 |
+
- Suppress pixel if either neighbor along gradient direction has higher magnitude
|
| 712 |
+
- Result: edges thinned to single-pixel width
|
| 713 |
+
|
| 714 |
+
**The "Aha!" Moment:**
|
| 715 |
+
This stage enforces the "single response" criterion. By suppressing non-maxima perpendicular
|
| 716 |
+
to the edge direction, Canny achieves sub-pixel localization accuracy.
|
| 717 |
+
|
| 718 |
+
---
|
| 719 |
+
|
| 720 |
+
#### Stage 4: Double Thresholding
|
| 721 |
+
|
| 722 |
+
Use two thresholds to classify edge pixels:
|
| 723 |
+
- **Strong edges**: $M > T_{high}$ β definitely edges
|
| 724 |
+
- **Weak edges**: $T_{low} < M < T_{high}$ β potential edges
|
| 725 |
+
- **Non-edges**: $M < T_{low}$ β definitely not edges
|
| 726 |
+
|
| 727 |
+
**Rationale:**
|
| 728 |
+
- Single threshold is too binary: either misses edges or includes noise
|
| 729 |
+
- Double threshold creates a "confidence gradient"
|
| 730 |
+
|
| 731 |
+
---
|
| 732 |
+
|
| 733 |
+
#### Stage 5: Edge Tracking by Hysteresis
|
| 734 |
+
|
| 735 |
+
**The Final Step:**
|
| 736 |
+
- Keep all strong edges
|
| 737 |
+
- Keep weak edges only if connected to strong edges
|
| 738 |
+
- Discard isolated weak edges
|
| 739 |
+
|
| 740 |
+
**The "Aha!" Moment:**
|
| 741 |
+
Hysteresis exploits **edge continuity**. Real edges are extended structures; noise is isolated.
|
| 742 |
+
By requiring weak edges to connect to strong edges, Canny achieves robustness without
|
| 743 |
+
sacrificing sensitivity.
|
| 744 |
+
|
| 745 |
+
---
|
| 746 |
+
|
| 747 |
+
### Why Canny Is "Optimal"
|
| 748 |
+
|
| 749 |
+
Canny proved that, under certain assumptions (Gaussian noise, linear filters), his algorithm
|
| 750 |
+
achieves optimal trade-offs among his three criteria. Specifically:
|
| 751 |
+
|
| 752 |
+
- The **optimal filter** for edge detection is approximately the derivative of a Gaussian
|
| 753 |
+
- The multi-scale nature (controlled by $\\sigma$) allows detection of edges at different scales
|
| 754 |
+
- Non-maximum suppression and hysteresis address fundamental ambiguities in edge detection
|
| 755 |
+
|
| 756 |
+
---
|
| 757 |
+
|
| 758 |
+
### Limitations and Practical Considerations
|
| 759 |
+
|
| 760 |
+
**When Canny Struggles:**
|
| 761 |
+
- **Texture**: Dense texture can trigger many weak edges
|
| 762 |
+
- **Low contrast**: Subtle edges may fall below threshold
|
| 763 |
+
- **Computational cost**: More expensive than simple gradient operators
|
| 764 |
+
|
| 765 |
+
**Parameter Tuning:**
|
| 766 |
+
- **$T_{low}$ and $T_{high}$**: Typical ratio is 1:2 or 1:3
|
| 767 |
+
- Too high: miss edges
|
| 768 |
+
- Too low: include noise
|
| 769 |
+
- **$\\sigma$ (implicit in aperture size)**: Larger for coarse edges, smaller for fine details
|
| 770 |
+
|
| 771 |
+
**Modern Context:**
|
| 772 |
+
Despite being nearly 40 years old, Canny remains widely used. Its principles influenced:
|
| 773 |
+
- Multi-scale edge detection (e.g., Canny-Deriche)
|
| 774 |
+
- Learning-based edge detection (e.g., structured forests, CNNs)
|
| 775 |
+
- Active contours and level sets
|
| 776 |
+
|
| 777 |
+
---
|
| 778 |
+
|
| 779 |
+
### Interactive Exploration
|
| 780 |
+
|
| 781 |
+
**Try This:**
|
| 782 |
+
1. Set low_threshold = 50, high_threshold = 150 (default)
|
| 783 |
+
2. Gradually increase both thresholds β edges disappear
|
| 784 |
+
3. Gradually decrease both β more edges, including noise
|
| 785 |
+
4. Compare Canny to Sobel with the comparison feature
|
| 786 |
+
|
| 787 |
+
**Question to Ponder:**
|
| 788 |
+
Can you find threshold values where Canny detects edges that Sobel misses,
|
| 789 |
+
and vice versa? What does this reveal about their different approaches?
|
| 790 |
+
""")
|
| 791 |
+
|
| 792 |
+
if method == "Canny":
|
| 793 |
+
st.info(f"""
|
| 794 |
+
**Current Parameters:**
|
| 795 |
+
- Low Threshold: {low_threshold}
|
| 796 |
+
- High Threshold: {high_threshold}
|
| 797 |
+
- Ratio: 1:{high_threshold/low_threshold:.2f}
|
| 798 |
+
|
| 799 |
+
**Interpretation:**
|
| 800 |
+
- Pixels with magnitude > {high_threshold} are **strong edges** (white)
|
| 801 |
+
- Pixels with magnitude between {low_threshold} and {high_threshold} are kept only if connected to strong edges
|
| 802 |
+
- All other pixels are **non-edges** (black)
|
| 803 |
+
""")
|
| 804 |
+
|
| 805 |
+
with tab4:
|
| 806 |
+
st.markdown("""
|
| 807 |
+
### Practical Considerations in Edge Detection
|
| 808 |
+
|
| 809 |
+
---
|
| 810 |
+
|
| 811 |
+
#### 1. Pre-processing: The Critical First Step
|
| 812 |
+
|
| 813 |
+
**Gaussian Smoothing:**
|
| 814 |
+
- Almost always beneficial, especially for Laplacian
|
| 815 |
+
- Suppresses noise while preserving edge structure
|
| 816 |
+
- $\\sigma$ controls scale: larger $\\sigma$ for coarser edges
|
| 817 |
+
|
| 818 |
+
**Histogram Equalization:**
|
| 819 |
+
- Enhances contrast in low-contrast images
|
| 820 |
+
- Can make subtle edges detectable
|
| 821 |
+
- May amplify noise in uniform regions
|
| 822 |
+
|
| 823 |
+
**Morphological Operations:**
|
| 824 |
+
- Closing: Connect nearby edge segments
|
| 825 |
+
- Opening: Remove isolated noise pixels
|
| 826 |
+
- Applied after edge detection, not before
|
| 827 |
+
|
| 828 |
+
---
|
| 829 |
+
|
| 830 |
+
#### 2. Choosing the Right Method
|
| 831 |
+
|
| 832 |
+
**Sobel** when:
|
| 833 |
+
- You need directional gradient information
|
| 834 |
+
- Computational efficiency matters
|
| 835 |
+
- Image is reasonably clean
|
| 836 |
+
- You're building a pipeline for further processing (e.g., Hough transform)
|
| 837 |
+
|
| 838 |
+
**Canny** when:
|
| 839 |
+
- You need thin, connected edges
|
| 840 |
+
- You can afford the computational cost
|
| 841 |
+
- You need robustness to noise
|
| 842 |
+
- Output will be used for segmentation or shape analysis
|
| 843 |
+
|
| 844 |
+
**Laplacian** when:
|
| 845 |
+
- You need isotropic response
|
| 846 |
+
- You're detecting blob-like features (LoG)
|
| 847 |
+
- Combined with Gaussian (LoG): excellent for multi-scale analysis
|
| 848 |
+
|
| 849 |
+
**Roberts** when:
|
| 850 |
+
- Extreme computational constraints
|
| 851 |
+
- Diagonal edges are prominent
|
| 852 |
+
- Historical comparison or educational purposes
|
| 853 |
+
|
| 854 |
+
---
|
| 855 |
+
|
| 856 |
+
#### 3. Common Pitfalls and Solutions
|
| 857 |
+
|
| 858 |
+
**Problem: Too many edges detected**
|
| 859 |
+
- **Cause:** Low threshold, high noise, texture
|
| 860 |
+
- **Solution:**
|
| 861 |
+
- Increase threshold (Canny)
|
| 862 |
+
- Pre-smooth more aggressively
|
| 863 |
+
- Use morphological opening to remove small responses
|
| 864 |
+
|
| 865 |
+
**Problem: Missing important edges**
|
| 866 |
+
- **Cause:** High threshold, low contrast, blurred image
|
| 867 |
+
- **Solution:**
|
| 868 |
+
- Decrease threshold
|
| 869 |
+
- Apply histogram equalization
|
| 870 |
+
- Try multi-scale detection (multiple $\\sigma$ values)
|
| 871 |
+
|
| 872 |
+
**Problem: Thick, messy edges**
|
| 873 |
+
- **Cause:** Not using non-maximum suppression
|
| 874 |
+
- **Solution:**
|
| 875 |
+
- Use Canny instead of raw gradient magnitude
|
| 876 |
+
- Implement custom NMS if using Sobel/Prewitt
|
| 877 |
+
|
| 878 |
+
**Problem: Disconnected edge segments**
|
| 879 |
+
- **Cause:** Threshold too high, gaps in actual edges
|
| 880 |
+
- **Solution:**
|
| 881 |
+
- Lower threshold (especially Canny's low threshold)
|
| 882 |
+
- Apply morphological closing
|
| 883 |
+
- Use probabilistic Hough transform to connect segments
|
| 884 |
+
|
| 885 |
+
---
|
| 886 |
+
|
| 887 |
+
#### 4. Domain-Specific Considerations
|
| 888 |
+
|
| 889 |
+
**Medical Imaging:**
|
| 890 |
+
- Often low contrast, high noise
|
| 891 |
+
- Canny with careful threshold tuning
|
| 892 |
+
- Multi-scale approaches (LoG pyramids)
|
| 893 |
+
- Consider anisotropic diffusion for pre-processing
|
| 894 |
+
|
| 895 |
+
**Natural Images (Photography):**
|
| 896 |
+
- Complex scenes, varied lighting
|
| 897 |
+
- Canny generally works well
|
| 898 |
+
- May need semantic segmentation to identify "important" edges
|
| 899 |
+
|
| 900 |
+
**Document Analysis:**
|
| 901 |
+
- High contrast, sharp edges
|
| 902 |
+
- Simple methods (Sobel, Roberts) often sufficient
|
| 903 |
+
- Adaptive thresholding for varying illumination
|
| 904 |
+
|
| 905 |
+
**Industrial Inspection:**
|
| 906 |
+
- Controlled environment, consistent lighting
|
| 907 |
+
- Fast methods preferred (Sobel)
|
| 908 |
+
- Template matching often combined with edges
|
| 909 |
+
|
| 910 |
+
**Autonomous Driving:**
|
| 911 |
+
- Real-time requirements
|
| 912 |
+
- Lane detection: focused edge detection in ROI
|
| 913 |
+
- Modern systems use CNN-based edge detection
|
| 914 |
+
|
| 915 |
+
---
|
| 916 |
+
|
| 917 |
+
#### 5. Computational Performance
|
| 918 |
+
|
| 919 |
+
**Runtime Complexity (for NΓN image):**
|
| 920 |
+
- Roberts, Prewitt, Sobel: O(NΒ²) β single-pass convolution
|
| 921 |
+
- Laplacian: O(NΒ²) β single-pass convolution
|
| 922 |
+
- Canny: O(NΒ²) but with higher constant factor due to multi-stage pipeline
|
| 923 |
+
|
| 924 |
+
**Memory Considerations:**
|
| 925 |
+
- Gradient methods: 2-3Γ image memory (for Gx, Gy, magnitude)
|
| 926 |
+
- Canny: 3-4Γ image memory (gradients + intermediate stages)
|
| 927 |
+
|
| 928 |
+
**Optimization Strategies:**
|
| 929 |
+
- Use separable filters when possible (Sobel, Prewitt)
|
| 930 |
+
- Implement on GPU for real-time applications
|
| 931 |
+
- Consider approximate methods for very large images
|
| 932 |
+
- Use integral images for multi-scale LoG
|
| 933 |
+
|
| 934 |
+
---
|
| 935 |
+
|
| 936 |
+
#### 6. Beyond Classical Methods
|
| 937 |
+
|
| 938 |
+
**Learning-Based Edge Detection:**
|
| 939 |
+
- Structured Edge Detection (SED): Random forests on local patches
|
| 940 |
+
- Holistically-Nested Edge Detection (HED): Deep CNN
|
| 941 |
+
- Advantages: Context-aware, learns from data
|
| 942 |
+
- Disadvantages: Requires training data, computationally intensive
|
| 943 |
+
|
| 944 |
+
**Multi-Scale Approaches:**
|
| 945 |
+
- Canny at multiple scales, combine results
|
| 946 |
+
- Laplacian of Gaussian pyramid
|
| 947 |
+
- Scale-space theory (Lindeberg)
|
| 948 |
+
|
| 949 |
+
**Oriented Edge Detection:**
|
| 950 |
+
- Steerable filters: efficient computation of gradients at multiple orientations
|
| 951 |
+
- Gabor filters: detect edges at specific scales and orientations
|
| 952 |
+
- Useful for texture analysis and orientation-dependent tasks
|
| 953 |
+
|
| 954 |
+
---
|
| 955 |
+
|
| 956 |
+
#### 7. Evaluation Metrics
|
| 957 |
+
|
| 958 |
+
**Quantitative Evaluation (when ground truth available):**
|
| 959 |
+
- **Precision**: fraction of detected edges that are true edges
|
| 960 |
+
- **Recall**: fraction of true edges that are detected
|
| 961 |
+
- **F-measure**: harmonic mean of precision and recall
|
| 962 |
+
- **Localization error**: distance between detected and true edge pixels
|
| 963 |
+
|
| 964 |
+
**Qualitative Evaluation:**
|
| 965 |
+
- Visual inspection: Are important edges detected?
|
| 966 |
+
- Downstream task performance: Does edge quality improve final result?
|
| 967 |
+
|
| 968 |
+
---
|
| 969 |
+
|
| 970 |
+
### Take-Home Messages
|
| 971 |
+
|
| 972 |
+
1. **No Universal Best Method**: Choice depends on application requirements
|
| 973 |
+
2. **Pre-processing Matters**: Clean input β clean edges
|
| 974 |
+
3. **Thresholds Are Critical**: Tune based on your specific images
|
| 975 |
+
4. **Computational Complexity**: Simple doesn't mean inferior
|
| 976 |
+
5. **Edge Detection Is a Means, Not an End**: Always consider the downstream task
|
| 977 |
+
|
| 978 |
+
**The Ultimate "Aha!" Moment:**
|
| 979 |
+
Edge detection transforms high-dimensional pixel data into sparse, interpretable structures.
|
| 980 |
+
This dimensionality reductionβfrom millions of pixels to thousands of edge pointsβis what makes
|
| 981 |
+
computer vision computationally tractable. Understanding edges is understanding how machines
|
| 982 |
+
"see" structure in the visual world.
|
| 983 |
+
""")
|
| 984 |
+
|
| 985 |
+
# Footer
|
| 986 |
+
st.markdown("---")
|
| 987 |
+
st.markdown("""
|
| 988 |
+
<small>
|
| 989 |
+
Educational Demo for Image Analysis Courses |
|
| 990 |
+
Built with Streamlit and OpenCV |
|
| 991 |
+
<a href="https://github.com/ubern-image-analysis/edge-detection" target="_blank">View Source</a>
|
| 992 |
+
</small>
|
| 993 |
+
""", unsafe_allow_html=True)
|
| 994 |
+
|
| 995 |
+
|
| 996 |
+
if __name__ == "__main__":
|
| 997 |
+
main_loop()
|
deploy.sh
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Deployment script for Hugging Face Spaces
|
| 4 |
+
|
| 5 |
+
set -e
|
| 6 |
+
|
| 7 |
+
echo "π Deploying Edge Detection Demo to Hugging Face Spaces"
|
| 8 |
+
echo "========================================================"
|
| 9 |
+
echo ""
|
| 10 |
+
|
| 11 |
+
# Check if git is initialized
|
| 12 |
+
if [ ! -d ".git" ]; then
|
| 13 |
+
echo "π¦ Initializing git repository..."
|
| 14 |
+
git init
|
| 15 |
+
git add .
|
| 16 |
+
git commit -m "Initial commit: Edge Detection Demo"
|
| 17 |
+
fi
|
| 18 |
+
|
| 19 |
+
# Get Space information
|
| 20 |
+
echo "Please provide your Hugging Face Space details:"
|
| 21 |
+
echo ""
|
| 22 |
+
read -p "π Space name (e.g., username/edge-detection-demo): " SPACE_NAME
|
| 23 |
+
|
| 24 |
+
if [ -z "$SPACE_NAME" ]; then
|
| 25 |
+
echo "β Space name cannot be empty"
|
| 26 |
+
exit 1
|
| 27 |
+
fi
|
| 28 |
+
|
| 29 |
+
SPACE_URL="https://huggingface.co/spaces/$SPACE_NAME"
|
| 30 |
+
|
| 31 |
+
echo ""
|
| 32 |
+
echo "π Space URL: $SPACE_URL"
|
| 33 |
+
echo ""
|
| 34 |
+
|
| 35 |
+
# Check if remote already exists
|
| 36 |
+
if git remote | grep -q "hf"; then
|
| 37 |
+
echo "π Remote 'hf' already exists, updating URL..."
|
| 38 |
+
git remote set-url hf "$SPACE_URL"
|
| 39 |
+
else
|
| 40 |
+
echo "π Adding Hugging Face remote..."
|
| 41 |
+
git remote add hf "$SPACE_URL"
|
| 42 |
+
fi
|
| 43 |
+
|
| 44 |
+
echo ""
|
| 45 |
+
echo "π¦ Committing changes..."
|
| 46 |
+
|
| 47 |
+
# Add all files
|
| 48 |
+
git add .
|
| 49 |
+
|
| 50 |
+
# Check if there are changes to commit
|
| 51 |
+
if git diff --staged --quiet; then
|
| 52 |
+
echo "βΉοΈ No changes to commit."
|
| 53 |
+
else
|
| 54 |
+
read -p "π¬ Enter commit message (default: 'Update demo'): " COMMIT_MSG
|
| 55 |
+
COMMIT_MSG=${COMMIT_MSG:-"Update demo"}
|
| 56 |
+
git commit -m "$COMMIT_MSG"
|
| 57 |
+
fi
|
| 58 |
+
|
| 59 |
+
echo ""
|
| 60 |
+
echo "π Pushing to Hugging Face Spaces..."
|
| 61 |
+
git push hf main
|
| 62 |
+
|
| 63 |
+
echo ""
|
| 64 |
+
echo "β
Deployment complete!"
|
| 65 |
+
echo "π Your Space will be available at: $SPACE_URL"
|
| 66 |
+
echo ""
|
| 67 |
+
echo "β±οΈ Note: It may take a few minutes for the Space to build and start."
|
packages.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
libgl1
|
| 2 |
+
libglib2.0-0
|
pyproject.toml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=45", "wheel"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "edge-detection-demo"
|
| 7 |
+
version = "1.0.0"
|
| 8 |
+
description = "Interactive educational demo for edge detection algorithms"
|
| 9 |
+
readme = "README.md"
|
| 10 |
+
requires-python = ">=3.11"
|
| 11 |
+
license = {text = "MIT"}
|
| 12 |
+
authors = [
|
| 13 |
+
{name = "Uni Bern Intro to Image Analysis Course"}
|
| 14 |
+
]
|
| 15 |
+
keywords = ["edge-detection", "computer-vision", "image-processing", "education", "streamlit"]
|
| 16 |
+
classifiers = [
|
| 17 |
+
"Development Status :: 4 - Beta",
|
| 18 |
+
"Intended Audience :: Education",
|
| 19 |
+
"License :: OSI Approved :: MIT License",
|
| 20 |
+
"Programming Language :: Python :: 3",
|
| 21 |
+
"Programming Language :: Python :: 3.11",
|
| 22 |
+
"Topic :: Scientific/Engineering :: Image Processing",
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
+
dependencies = [
|
| 26 |
+
"numpy>=1.24.0",
|
| 27 |
+
"opencv-python-headless>=4.8.0",
|
| 28 |
+
"streamlit>=1.39.0",
|
| 29 |
+
"Pillow>=10.0.0",
|
| 30 |
+
"huggingface-hub>=0.19.0",
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
[project.urls]
|
| 34 |
+
Homepage = "https://github.com/ubern-image-analysis/edge-detection"
|
| 35 |
+
Repository = "https://github.com/ubern-image-analysis/edge-detection"
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy>=1.24.0
|
| 2 |
+
opencv-python-headless>=4.8.0
|
| 3 |
+
streamlit>=1.39.0
|
| 4 |
+
Pillow>=10.0.0
|
| 5 |
+
huggingface-hub>=0.19.0
|
run_simple.sh
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Simple script to run the Streamlit app locally
|
| 4 |
+
|
| 5 |
+
echo "π Starting Edge Detection Demo..."
|
| 6 |
+
echo ""
|
| 7 |
+
|
| 8 |
+
# Check if virtual environment exists
|
| 9 |
+
if [ ! -d "venv" ]; then
|
| 10 |
+
echo "π¦ No virtual environment found. Creating one..."
|
| 11 |
+
python3 -m venv venv
|
| 12 |
+
fi
|
| 13 |
+
|
| 14 |
+
# Activate virtual environment
|
| 15 |
+
echo "π§ Activating virtual environment..."
|
| 16 |
+
source venv/bin/activate
|
| 17 |
+
|
| 18 |
+
# Install requirements if needed
|
| 19 |
+
if [ ! -f "venv/.requirements_installed" ]; then
|
| 20 |
+
echo "π₯ Installing dependencies..."
|
| 21 |
+
pip install -r requirements.txt
|
| 22 |
+
touch venv/.requirements_installed
|
| 23 |
+
else
|
| 24 |
+
echo "β
Dependencies already installed"
|
| 25 |
+
fi
|
| 26 |
+
|
| 27 |
+
# Run the app
|
| 28 |
+
echo ""
|
| 29 |
+
echo "π Launching Streamlit app..."
|
| 30 |
+
echo " Open your browser to http://localhost:8501"
|
| 31 |
+
echo ""
|
| 32 |
+
streamlit run app.py
|
setup.sh
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Setup script for local development
|
| 4 |
+
|
| 5 |
+
set -e
|
| 6 |
+
|
| 7 |
+
echo "π§ Setting up Edge Detection Demo"
|
| 8 |
+
echo "=================================="
|
| 9 |
+
echo ""
|
| 10 |
+
|
| 11 |
+
# Check Python version
|
| 12 |
+
echo "π Checking Python version..."
|
| 13 |
+
PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}')
|
| 14 |
+
echo " Found Python $PYTHON_VERSION"
|
| 15 |
+
|
| 16 |
+
REQUIRED_VERSION="3.11"
|
| 17 |
+
if [ "$(printf '%s\n' "$REQUIRED_VERSION" "$PYTHON_VERSION" | sort -V | head -n1)" != "$REQUIRED_VERSION" ]; then
|
| 18 |
+
echo "β οΈ Warning: Python $REQUIRED_VERSION or higher is recommended."
|
| 19 |
+
fi
|
| 20 |
+
|
| 21 |
+
echo ""
|
| 22 |
+
|
| 23 |
+
# Create virtual environment
|
| 24 |
+
if [ -d "venv" ]; then
|
| 25 |
+
echo "π¦ Virtual environment already exists."
|
| 26 |
+
read -p " Do you want to recreate it? (y/N): " RECREATE
|
| 27 |
+
if [ "$RECREATE" = "y" ] || [ "$RECREATE" = "Y" ]; then
|
| 28 |
+
echo " Removing old virtual environment..."
|
| 29 |
+
rm -rf venv
|
| 30 |
+
echo " Creating new virtual environment..."
|
| 31 |
+
python3 -m venv venv
|
| 32 |
+
fi
|
| 33 |
+
else
|
| 34 |
+
echo "π¦ Creating virtual environment..."
|
| 35 |
+
python3 -m venv venv
|
| 36 |
+
fi
|
| 37 |
+
|
| 38 |
+
# Activate virtual environment
|
| 39 |
+
echo "π§ Activating virtual environment..."
|
| 40 |
+
source venv/bin/activate
|
| 41 |
+
|
| 42 |
+
# Upgrade pip
|
| 43 |
+
echo "β¬οΈ Upgrading pip..."
|
| 44 |
+
pip install --upgrade pip > /dev/null 2>&1
|
| 45 |
+
|
| 46 |
+
# Install requirements
|
| 47 |
+
echo "π₯ Installing requirements..."
|
| 48 |
+
pip install -r requirements.txt
|
| 49 |
+
|
| 50 |
+
echo ""
|
| 51 |
+
echo "β
Setup complete!"
|
| 52 |
+
echo ""
|
| 53 |
+
echo "π Next steps:"
|
| 54 |
+
echo " 1. Run './run_simple.sh' to start the demo locally"
|
| 55 |
+
echo " 2. Or activate the environment: 'source venv/bin/activate'"
|
| 56 |
+
echo " 3. Then run: 'streamlit run app.py'"
|
| 57 |
+
echo ""
|
| 58 |
+
echo "π To deploy to Hugging Face Spaces:"
|
| 59 |
+
echo " 1. Create a Space at https://huggingface.co/new-space"
|
| 60 |
+
echo " 2. Run './deploy.sh' and follow the prompts"
|
test_setup.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Quick test script to verify the edge detection demo setup
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
def test_imports():
|
| 9 |
+
"""Test that all required packages can be imported."""
|
| 10 |
+
print("π§ͺ Testing package imports...")
|
| 11 |
+
|
| 12 |
+
packages = [
|
| 13 |
+
('numpy', 'NumPy'),
|
| 14 |
+
('cv2', 'OpenCV'),
|
| 15 |
+
('streamlit', 'Streamlit'),
|
| 16 |
+
('PIL', 'Pillow'),
|
| 17 |
+
('huggingface_hub', 'HuggingFace Hub'),
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
failed = []
|
| 21 |
+
for module, name in packages:
|
| 22 |
+
try:
|
| 23 |
+
__import__(module)
|
| 24 |
+
print(f" β
{name}")
|
| 25 |
+
except ImportError as e:
|
| 26 |
+
print(f" β {name}: {e}")
|
| 27 |
+
failed.append(name)
|
| 28 |
+
|
| 29 |
+
return len(failed) == 0, failed
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def test_app_structure():
|
| 33 |
+
"""Test that app.py has the expected structure."""
|
| 34 |
+
print("\nπ Testing app structure...")
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
import app
|
| 38 |
+
|
| 39 |
+
# Check for key functions
|
| 40 |
+
functions = [
|
| 41 |
+
'load_sample_image',
|
| 42 |
+
'generate_sample_image',
|
| 43 |
+
'apply_sobel_filter',
|
| 44 |
+
'apply_prewitt_filter',
|
| 45 |
+
'apply_roberts_filter',
|
| 46 |
+
'apply_laplacian_filter',
|
| 47 |
+
'apply_canny_edge_detector',
|
| 48 |
+
'compute_gradient_direction',
|
| 49 |
+
'create_gradient_visualization',
|
| 50 |
+
'main_loop',
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
failed = []
|
| 54 |
+
for func in functions:
|
| 55 |
+
if hasattr(app, func):
|
| 56 |
+
print(f" β
Function '{func}' found")
|
| 57 |
+
else:
|
| 58 |
+
print(f" β Function '{func}' missing")
|
| 59 |
+
failed.append(func)
|
| 60 |
+
|
| 61 |
+
return len(failed) == 0, failed
|
| 62 |
+
|
| 63 |
+
except Exception as e:
|
| 64 |
+
print(f" β Error loading app: {e}")
|
| 65 |
+
return False, [str(e)]
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def test_edge_detection():
|
| 69 |
+
"""Test basic edge detection functions."""
|
| 70 |
+
print("\nβοΈ Testing edge detection...")
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
import app
|
| 74 |
+
import numpy as np
|
| 75 |
+
|
| 76 |
+
# Create a simple test image
|
| 77 |
+
test_img = np.random.randint(0, 256, (128, 128), dtype=np.uint8)
|
| 78 |
+
|
| 79 |
+
# Test Sobel
|
| 80 |
+
edges, gx, gy = app.apply_sobel_filter(test_img, ksize=3)
|
| 81 |
+
assert edges.shape == test_img.shape, "Sobel output shape mismatch"
|
| 82 |
+
print(f" β
Sobel filter works")
|
| 83 |
+
|
| 84 |
+
# Test Prewitt
|
| 85 |
+
edges, gx, gy = app.apply_prewitt_filter(test_img)
|
| 86 |
+
assert edges.shape == test_img.shape, "Prewitt output shape mismatch"
|
| 87 |
+
print(f" β
Prewitt filter works")
|
| 88 |
+
|
| 89 |
+
# Test Roberts
|
| 90 |
+
edges, gx, gy = app.apply_roberts_filter(test_img)
|
| 91 |
+
print(f" β
Roberts filter works")
|
| 92 |
+
|
| 93 |
+
# Test Laplacian
|
| 94 |
+
edges = app.apply_laplacian_filter(test_img, ksize=3)
|
| 95 |
+
assert edges.shape == test_img.shape, "Laplacian output shape mismatch"
|
| 96 |
+
print(f" β
Laplacian filter works")
|
| 97 |
+
|
| 98 |
+
# Test Canny
|
| 99 |
+
edges = app.apply_canny_edge_detector(test_img, 50, 150)
|
| 100 |
+
assert edges.shape == test_img.shape, "Canny output shape mismatch"
|
| 101 |
+
print(f" β
Canny edge detector works")
|
| 102 |
+
|
| 103 |
+
# Test gradient visualization
|
| 104 |
+
viz = app.create_gradient_visualization(gx, gy)
|
| 105 |
+
print(f" β
Gradient visualization works")
|
| 106 |
+
|
| 107 |
+
return True, []
|
| 108 |
+
|
| 109 |
+
except Exception as e:
|
| 110 |
+
print(f" β Error: {e}")
|
| 111 |
+
import traceback
|
| 112 |
+
traceback.print_exc()
|
| 113 |
+
return False, [str(e)]
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def main():
|
| 117 |
+
"""Run all tests."""
|
| 118 |
+
print("=" * 60)
|
| 119 |
+
print("π Edge Detection Demo - Test Suite")
|
| 120 |
+
print("=" * 60)
|
| 121 |
+
|
| 122 |
+
all_passed = True
|
| 123 |
+
|
| 124 |
+
# Test 1: Imports
|
| 125 |
+
passed, failed = test_imports()
|
| 126 |
+
if not passed:
|
| 127 |
+
print(f"\nβ Import test failed. Missing packages: {', '.join(failed)}")
|
| 128 |
+
print("\nπ‘ Run: pip install -r requirements.txt")
|
| 129 |
+
all_passed = False
|
| 130 |
+
|
| 131 |
+
# Test 2: App structure
|
| 132 |
+
if passed: # Only run if imports work
|
| 133 |
+
passed, failed = test_app_structure()
|
| 134 |
+
if not passed:
|
| 135 |
+
print(f"\nβ App structure test failed.")
|
| 136 |
+
all_passed = False
|
| 137 |
+
|
| 138 |
+
# Test 3: Edge detection
|
| 139 |
+
if passed:
|
| 140 |
+
passed, failed = test_edge_detection()
|
| 141 |
+
if not passed:
|
| 142 |
+
print(f"\nβ Edge detection test failed.")
|
| 143 |
+
all_passed = False
|
| 144 |
+
|
| 145 |
+
# Summary
|
| 146 |
+
print("\n" + "=" * 60)
|
| 147 |
+
if all_passed:
|
| 148 |
+
print("β
All tests passed! Ready to run the demo.")
|
| 149 |
+
print("\nπ Start with: ./run_simple.sh")
|
| 150 |
+
print(" Or: make run")
|
| 151 |
+
print(" Or: streamlit run app.py")
|
| 152 |
+
return 0
|
| 153 |
+
else:
|
| 154 |
+
print("β Some tests failed. Please fix the issues above.")
|
| 155 |
+
return 1
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
if __name__ == "__main__":
|
| 159 |
+
sys.exit(main())
|