app
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +4 -0
- DEPLOYMENT.md +221 -0
- HUGGINGFACE_DEPLOYMENT.md +264 -0
- Procfile +1 -0
- README.md +264 -8
- RENDER_DEPLOY_FIX.md +112 -0
- __pycache__/app.cpython-312.pyc +0 -0
- __pycache__/app_gradio.cpython-312.pyc +0 -0
- __pycache__/config_hf.cpython-312.pyc +0 -0
- __pycache__/model_predictor.cpython-312.pyc +0 -0
- app.py +424 -0
- app_config.py +59 -0
- app_gradio.py +424 -0
- config_hf.py +269 -0
- deploy_hf.py +246 -0
- model_predictor.py +386 -0
- models/.DS_Store +0 -0
- models/epitope_model.h5 +3 -0
- models/epitope_model.keras +3 -0
- models/epitope_model_savedmodel/.DS_Store +0 -0
- models/epitope_model_savedmodel/fingerprint.pb +3 -0
- models/epitope_model_savedmodel/saved_model.pb +3 -0
- models/epitope_model_savedmodel/variables/variables.data-00000-of-00001 +3 -0
- models/epitope_model_savedmodel/variables/variables.index +0 -0
- models/epitope_model_single_block.h5 +3 -0
- models/epitope_model_single_block.keras +3 -0
- render.yaml +23 -0
- requirements.txt +12 -0
- run.py +128 -0
- runtime.txt +1 -0
- start.sh +52 -0
- start_production.sh +12 -0
- static/.DS_Store +0 -0
- static/css/style.css +473 -0
- static/js/main.js +337 -0
- static/logo.png +3 -0
- static/uploads/27f92020-4c76-4a4d-a2d7-ae9e5e1688db_results.json +122 -0
- static/uploads/2dbe50b9-c42e-4889-8461-682ddf95b9d7_results.json +561 -0
- static/uploads/c803e953-00e7-49e3-ae1c-4228f3e7f02f_results.json +122 -0
- static/uploads/d5dc50da-3f8b-4277-a90a-4711f03435a4_results.json +122 -0
- static/uploads/f20900ed-1799-4b0c-a32b-368b667109f7_results.json +122 -0
- templates/about.html +326 -0
- templates/base.html +122 -0
- templates/index.html +234 -0
- templates/instructions.html +219 -0
- templates/results.html +392 -0
- test_app.py +232 -0
- test_deployment.py +137 -0
- test_gradio.py +175 -0
- test_model.py +69 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
models/epitope_model_savedmodel/variables/variables.data-00000-of-00001 filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
models/epitope_model_single_block.keras filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
models/epitope_model.keras filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
static/logo.png filter=lfs diff=lfs merge=lfs -text
|
DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EpiPred Deployment Guide
|
| 2 |
+
|
| 3 |
+
## 🚀 Render.com Deployment
|
| 4 |
+
|
| 5 |
+
### Quick Deploy Steps:
|
| 6 |
+
|
| 7 |
+
1. **Push to GitHub:**
|
| 8 |
+
```bash
|
| 9 |
+
git add .
|
| 10 |
+
git commit -m "Deploy EpiPred to Render"
|
| 11 |
+
git push origin main
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
2. **Connect to Render:**
|
| 15 |
+
- Go to [render.com](https://render.com)
|
| 16 |
+
- Click "New +" → "Web Service"
|
| 17 |
+
- Connect your GitHub repository
|
| 18 |
+
- Select the repository containing your EpiPred app
|
| 19 |
+
|
| 20 |
+
3. **Configure Service:**
|
| 21 |
+
- **Name:** `epitope-predictor`
|
| 22 |
+
- **Environment:** `Python`
|
| 23 |
+
- **Build Command:** `pip install --upgrade pip && pip install -r requirements-minimal.txt`
|
| 24 |
+
- **Start Command:** `gunicorn --bind 0.0.0.0:$PORT --timeout 300 --workers 1 wsgi:app`
|
| 25 |
+
- **Python Version:** `3.12.0`
|
| 26 |
+
|
| 27 |
+
4. **Environment Variables:**
|
| 28 |
+
```
|
| 29 |
+
FLASK_ENV=production
|
| 30 |
+
FLASK_DEBUG=false
|
| 31 |
+
PYTHON_VERSION=3.12.0
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
5. **Deploy:** Click "Create Web Service"
|
| 35 |
+
|
| 36 |
+
### Alternative: Use render.yaml
|
| 37 |
+
|
| 38 |
+
If you have `render.yaml` in your repository, Render will automatically use it:
|
| 39 |
+
|
| 40 |
+
```yaml
|
| 41 |
+
services:
|
| 42 |
+
- type: web
|
| 43 |
+
name: epitope-predictor
|
| 44 |
+
env: python
|
| 45 |
+
plan: free
|
| 46 |
+
buildCommand: |
|
| 47 |
+
pip install --upgrade pip
|
| 48 |
+
pip install -r requirements-minimal.txt
|
| 49 |
+
startCommand: gunicorn --bind 0.0.0.0:$PORT --timeout 300 --workers 1 wsgi:app
|
| 50 |
+
envVars:
|
| 51 |
+
- key: PYTHON_VERSION
|
| 52 |
+
value: 3.12.0
|
| 53 |
+
- key: FLASK_ENV
|
| 54 |
+
value: production
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
## 🐳 Docker Deployment
|
| 58 |
+
|
| 59 |
+
### Create Dockerfile:
|
| 60 |
+
|
| 61 |
+
```dockerfile
|
| 62 |
+
FROM python:3.12-slim
|
| 63 |
+
|
| 64 |
+
WORKDIR /app
|
| 65 |
+
|
| 66 |
+
# Install system dependencies
|
| 67 |
+
RUN apt-get update && apt-get install -y \
|
| 68 |
+
gcc \
|
| 69 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 70 |
+
|
| 71 |
+
# Copy requirements and install Python dependencies
|
| 72 |
+
COPY requirements-minimal.txt .
|
| 73 |
+
RUN pip install --no-cache-dir -r requirements-minimal.txt
|
| 74 |
+
|
| 75 |
+
# Copy application code
|
| 76 |
+
COPY . .
|
| 77 |
+
|
| 78 |
+
# Create uploads directory
|
| 79 |
+
RUN mkdir -p static/uploads
|
| 80 |
+
|
| 81 |
+
# Expose port
|
| 82 |
+
EXPOSE 5000
|
| 83 |
+
|
| 84 |
+
# Run the application
|
| 85 |
+
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--timeout", "300", "wsgi:app"]
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
### Build and Run:
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
docker build -t epipred .
|
| 92 |
+
docker run -p 5000:5000 epipred
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
## ☁️ Heroku Deployment
|
| 96 |
+
|
| 97 |
+
### Setup:
|
| 98 |
+
|
| 99 |
+
1. **Install Heroku CLI**
|
| 100 |
+
2. **Login:** `heroku login`
|
| 101 |
+
3. **Create app:** `heroku create your-app-name`
|
| 102 |
+
4. **Set Python version:** Ensure `runtime.txt` contains `python-3.12.0`
|
| 103 |
+
5. **Deploy:**
|
| 104 |
+
```bash
|
| 105 |
+
git add .
|
| 106 |
+
git commit -m "Deploy to Heroku"
|
| 107 |
+
git push heroku main
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
### Heroku Configuration:
|
| 111 |
+
|
| 112 |
+
```bash
|
| 113 |
+
heroku config:set FLASK_ENV=production
|
| 114 |
+
heroku config:set FLASK_DEBUG=false
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
## 🔧 Troubleshooting Deployment Issues
|
| 118 |
+
|
| 119 |
+
### Common Problems:
|
| 120 |
+
|
| 121 |
+
1. **Scikit-learn compilation errors:**
|
| 122 |
+
- Use `requirements-minimal.txt` instead of `requirements.txt`
|
| 123 |
+
- Remove scikit-learn dependency if not essential
|
| 124 |
+
|
| 125 |
+
2. **TensorFlow size issues:**
|
| 126 |
+
- Use `tensorflow-cpu` instead of `tensorflow`
|
| 127 |
+
- Consider using TensorFlow Lite for smaller deployments
|
| 128 |
+
|
| 129 |
+
3. **Memory issues:**
|
| 130 |
+
- Reduce model size or use model quantization
|
| 131 |
+
- Limit concurrent requests with gunicorn workers
|
| 132 |
+
|
| 133 |
+
4. **Build timeout:**
|
| 134 |
+
- Use pre-compiled wheels
|
| 135 |
+
- Remove unnecessary dependencies
|
| 136 |
+
- Use Docker with cached layers
|
| 137 |
+
|
| 138 |
+
### Memory Optimization:
|
| 139 |
+
|
| 140 |
+
```python
|
| 141 |
+
# In model_predictor.py, add memory management
|
| 142 |
+
import gc
|
| 143 |
+
import tensorflow as tf
|
| 144 |
+
|
| 145 |
+
# After model loading
|
| 146 |
+
tf.keras.backend.clear_session()
|
| 147 |
+
gc.collect()
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
### Performance Settings:
|
| 151 |
+
|
| 152 |
+
```bash
|
| 153 |
+
# Gunicorn configuration for production
|
| 154 |
+
gunicorn --bind 0.0.0.0:$PORT \
|
| 155 |
+
--timeout 300 \
|
| 156 |
+
--workers 1 \
|
| 157 |
+
--max-requests 1000 \
|
| 158 |
+
--max-requests-jitter 100 \
|
| 159 |
+
--preload \
|
| 160 |
+
wsgi:app
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
## 📊 Monitoring
|
| 164 |
+
|
| 165 |
+
### Health Check Endpoint:
|
| 166 |
+
|
| 167 |
+
Add to your Flask app:
|
| 168 |
+
|
| 169 |
+
```python
|
| 170 |
+
@app.route('/health')
|
| 171 |
+
def health_check():
|
| 172 |
+
return {'status': 'healthy', 'timestamp': datetime.now().isoformat()}
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
### Logging Configuration:
|
| 176 |
+
|
| 177 |
+
```python
|
| 178 |
+
import logging
|
| 179 |
+
logging.basicConfig(
|
| 180 |
+
level=logging.INFO,
|
| 181 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 182 |
+
)
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
## 🔒 Security Considerations
|
| 186 |
+
|
| 187 |
+
1. **Environment Variables:**
|
| 188 |
+
- Set `SECRET_KEY` in production
|
| 189 |
+
- Use environment-specific configurations
|
| 190 |
+
|
| 191 |
+
2. **File Uploads:**
|
| 192 |
+
- Validate file types and sizes
|
| 193 |
+
- Use temporary storage for uploads
|
| 194 |
+
- Clean up uploaded files regularly
|
| 195 |
+
|
| 196 |
+
3. **Rate Limiting:**
|
| 197 |
+
- Consider adding rate limiting for API endpoints
|
| 198 |
+
- Monitor resource usage
|
| 199 |
+
|
| 200 |
+
## 📝 Deployment Checklist
|
| 201 |
+
|
| 202 |
+
- [ ] Model files are in `models/` directory
|
| 203 |
+
- [ ] `requirements-minimal.txt` is used for deployment
|
| 204 |
+
- [ ] `wsgi.py` is configured as entry point
|
| 205 |
+
- [ ] Environment variables are set
|
| 206 |
+
- [ ] Health check endpoint is working
|
| 207 |
+
- [ ] File upload directory is writable
|
| 208 |
+
- [ ] Logging is configured
|
| 209 |
+
- [ ] Error handling is in place
|
| 210 |
+
- [ ] Security settings are applied
|
| 211 |
+
|
| 212 |
+
## 🎯 Production Recommendations
|
| 213 |
+
|
| 214 |
+
1. **Use a CDN** for static files
|
| 215 |
+
2. **Enable HTTPS** (Render provides this automatically)
|
| 216 |
+
3. **Set up monitoring** and alerting
|
| 217 |
+
4. **Regular backups** of any persistent data
|
| 218 |
+
5. **Performance testing** with expected load
|
| 219 |
+
6. **Error tracking** (e.g., Sentry)
|
| 220 |
+
|
| 221 |
+
Your EpiPred application should now be ready for production deployment! 🚀
|
HUGGINGFACE_DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🤗 Hugging Face Spaces Deployment Guide
|
| 2 |
+
|
| 3 |
+
## 🚀 Quick Start
|
| 4 |
+
|
| 5 |
+
Your EpiPred app is now ready for Hugging Face Spaces! Follow these steps:
|
| 6 |
+
|
| 7 |
+
### 1. Prepare Deployment Files
|
| 8 |
+
|
| 9 |
+
Run the deployment preparation script:
|
| 10 |
+
```bash
|
| 11 |
+
python deploy_hf.py
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
This will create a `hf_deploy` directory with all necessary files.
|
| 15 |
+
|
| 16 |
+
### 2. Create Hugging Face Space
|
| 17 |
+
|
| 18 |
+
1. Go to [Hugging Face Spaces](https://huggingface.co/spaces)
|
| 19 |
+
2. Click **"Create new Space"**
|
| 20 |
+
3. Fill in the details:
|
| 21 |
+
- **Owner**: Your username
|
| 22 |
+
- **Space name**: `epipred` (or your preferred name)
|
| 23 |
+
- **License**: MIT
|
| 24 |
+
- **SDK**: Gradio
|
| 25 |
+
- **Hardware**: CPU Basic (free tier)
|
| 26 |
+
- **Visibility**: Public
|
| 27 |
+
|
| 28 |
+
### 3. Upload Files
|
| 29 |
+
|
| 30 |
+
Upload all files from the `hf_deploy` directory to your Space repository:
|
| 31 |
+
|
| 32 |
+
- **Via Web Interface**: Drag and drop files
|
| 33 |
+
- **Via Git**: Clone the repository and push files
|
| 34 |
+
- **Via Hugging Face Hub**: Use the `huggingface_hub` library
|
| 35 |
+
|
| 36 |
+
## 📁 File Structure
|
| 37 |
+
|
| 38 |
+
```
|
| 39 |
+
hf_deploy/
|
| 40 |
+
├── README.md # Space metadata and description
|
| 41 |
+
├── app_gradio.py # Main Gradio application
|
| 42 |
+
├── model_predictor.py # Model loading and prediction
|
| 43 |
+
├── config_hf.py # Configuration for HF Spaces
|
| 44 |
+
├── requirements.txt # Python dependencies
|
| 45 |
+
├── .gitignore # Git ignore patterns
|
| 46 |
+
├── models/ # Model files directory
|
| 47 |
+
│ ├── epitope_model.keras
|
| 48 |
+
│ ├── epitope_model.h5
|
| 49 |
+
│ └── epitope_model_savedmodel/
|
| 50 |
+
├── test_sequences.fasta # Sample input files
|
| 51 |
+
└── sample_input.fasta
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
## ⚙️ Configuration
|
| 55 |
+
|
| 56 |
+
### Space Metadata (README.md)
|
| 57 |
+
|
| 58 |
+
The README.md contains important metadata:
|
| 59 |
+
|
| 60 |
+
```yaml
|
| 61 |
+
---
|
| 62 |
+
title: EpiPred - Epitope Prediction Tool
|
| 63 |
+
emoji: 🧬
|
| 64 |
+
colorFrom: blue
|
| 65 |
+
colorTo: green
|
| 66 |
+
sdk: gradio
|
| 67 |
+
sdk_version: 4.44.0
|
| 68 |
+
app_file: app_gradio.py
|
| 69 |
+
pinned: false
|
| 70 |
+
license: mit
|
| 71 |
+
---
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
### Resource Limits
|
| 75 |
+
|
| 76 |
+
Configured for Hugging Face Spaces free tier:
|
| 77 |
+
- **Max sequences**: 25 per submission
|
| 78 |
+
- **Max total length**: 150,000 amino acids
|
| 79 |
+
- **Max sequence length**: 3,000 amino acids each
|
| 80 |
+
- **Timeout**: 5 minutes per prediction
|
| 81 |
+
- **Memory**: Optimized for CPU Basic hardware
|
| 82 |
+
|
| 83 |
+
## 🧪 Testing
|
| 84 |
+
|
| 85 |
+
Before deployment, test your app locally:
|
| 86 |
+
|
| 87 |
+
```bash
|
| 88 |
+
# Test all components
|
| 89 |
+
python test_gradio.py
|
| 90 |
+
|
| 91 |
+
# Test Gradio app specifically
|
| 92 |
+
python app_gradio.py
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
## 🚀 Deployment Process
|
| 96 |
+
|
| 97 |
+
### Option 1: Web Interface
|
| 98 |
+
|
| 99 |
+
1. **Create Space** on Hugging Face
|
| 100 |
+
2. **Upload files** via drag-and-drop
|
| 101 |
+
3. **Wait for build** (usually 2-5 minutes)
|
| 102 |
+
4. **Test your app** at `https://huggingface.co/spaces/YOUR_USERNAME/epipred`
|
| 103 |
+
|
| 104 |
+
### Option 2: Git Repository
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
# Clone your Space repository
|
| 108 |
+
git clone https://huggingface.co/spaces/YOUR_USERNAME/epipred
|
| 109 |
+
cd epipred
|
| 110 |
+
|
| 111 |
+
# Copy deployment files
|
| 112 |
+
cp -r ../hf_deploy/* .
|
| 113 |
+
|
| 114 |
+
# Commit and push
|
| 115 |
+
git add .
|
| 116 |
+
git commit -m "Initial deployment of EpiPred"
|
| 117 |
+
git push
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### Option 3: Hugging Face Hub
|
| 121 |
+
|
| 122 |
+
```python
|
| 123 |
+
from huggingface_hub import HfApi
|
| 124 |
+
|
| 125 |
+
api = HfApi()
|
| 126 |
+
api.upload_folder(
|
| 127 |
+
folder_path="hf_deploy",
|
| 128 |
+
repo_id="YOUR_USERNAME/epipred",
|
| 129 |
+
repo_type="space"
|
| 130 |
+
)
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
## 🎯 Features
|
| 134 |
+
|
| 135 |
+
Your deployed app will include:
|
| 136 |
+
|
| 137 |
+
### ✅ Core Functionality
|
| 138 |
+
- **Sequence Input**: Text area and file upload
|
| 139 |
+
- **FASTA Parsing**: Automatic sequence extraction
|
| 140 |
+
- **Epitope Prediction**: B-cell and T-cell epitopes
|
| 141 |
+
- **Confidence Scoring**: Adjustable threshold
|
| 142 |
+
- **Results Visualization**: Interactive sequence markup
|
| 143 |
+
|
| 144 |
+
### ✅ User Interface
|
| 145 |
+
- **Professional Design**: Clean, responsive layout
|
| 146 |
+
- **Mobile Support**: Works on phones and tablets
|
| 147 |
+
- **Progress Indicators**: Real-time prediction progress
|
| 148 |
+
- **Error Handling**: User-friendly error messages
|
| 149 |
+
- **Help Documentation**: Built-in instructions
|
| 150 |
+
|
| 151 |
+
### ✅ Export Options
|
| 152 |
+
- **CSV Download**: Structured epitope data
|
| 153 |
+
- **JSON Download**: Complete results with metadata
|
| 154 |
+
- **Formatted Display**: Human-readable sequence markup
|
| 155 |
+
|
| 156 |
+
## 🔧 Troubleshooting
|
| 157 |
+
|
| 158 |
+
### Common Issues
|
| 159 |
+
|
| 160 |
+
**1. Build Fails**
|
| 161 |
+
- Check build logs in your Space
|
| 162 |
+
- Verify all files are uploaded
|
| 163 |
+
- Ensure requirements.txt is correct
|
| 164 |
+
|
| 165 |
+
**2. Model Loading Fails**
|
| 166 |
+
- App will automatically switch to demo mode
|
| 167 |
+
- Check model file sizes (HF has limits)
|
| 168 |
+
- Verify model file formats
|
| 169 |
+
|
| 170 |
+
**3. Out of Memory**
|
| 171 |
+
- Reduce sequence limits in config_hf.py
|
| 172 |
+
- Use smaller model files
|
| 173 |
+
- Consider upgrading to paid hardware
|
| 174 |
+
|
| 175 |
+
**4. Slow Performance**
|
| 176 |
+
- Optimize model inference
|
| 177 |
+
- Reduce batch sizes
|
| 178 |
+
- Use CPU-optimized TensorFlow
|
| 179 |
+
|
| 180 |
+
### Debug Mode
|
| 181 |
+
|
| 182 |
+
Enable debug mode by setting environment variables in your Space:
|
| 183 |
+
|
| 184 |
+
```
|
| 185 |
+
GRADIO_DEBUG=1
|
| 186 |
+
DEMO_MODE=true
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
## 🎨 Customization
|
| 190 |
+
|
| 191 |
+
### Modify Appearance
|
| 192 |
+
|
| 193 |
+
Edit `config_hf.py`:
|
| 194 |
+
```python
|
| 195 |
+
# Change limits
|
| 196 |
+
'MAX_SEQUENCES': 10, # Reduce for faster processing
|
| 197 |
+
|
| 198 |
+
# Modify styling
|
| 199 |
+
CUSTOM_CSS = """
|
| 200 |
+
.main-header {
|
| 201 |
+
background: linear-gradient(135deg, #your-colors);
|
| 202 |
+
}
|
| 203 |
+
"""
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
### Add Features
|
| 207 |
+
|
| 208 |
+
Edit `app_gradio.py`:
|
| 209 |
+
- Add new input components
|
| 210 |
+
- Modify prediction logic
|
| 211 |
+
- Enhance visualizations
|
| 212 |
+
- Add export formats
|
| 213 |
+
|
| 214 |
+
### Update Examples
|
| 215 |
+
|
| 216 |
+
Modify example sequences in `config_hf.py`:
|
| 217 |
+
```python
|
| 218 |
+
EXAMPLE_SEQUENCES = {
|
| 219 |
+
'basic': """Your custom examples here"""
|
| 220 |
+
}
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
## 📊 Analytics
|
| 224 |
+
|
| 225 |
+
Monitor your Space usage:
|
| 226 |
+
- **Views**: Track user engagement
|
| 227 |
+
- **Likes**: Community feedback
|
| 228 |
+
- **Duplicates**: Other users copying your Space
|
| 229 |
+
- **Discussions**: User questions and feedback
|
| 230 |
+
|
| 231 |
+
## 🔒 Security
|
| 232 |
+
|
| 233 |
+
### Best Practices
|
| 234 |
+
- **Input Validation**: Already implemented
|
| 235 |
+
- **File Size Limits**: Configured for safety
|
| 236 |
+
- **Timeout Protection**: Prevents long-running jobs
|
| 237 |
+
- **Error Handling**: No sensitive information exposed
|
| 238 |
+
|
| 239 |
+
### Privacy
|
| 240 |
+
- **No Data Storage**: Sequences are processed and discarded
|
| 241 |
+
- **Temporary Files**: Automatically cleaned up
|
| 242 |
+
- **No User Tracking**: Privacy-focused design
|
| 243 |
+
|
| 244 |
+
## 🎉 Success!
|
| 245 |
+
|
| 246 |
+
Once deployed, your EpiPred Space will be:
|
| 247 |
+
- **Publicly accessible** at your Space URL
|
| 248 |
+
- **Automatically scaled** by Hugging Face
|
| 249 |
+
- **Professionally hosted** with 99.9% uptime
|
| 250 |
+
- **Community discoverable** in the Spaces gallery
|
| 251 |
+
|
| 252 |
+
## 📞 Support
|
| 253 |
+
|
| 254 |
+
Need help?
|
| 255 |
+
- **Hugging Face Docs**: [spaces documentation](https://huggingface.co/docs/hub/spaces)
|
| 256 |
+
- **Community Forum**: [discuss.huggingface.co](https://discuss.huggingface.co)
|
| 257 |
+
- **Discord**: Hugging Face community server
|
| 258 |
+
- **GitHub Issues**: For app-specific problems
|
| 259 |
+
|
| 260 |
+
---
|
| 261 |
+
|
| 262 |
+
**Ready to deploy?** 🚀
|
| 263 |
+
|
| 264 |
+
Your EpiPred tool will soon be available to researchers worldwide! 🌍🧬
|
Procfile
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
web: gunicorn --bind 0.0.0.0:$PORT --timeout 300 --workers 1 app:app
|
README.md
CHANGED
|
@@ -1,13 +1,269 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
app_file:
|
| 9 |
pinned: false
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: EpiPred - Epitope Prediction Tool
|
| 3 |
+
emoji: 🧬
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.0
|
| 8 |
+
app_file: app_gradio.py
|
| 9 |
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
short_description: B-cell and T-cell epitope prediction using deep learning
|
| 12 |
+
tags:
|
| 13 |
+
- bioinformatics
|
| 14 |
+
- machine-learning
|
| 15 |
+
- epitope-prediction
|
| 16 |
+
- protein-analysis
|
| 17 |
+
- immunology
|
| 18 |
+
- tensorflow
|
| 19 |
+
- deep-learning
|
| 20 |
+
- attention-mechanism
|
| 21 |
+
- bilstm
|
| 22 |
---
|
| 23 |
|
| 24 |
+
# EpiPred - Epitope Prediction Tool 🧬
|
| 25 |
+
|
| 26 |
+
An advanced web-based tool for predicting B-cell and T-cell epitopes from protein sequences using deep learning with attention mechanisms and bidirectional LSTM networks.
|
| 27 |
+
|
| 28 |
+
## Features
|
| 29 |
+
|
| 30 |
+
- **Advanced Deep Learning**: Attention-based neural networks with bidirectional LSTM
|
| 31 |
+
- **Multi-target Prediction**: Both B-cell and T-cell epitope prediction
|
| 32 |
+
- **Interactive Interface**: User-friendly web interface similar to BepiPred-2.0
|
| 33 |
+
- **Flexible Input**: Support for text input and file upload (FASTA format)
|
| 34 |
+
- **Real-time Visualization**: Interactive sequence markup and confidence scoring
|
| 35 |
+
- **Multiple Export Formats**: Download results in CSV or JSON format
|
| 36 |
+
- **Responsive Design**: Works on desktop and mobile devices
|
| 37 |
+
|
| 38 |
+
## Installation
|
| 39 |
+
|
| 40 |
+
### Prerequisites
|
| 41 |
+
|
| 42 |
+
- Python 3.8 or higher
|
| 43 |
+
- pip (Python package installer)
|
| 44 |
+
|
| 45 |
+
### Setup
|
| 46 |
+
|
| 47 |
+
1. **Clone or download the application files**
|
| 48 |
+
```bash
|
| 49 |
+
cd app/
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
2. **Install dependencies**
|
| 53 |
+
```bash
|
| 54 |
+
pip install -r requirements.txt
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
3. **Ensure model files are available**
|
| 58 |
+
|
| 59 |
+
The application looks for trained model files in the following locations (in order of preference):
|
| 60 |
+
- `models/epitope_model.keras`
|
| 61 |
+
- `models/epitope_model.h5`
|
| 62 |
+
- `models/epitope_model_savedmodel/`
|
| 63 |
+
- `../epitope_model.keras`
|
| 64 |
+
- `../epitope_model.h5`
|
| 65 |
+
- `../epitope_model_savedmodel/`
|
| 66 |
+
- `epitope_model.keras`
|
| 67 |
+
- `epitope_model.h5`
|
| 68 |
+
- `epitope_model_savedmodel/`
|
| 69 |
+
|
| 70 |
+
The model files should be placed in the `models/` directory within the app folder.
|
| 71 |
+
|
| 72 |
+
4. **Run the application**
|
| 73 |
+
```bash
|
| 74 |
+
python run.py
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
Or alternatively:
|
| 78 |
+
```bash
|
| 79 |
+
python app.py
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
5. **Access the web interface**
|
| 83 |
+
|
| 84 |
+
Open your web browser and navigate to:
|
| 85 |
+
```
|
| 86 |
+
http://localhost:5000
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
## Usage
|
| 90 |
+
|
| 91 |
+
### Input Formats
|
| 92 |
+
|
| 93 |
+
The tool accepts protein sequences in FASTA format:
|
| 94 |
+
|
| 95 |
+
```
|
| 96 |
+
>Sequence_Name_1
|
| 97 |
+
MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL
|
| 98 |
+
|
| 99 |
+
>Sequence_Name_2
|
| 100 |
+
CARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
### Submission Limits
|
| 104 |
+
|
| 105 |
+
- **Maximum sequences**: 50 per submission
|
| 106 |
+
- **Maximum total length**: 300,000 amino acids
|
| 107 |
+
- **Sequence length range**: 10-6,000 amino acids each
|
| 108 |
+
- **File size limit**: 16 MB
|
| 109 |
+
|
| 110 |
+
### Input Methods
|
| 111 |
+
|
| 112 |
+
1. **Text Input**: Paste sequences directly into the text area
|
| 113 |
+
2. **File Upload**: Upload FASTA files (.txt, .fasta, .fa, .fas)
|
| 114 |
+
|
| 115 |
+
### Results Interpretation
|
| 116 |
+
|
| 117 |
+
- **Confidence Scores**: Range from 0.0 to 1.0 (higher = more confident)
|
| 118 |
+
- **Sequence Markup**:
|
| 119 |
+
- `B` = B-cell epitope regions
|
| 120 |
+
- `T` = T-cell epitope regions
|
| 121 |
+
- `.` = Non-epitope regions
|
| 122 |
+
- **Interactive Threshold**: Adjust confidence threshold to filter results
|
| 123 |
+
- **Position Ranges**: 1-based indexing (first amino acid = position 1)
|
| 124 |
+
|
| 125 |
+
## Configuration
|
| 126 |
+
|
| 127 |
+
### Environment Variables
|
| 128 |
+
|
| 129 |
+
- `FLASK_DEBUG`: Set to 'true' for debug mode (default: false)
|
| 130 |
+
- `FLASK_HOST`: Host address (default: 0.0.0.0)
|
| 131 |
+
- `FLASK_PORT`: Port number (default: 5000)
|
| 132 |
+
|
| 133 |
+
### Example
|
| 134 |
+
|
| 135 |
+
```bash
|
| 136 |
+
export FLASK_DEBUG=true
|
| 137 |
+
export FLASK_PORT=8080
|
| 138 |
+
python run.py
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
## Sample Input Files
|
| 142 |
+
|
| 143 |
+
Two sample FASTA files are provided for testing:
|
| 144 |
+
|
| 145 |
+
1. **`test_sequences.fasta`** - Small test file with 3 sequences (good for quick testing)
|
| 146 |
+
2. **`sample_input.fasta`** - Larger sample with 10 diverse protein sequences including:
|
| 147 |
+
- Human Insulin chains
|
| 148 |
+
- Viral proteins (SARS-CoV-2, HIV, Influenza, Zika)
|
| 149 |
+
- Bacterial antigens (Tuberculosis, Hepatitis B)
|
| 150 |
+
- Cancer-related proteins (p53)
|
| 151 |
+
- Parasitic proteins (Malaria)
|
| 152 |
+
|
| 153 |
+
### How to Use Sample Files:
|
| 154 |
+
|
| 155 |
+
1. **Via Web Interface:**
|
| 156 |
+
- Go to http://localhost:5000
|
| 157 |
+
- Click "Upload a FASTA file"
|
| 158 |
+
- Select either `test_sequences.fasta` or `sample_input.fasta`
|
| 159 |
+
- Click "Predict Epitopes"
|
| 160 |
+
|
| 161 |
+
2. **Copy and Paste:**
|
| 162 |
+
- Open either sample file in a text editor
|
| 163 |
+
- Copy the contents
|
| 164 |
+
- Paste into the text area on the web interface
|
| 165 |
+
- Click "Predict Epitopes"
|
| 166 |
+
|
| 167 |
+
## File Structure
|
| 168 |
+
|
| 169 |
+
```
|
| 170 |
+
app/
|
| 171 |
+
├── app.py # Main Flask application
|
| 172 |
+
├── model_predictor.py # Model loading and prediction logic
|
| 173 |
+
├── run.py # Application launcher
|
| 174 |
+
├── test_app.py # Test script
|
| 175 |
+
├── start.sh # Linux/Mac startup script
|
| 176 |
+
├── start.bat # Windows startup script
|
| 177 |
+
├── requirements.txt # Python dependencies
|
| 178 |
+
├── README.md # This file
|
| 179 |
+
├── test_sequences.fasta # Small test file
|
| 180 |
+
├── sample_input.fasta # Larger sample file
|
| 181 |
+
├── models/ # Model files directory
|
| 182 |
+
│ ├── epitope_model.keras
|
| 183 |
+
│ ├── epitope_model.h5
|
| 184 |
+
│ └── epitope_model_savedmodel/
|
| 185 |
+
├── templates/ # HTML templates
|
| 186 |
+
│ ├── base.html
|
| 187 |
+
│ ├── index.html
|
| 188 |
+
│ ├── results.html
|
| 189 |
+
│ ├── instructions.html
|
| 190 |
+
│ └── about.html
|
| 191 |
+
└── static/ # Static files
|
| 192 |
+
├── css/
|
| 193 |
+
│ └── style.css
|
| 194 |
+
├── js/
|
| 195 |
+
│ └── main.js
|
| 196 |
+
└── uploads/ # Temporary file storage
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
## API Endpoints
|
| 200 |
+
|
| 201 |
+
- `GET /` - Main page
|
| 202 |
+
- `POST /predict` - Submit sequences for prediction
|
| 203 |
+
- `GET /instructions` - Usage instructions
|
| 204 |
+
- `GET /about` - Method information
|
| 205 |
+
- `GET /download/<job_id>/<format>` - Download results
|
| 206 |
+
|
| 207 |
+
## Troubleshooting
|
| 208 |
+
|
| 209 |
+
### Common Issues
|
| 210 |
+
|
| 211 |
+
1. **Model not found error**
|
| 212 |
+
- Ensure model files are in the correct location
|
| 213 |
+
- Check file permissions
|
| 214 |
+
|
| 215 |
+
2. **Import errors**
|
| 216 |
+
- Install all dependencies: `pip install -r requirements.txt`
|
| 217 |
+
- Check Python version (3.8+ required)
|
| 218 |
+
|
| 219 |
+
3. **Memory errors**
|
| 220 |
+
- Reduce batch size for large sequences
|
| 221 |
+
- Process fewer sequences at once
|
| 222 |
+
|
| 223 |
+
4. **Port already in use**
|
| 224 |
+
- Change port: `export FLASK_PORT=8080`
|
| 225 |
+
- Or kill existing process
|
| 226 |
+
|
| 227 |
+
### Performance Tips
|
| 228 |
+
|
| 229 |
+
- Use smaller batch sizes for very long sequences
|
| 230 |
+
- Consider using GPU acceleration for TensorFlow if available
|
| 231 |
+
- Limit concurrent users in production environments
|
| 232 |
+
|
| 233 |
+
## Development
|
| 234 |
+
|
| 235 |
+
### Adding New Features
|
| 236 |
+
|
| 237 |
+
1. **Backend**: Modify `app.py` and `model_predictor.py`
|
| 238 |
+
2. **Frontend**: Update templates and static files
|
| 239 |
+
3. **Styling**: Modify `static/css/style.css`
|
| 240 |
+
4. **JavaScript**: Update `static/js/main.js`
|
| 241 |
+
|
| 242 |
+
### Testing
|
| 243 |
+
|
| 244 |
+
```bash
|
| 245 |
+
# Test with example sequences
|
| 246 |
+
curl -X POST http://localhost:5000/predict \
|
| 247 |
+
-F "sequence_text=>Test\nMKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL"
|
| 248 |
+
```
|
| 249 |
+
|
| 250 |
+
## License
|
| 251 |
+
|
| 252 |
+
This project is provided as-is for research and educational purposes.
|
| 253 |
+
|
| 254 |
+
## Support
|
| 255 |
+
|
| 256 |
+
For technical issues or questions about the method, please refer to:
|
| 257 |
+
- Instructions page in the web interface
|
| 258 |
+
- About page for method details
|
| 259 |
+
- This README file for setup help
|
| 260 |
+
|
| 261 |
+
## Citation
|
| 262 |
+
|
| 263 |
+
If you use EpiPred in your research, please cite:
|
| 264 |
+
|
| 265 |
+
```
|
| 266 |
+
EpiPred: Advanced Epitope Prediction Using Attention-based Deep Learning.
|
| 267 |
+
Bioinformatics and Computational Biology (2024)
|
| 268 |
+
DOI: 10.xxxx/xxxxxx
|
| 269 |
+
```
|
RENDER_DEPLOY_FIX.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚀 Render Deployment Fix Guide
|
| 2 |
+
|
| 3 |
+
## Current Issue
|
| 4 |
+
Render is using Python 3.13.4, but TensorFlow 2.13.1 is not available for this version.
|
| 5 |
+
|
| 6 |
+
## ✅ Solution Applied
|
| 7 |
+
|
| 8 |
+
I've updated the requirements to be compatible with Python 3.13:
|
| 9 |
+
|
| 10 |
+
### Updated `requirements.txt`:
|
| 11 |
+
```txt
|
| 12 |
+
# Core Flask dependencies - compatible with Python 3.13
|
| 13 |
+
Flask>=2.3.0,<4.0.0
|
| 14 |
+
Werkzeug>=2.3.0,<4.0.0
|
| 15 |
+
Jinja2>=3.1.0,<4.0.0
|
| 16 |
+
MarkupSafe>=2.1.0,<3.0.0
|
| 17 |
+
click>=8.1.0,<9.0.0
|
| 18 |
+
itsdangerous>=2.1.0,<3.0.0
|
| 19 |
+
|
| 20 |
+
# Machine Learning - Use latest compatible TensorFlow CPU
|
| 21 |
+
tensorflow-cpu>=2.16.0,<2.21.0
|
| 22 |
+
numpy>=1.24.0,<2.0.0
|
| 23 |
+
pandas>=2.0.0,<3.0.0
|
| 24 |
+
|
| 25 |
+
# Production server
|
| 26 |
+
gunicorn>=21.0.0,<23.0.0
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
### Updated `render.yaml`:
|
| 30 |
+
- Simplified build command to use main `requirements.txt`
|
| 31 |
+
- Specified Python 3.12.7 (more stable than 3.13)
|
| 32 |
+
- Optimized gunicorn settings
|
| 33 |
+
|
| 34 |
+
## 🎯 Deployment Strategies
|
| 35 |
+
|
| 36 |
+
### Strategy 1: Force Python 3.12 (Recommended)
|
| 37 |
+
The `render.yaml` now specifies Python 3.12.7 which has better package compatibility.
|
| 38 |
+
|
| 39 |
+
### Strategy 2: If Python 3.13 is Required
|
| 40 |
+
Use the flexible version ranges in the updated `requirements.txt` which should work with Python 3.13.
|
| 41 |
+
|
| 42 |
+
### Strategy 3: Fallback (Demo Mode)
|
| 43 |
+
If TensorFlow still fails, you can temporarily use `requirements-no-tf.txt`:
|
| 44 |
+
|
| 45 |
+
```yaml
|
| 46 |
+
buildCommand: |
|
| 47 |
+
pip install --upgrade pip
|
| 48 |
+
pip install -r requirements-no-tf.txt
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
## 🔧 Manual Override Options
|
| 52 |
+
|
| 53 |
+
If the automatic deployment still fails, you can manually override in Render dashboard:
|
| 54 |
+
|
| 55 |
+
### Option A: Use Python 3.12
|
| 56 |
+
1. Go to your service settings in Render
|
| 57 |
+
2. Set Environment Variable: `PYTHON_VERSION=3.12.7`
|
| 58 |
+
3. Redeploy
|
| 59 |
+
|
| 60 |
+
### Option B: Use Fallback Requirements
|
| 61 |
+
1. Change build command to: `pip install -r requirements-no-tf.txt`
|
| 62 |
+
2. App will run in demo mode (still fully functional UI)
|
| 63 |
+
|
| 64 |
+
### Option C: Use Latest TensorFlow
|
| 65 |
+
1. Change build command to: `pip install Flask Werkzeug numpy pandas gunicorn tensorflow-cpu`
|
| 66 |
+
2. Let pip resolve the latest compatible versions
|
| 67 |
+
|
| 68 |
+
## 🧪 Test Locally First
|
| 69 |
+
|
| 70 |
+
Before deploying, test the requirements locally:
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
# Test with Python 3.12
|
| 74 |
+
python3.12 -m pip install -r requirements.txt
|
| 75 |
+
|
| 76 |
+
# Test with Python 3.13
|
| 77 |
+
python3.13 -m pip install -r requirements.txt
|
| 78 |
+
|
| 79 |
+
# Test fallback
|
| 80 |
+
python -m pip install -r requirements-no-tf.txt
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
## 📊 Expected Outcomes
|
| 84 |
+
|
| 85 |
+
### ✅ Success Scenario:
|
| 86 |
+
- TensorFlow loads successfully
|
| 87 |
+
- Full ML functionality available
|
| 88 |
+
- Real epitope predictions
|
| 89 |
+
|
| 90 |
+
### ⚠️ Fallback Scenario:
|
| 91 |
+
- TensorFlow fails to load
|
| 92 |
+
- App runs in demo mode
|
| 93 |
+
- UI fully functional with demo predictions
|
| 94 |
+
- Still useful for testing and demonstration
|
| 95 |
+
|
| 96 |
+
## 🚀 Ready to Deploy
|
| 97 |
+
|
| 98 |
+
The updated files should now work with Render's Python 3.13.4. The key changes:
|
| 99 |
+
|
| 100 |
+
1. ✅ Flexible version ranges instead of pinned versions
|
| 101 |
+
2. ✅ Latest TensorFlow CPU version (2.16+)
|
| 102 |
+
3. ✅ Python 3.12.7 specification in render.yaml
|
| 103 |
+
4. ✅ Fallback demo mode if ML libraries fail
|
| 104 |
+
5. ✅ Simplified build process
|
| 105 |
+
|
| 106 |
+
**Next Steps:**
|
| 107 |
+
1. Commit and push these changes
|
| 108 |
+
2. Trigger a new deployment on Render
|
| 109 |
+
3. Monitor the build logs
|
| 110 |
+
4. Test the deployed application
|
| 111 |
+
|
| 112 |
+
The deployment should now succeed! 🎉
|
__pycache__/app.cpython-312.pyc
ADDED
|
Binary file (13.5 kB). View file
|
|
|
__pycache__/app_gradio.cpython-312.pyc
ADDED
|
Binary file (18.6 kB). View file
|
|
|
__pycache__/config_hf.cpython-312.pyc
ADDED
|
Binary file (7.89 kB). View file
|
|
|
__pycache__/model_predictor.cpython-312.pyc
ADDED
|
Binary file (17 kB). View file
|
|
|
app.py
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Epitope Prediction Web Tool
|
| 4 |
+
Similar to BepiPred-2.0 for B-cell and T-cell epitope prediction
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import json
|
| 9 |
+
import uuid
|
| 10 |
+
import logging
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
from flask import Flask, render_template, request, jsonify, send_file, flash, redirect, url_for
|
| 13 |
+
from werkzeug.utils import secure_filename
|
| 14 |
+
import pandas as pd
|
| 15 |
+
import numpy as np
|
| 16 |
+
from io import StringIO, BytesIO
|
| 17 |
+
import tempfile
|
| 18 |
+
|
| 19 |
+
# Import our custom prediction module
|
| 20 |
+
from model_predictor import EpitopePredictor
|
| 21 |
+
|
| 22 |
+
# Configure logging
|
| 23 |
+
logging.basicConfig(
|
| 24 |
+
level=logging.INFO,
|
| 25 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 26 |
+
handlers=[
|
| 27 |
+
logging.FileHandler('epitope_prediction.log'),
|
| 28 |
+
logging.StreamHandler()
|
| 29 |
+
]
|
| 30 |
+
)
|
| 31 |
+
logger = logging.getLogger(__name__)
|
| 32 |
+
|
| 33 |
+
app = Flask(__name__)
|
| 34 |
+
app.secret_key = 'your-secret-key-change-this-in-production'
|
| 35 |
+
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
|
| 36 |
+
app.config['UPLOAD_FOLDER'] = 'static/uploads'
|
| 37 |
+
|
| 38 |
+
# Initialize the predictor with error handling
|
| 39 |
+
predictor = None
|
| 40 |
+
try:
|
| 41 |
+
logger.info("Initializing EpitopePredictor...")
|
| 42 |
+
predictor = EpitopePredictor()
|
| 43 |
+
logger.info("Model loaded successfully")
|
| 44 |
+
|
| 45 |
+
# Log model information
|
| 46 |
+
model_info = predictor.get_model_info()
|
| 47 |
+
logger.info(f"Model configuration: {model_info}")
|
| 48 |
+
|
| 49 |
+
except Exception as e:
|
| 50 |
+
logger.error(f"Failed to load model: {e}")
|
| 51 |
+
predictor = None
|
| 52 |
+
|
| 53 |
+
# Allowed file extensions
|
| 54 |
+
ALLOWED_EXTENSIONS = {'txt', 'fasta', 'fa', 'fas'}
|
| 55 |
+
|
| 56 |
+
def allowed_file(filename):
|
| 57 |
+
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
| 58 |
+
|
| 59 |
+
@app.route('/')
|
| 60 |
+
def index():
|
| 61 |
+
"""Main page with sequence input interface"""
|
| 62 |
+
logger.info("Serving main page")
|
| 63 |
+
return render_template('index.html')
|
| 64 |
+
|
| 65 |
+
@app.route('/instructions')
|
| 66 |
+
def instructions():
|
| 67 |
+
"""Instructions page"""
|
| 68 |
+
logger.info("Serving instructions page")
|
| 69 |
+
return render_template('instructions.html')
|
| 70 |
+
|
| 71 |
+
@app.route('/about')
|
| 72 |
+
def about():
|
| 73 |
+
"""About page with method information"""
|
| 74 |
+
logger.info("Serving about page")
|
| 75 |
+
return render_template('about.html')
|
| 76 |
+
|
| 77 |
+
@app.route('/health')
|
| 78 |
+
def health_check():
|
| 79 |
+
"""Health check endpoint for deployment monitoring"""
|
| 80 |
+
from datetime import datetime
|
| 81 |
+
return jsonify({
|
| 82 |
+
'status': 'healthy',
|
| 83 |
+
'timestamp': datetime.now().isoformat(),
|
| 84 |
+
'model_loaded': predictor is not None,
|
| 85 |
+
'version': '1.0.0'
|
| 86 |
+
})
|
| 87 |
+
|
| 88 |
+
@app.route('/status')
|
| 89 |
+
def status():
|
| 90 |
+
"""System status endpoint for monitoring"""
|
| 91 |
+
logger.info("Status check requested")
|
| 92 |
+
|
| 93 |
+
status_info = {
|
| 94 |
+
'model_loaded': predictor is not None,
|
| 95 |
+
'timestamp': datetime.now().isoformat(),
|
| 96 |
+
'upload_folder_exists': os.path.exists(app.config['UPLOAD_FOLDER'])
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
if predictor:
|
| 100 |
+
try:
|
| 101 |
+
model_info = predictor.get_model_info()
|
| 102 |
+
status_info.update({
|
| 103 |
+
'model_info': model_info,
|
| 104 |
+
'model_ready': True
|
| 105 |
+
})
|
| 106 |
+
except Exception as e:
|
| 107 |
+
logger.error(f"Error getting model info: {e}")
|
| 108 |
+
status_info.update({
|
| 109 |
+
'model_ready': False,
|
| 110 |
+
'model_error': str(e)
|
| 111 |
+
})
|
| 112 |
+
else:
|
| 113 |
+
status_info.update({
|
| 114 |
+
'model_ready': False,
|
| 115 |
+
'model_error': 'Model not loaded'
|
| 116 |
+
})
|
| 117 |
+
|
| 118 |
+
logger.info(f"Status check result: {status_info}")
|
| 119 |
+
return jsonify(status_info)
|
| 120 |
+
|
| 121 |
+
@app.route('/predict', methods=['POST'])
|
| 122 |
+
def predict():
|
| 123 |
+
"""Handle sequence prediction requests"""
|
| 124 |
+
logger.info("Received prediction request")
|
| 125 |
+
|
| 126 |
+
# Check if predictor is available
|
| 127 |
+
if predictor is None:
|
| 128 |
+
logger.error("Prediction service is unavailable - model not loaded")
|
| 129 |
+
flash('Prediction service is currently unavailable. Please check that the model files are properly installed.', 'error')
|
| 130 |
+
return redirect(url_for('index'))
|
| 131 |
+
|
| 132 |
+
try:
|
| 133 |
+
# Get input data
|
| 134 |
+
sequence_text = request.form.get('sequence_text', '').strip()
|
| 135 |
+
uploaded_file = request.files.get('sequence_file')
|
| 136 |
+
|
| 137 |
+
logger.info(f"Input - Text: {len(sequence_text)} chars, File: {'Yes' if uploaded_file and uploaded_file.filename else 'No'}")
|
| 138 |
+
|
| 139 |
+
sequences = {}
|
| 140 |
+
|
| 141 |
+
# Process text input
|
| 142 |
+
if sequence_text:
|
| 143 |
+
try:
|
| 144 |
+
logger.info("Parsing sequence text input")
|
| 145 |
+
sequences.update(parse_fasta_text(sequence_text))
|
| 146 |
+
logger.info(f"Parsed {len(sequences)} sequences from text input")
|
| 147 |
+
except Exception as e:
|
| 148 |
+
logger.error(f"Error parsing sequence text: {str(e)}")
|
| 149 |
+
flash(f'Error parsing sequence text: {str(e)}', 'error')
|
| 150 |
+
return redirect(url_for('index'))
|
| 151 |
+
|
| 152 |
+
# Process uploaded file
|
| 153 |
+
if uploaded_file and uploaded_file.filename != '' and allowed_file(uploaded_file.filename):
|
| 154 |
+
try:
|
| 155 |
+
filename = secure_filename(uploaded_file.filename)
|
| 156 |
+
file_content = uploaded_file.read().decode('utf-8')
|
| 157 |
+
logger.info(f"Processing uploaded file: {filename} ({len(file_content)} chars)")
|
| 158 |
+
|
| 159 |
+
file_sequences = parse_fasta_text(file_content)
|
| 160 |
+
sequences.update(file_sequences)
|
| 161 |
+
logger.info(f"Parsed {len(file_sequences)} sequences from uploaded file")
|
| 162 |
+
except UnicodeDecodeError:
|
| 163 |
+
logger.error("Unicode decode error when reading uploaded file")
|
| 164 |
+
flash('Error reading file. Please ensure the file is in text format with UTF-8 encoding.', 'error')
|
| 165 |
+
return redirect(url_for('index'))
|
| 166 |
+
except Exception as e:
|
| 167 |
+
logger.error(f"Error processing uploaded file: {str(e)}")
|
| 168 |
+
flash(f'Error processing uploaded file: {str(e)}', 'error')
|
| 169 |
+
return redirect(url_for('index'))
|
| 170 |
+
|
| 171 |
+
if not sequences:
|
| 172 |
+
logger.warning("No sequences provided in request")
|
| 173 |
+
flash('Please provide protein sequences in FASTA format.', 'error')
|
| 174 |
+
return redirect(url_for('index'))
|
| 175 |
+
|
| 176 |
+
logger.info(f"Total sequences to process: {len(sequences)}")
|
| 177 |
+
|
| 178 |
+
# Validate sequences
|
| 179 |
+
validation_errors = validate_sequences(sequences)
|
| 180 |
+
if validation_errors:
|
| 181 |
+
logger.warning(f"Sequence validation failed: {len(validation_errors)} errors")
|
| 182 |
+
for error in validation_errors:
|
| 183 |
+
logger.warning(f"Validation error: {error}")
|
| 184 |
+
flash(error, 'error')
|
| 185 |
+
return redirect(url_for('index'))
|
| 186 |
+
|
| 187 |
+
logger.info("Sequence validation passed")
|
| 188 |
+
|
| 189 |
+
# Generate job ID
|
| 190 |
+
job_id = str(uuid.uuid4())
|
| 191 |
+
logger.info(f"Generated job ID: {job_id}")
|
| 192 |
+
|
| 193 |
+
# Perform predictions
|
| 194 |
+
results = {}
|
| 195 |
+
failed_sequences = []
|
| 196 |
+
|
| 197 |
+
for seq_name, seq in sequences.items():
|
| 198 |
+
try:
|
| 199 |
+
logger.info(f"Predicting epitopes for sequence: {seq_name} (length: {len(seq)})")
|
| 200 |
+
|
| 201 |
+
# Call the model predictor
|
| 202 |
+
b_cell_epitopes, t_cell_epitopes = predictor.predict_epitopes(seq)
|
| 203 |
+
|
| 204 |
+
logger.info(f"Prediction completed for {seq_name}: {len(b_cell_epitopes)} B-cell, {len(t_cell_epitopes)} T-cell epitopes")
|
| 205 |
+
|
| 206 |
+
results[seq_name] = {
|
| 207 |
+
'sequence': seq,
|
| 208 |
+
'b_cell_epitopes': b_cell_epitopes,
|
| 209 |
+
't_cell_epitopes': t_cell_epitopes,
|
| 210 |
+
'length': len(seq)
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
# Log detailed results
|
| 214 |
+
if b_cell_epitopes:
|
| 215 |
+
logger.info(f"B-cell epitopes for {seq_name}: {[(ep[0], f'{ep[1]:.3f}', ep[2]) for ep in b_cell_epitopes[:3]]}")
|
| 216 |
+
if t_cell_epitopes:
|
| 217 |
+
logger.info(f"T-cell epitopes for {seq_name}: {[(ep[0], f'{ep[1]:.3f}', ep[2]) for ep in t_cell_epitopes[:3]]}")
|
| 218 |
+
|
| 219 |
+
except Exception as e:
|
| 220 |
+
logger.error(f"Prediction failed for sequence {seq_name}: {e}")
|
| 221 |
+
failed_sequences.append(seq_name)
|
| 222 |
+
|
| 223 |
+
# Check if any predictions succeeded
|
| 224 |
+
if not results:
|
| 225 |
+
logger.error("All predictions failed")
|
| 226 |
+
flash('Prediction failed for all sequences. Please check your input and try again.', 'error')
|
| 227 |
+
return redirect(url_for('index'))
|
| 228 |
+
|
| 229 |
+
logger.info(f"Prediction completed successfully for {len(results)} sequences")
|
| 230 |
+
|
| 231 |
+
# Warn about failed sequences
|
| 232 |
+
if failed_sequences:
|
| 233 |
+
logger.warning(f"Prediction failed for {len(failed_sequences)} sequences: {failed_sequences}")
|
| 234 |
+
flash(f'Prediction failed for {len(failed_sequences)} sequence(s): {", ".join(failed_sequences[:3])}{"..." if len(failed_sequences) > 3 else ""}', 'warning')
|
| 235 |
+
|
| 236 |
+
# Store results (in production, use a database)
|
| 237 |
+
results_data = {
|
| 238 |
+
'job_id': job_id,
|
| 239 |
+
'timestamp': datetime.now().isoformat(),
|
| 240 |
+
'results': results,
|
| 241 |
+
'total_sequences': len(sequences),
|
| 242 |
+
'model_info': predictor.get_model_info() if predictor else {}
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
# Save results to temporary file
|
| 246 |
+
results_file = os.path.join(app.config['UPLOAD_FOLDER'], f'{job_id}_results.json')
|
| 247 |
+
with open(results_file, 'w') as f:
|
| 248 |
+
json.dump(results_data, f, indent=2)
|
| 249 |
+
|
| 250 |
+
logger.info(f"Results saved to: {results_file}")
|
| 251 |
+
logger.info(f"Prediction request completed successfully for job: {job_id}")
|
| 252 |
+
|
| 253 |
+
return render_template('results.html',
|
| 254 |
+
job_id=job_id,
|
| 255 |
+
results=results_data,
|
| 256 |
+
enumerate=enumerate)
|
| 257 |
+
|
| 258 |
+
except Exception as e:
|
| 259 |
+
logger.error(f"Unexpected error during prediction: {str(e)}")
|
| 260 |
+
flash(f'An error occurred during prediction: {str(e)}', 'error')
|
| 261 |
+
return redirect(url_for('index'))
|
| 262 |
+
|
| 263 |
+
@app.route('/download/<job_id>/<format>')
|
| 264 |
+
def download_results(job_id, format):
|
| 265 |
+
"""Download results in specified format"""
|
| 266 |
+
logger.info(f"Download request - Job ID: {job_id}, Format: {format}")
|
| 267 |
+
|
| 268 |
+
try:
|
| 269 |
+
results_file = os.path.join(app.config['UPLOAD_FOLDER'], f'{job_id}_results.json')
|
| 270 |
+
|
| 271 |
+
if not os.path.exists(results_file):
|
| 272 |
+
logger.warning(f"Results file not found: {results_file}")
|
| 273 |
+
flash('Results not found or expired.', 'error')
|
| 274 |
+
return redirect(url_for('index'))
|
| 275 |
+
|
| 276 |
+
with open(results_file, 'r') as f:
|
| 277 |
+
results_data = json.load(f)
|
| 278 |
+
|
| 279 |
+
logger.info(f"Generating {format.upper()} download for job {job_id}")
|
| 280 |
+
|
| 281 |
+
if format.lower() == 'csv':
|
| 282 |
+
return download_csv(results_data, job_id)
|
| 283 |
+
elif format.lower() == 'json':
|
| 284 |
+
return download_json(results_data, job_id)
|
| 285 |
+
else:
|
| 286 |
+
logger.warning(f"Invalid download format requested: {format}")
|
| 287 |
+
flash('Invalid download format.', 'error')
|
| 288 |
+
return redirect(url_for('index'))
|
| 289 |
+
|
| 290 |
+
except Exception as e:
|
| 291 |
+
logger.error(f"Error downloading results for job {job_id}: {str(e)}")
|
| 292 |
+
flash(f'Error downloading results: {str(e)}', 'error')
|
| 293 |
+
return redirect(url_for('index'))
|
| 294 |
+
|
| 295 |
+
def parse_fasta_text(text):
|
| 296 |
+
"""Parse FASTA format text and return dictionary of sequences"""
|
| 297 |
+
logger.debug("Parsing FASTA text input")
|
| 298 |
+
sequences = {}
|
| 299 |
+
current_name = None
|
| 300 |
+
current_seq = []
|
| 301 |
+
|
| 302 |
+
for line in text.strip().split('\n'):
|
| 303 |
+
line = line.strip()
|
| 304 |
+
if line.startswith('>'):
|
| 305 |
+
if current_name and current_seq:
|
| 306 |
+
sequences[current_name] = ''.join(current_seq)
|
| 307 |
+
current_name = line[1:].strip() or f"Sequence_{len(sequences)+1}"
|
| 308 |
+
current_seq = []
|
| 309 |
+
elif line and current_name:
|
| 310 |
+
# Remove any non-amino acid characters
|
| 311 |
+
clean_seq = ''.join(c.upper() for c in line if c.upper() in 'ACDEFGHIKLMNPQRSTVWY')
|
| 312 |
+
current_seq.append(clean_seq)
|
| 313 |
+
|
| 314 |
+
if current_name and current_seq:
|
| 315 |
+
sequences[current_name] = ''.join(current_seq)
|
| 316 |
+
|
| 317 |
+
logger.debug(f"Parsed {len(sequences)} sequences from FASTA text")
|
| 318 |
+
return sequences
|
| 319 |
+
|
| 320 |
+
def validate_sequences(sequences):
|
| 321 |
+
"""Validate input sequences"""
|
| 322 |
+
logger.debug(f"Validating {len(sequences)} sequences")
|
| 323 |
+
errors = []
|
| 324 |
+
|
| 325 |
+
if len(sequences) > 50:
|
| 326 |
+
errors.append("Maximum 50 sequences allowed per submission.")
|
| 327 |
+
|
| 328 |
+
total_length = sum(len(seq) for seq in sequences.values())
|
| 329 |
+
if total_length > 300000:
|
| 330 |
+
errors.append("Total sequence length exceeds 300,000 amino acids.")
|
| 331 |
+
|
| 332 |
+
for name, seq in sequences.items():
|
| 333 |
+
if len(seq) < 10:
|
| 334 |
+
errors.append(f"Sequence '{name}' is too short (minimum 10 amino acids).")
|
| 335 |
+
elif len(seq) > 6000:
|
| 336 |
+
errors.append(f"Sequence '{name}' is too long (maximum 6000 amino acids).")
|
| 337 |
+
|
| 338 |
+
# Check for invalid characters
|
| 339 |
+
valid_aa = set('ACDEFGHIKLMNPQRSTVWY')
|
| 340 |
+
invalid_chars = set(seq.upper()) - valid_aa
|
| 341 |
+
if invalid_chars:
|
| 342 |
+
errors.append(f"Sequence '{name}' contains invalid characters: {', '.join(invalid_chars)}")
|
| 343 |
+
|
| 344 |
+
if errors:
|
| 345 |
+
logger.warning(f"Sequence validation failed with {len(errors)} errors")
|
| 346 |
+
else:
|
| 347 |
+
logger.debug("Sequence validation passed")
|
| 348 |
+
|
| 349 |
+
return errors
|
| 350 |
+
|
| 351 |
+
def download_csv(results_data, job_id):
|
| 352 |
+
"""Generate CSV download"""
|
| 353 |
+
logger.info(f"Generating CSV download for job {job_id}")
|
| 354 |
+
rows = []
|
| 355 |
+
|
| 356 |
+
for seq_name, data in results_data['results'].items():
|
| 357 |
+
# B-cell epitopes
|
| 358 |
+
for epitope, confidence, pos_range in data['b_cell_epitopes']:
|
| 359 |
+
rows.append({
|
| 360 |
+
'Sequence_Name': seq_name,
|
| 361 |
+
'Epitope_Type': 'B-cell',
|
| 362 |
+
'Epitope_Sequence': epitope,
|
| 363 |
+
'Confidence': confidence,
|
| 364 |
+
'Position_Range': pos_range,
|
| 365 |
+
'Full_Sequence': data['sequence']
|
| 366 |
+
})
|
| 367 |
+
|
| 368 |
+
# T-cell epitopes
|
| 369 |
+
for epitope, confidence, pos_range in data['t_cell_epitopes']:
|
| 370 |
+
rows.append({
|
| 371 |
+
'Sequence_Name': seq_name,
|
| 372 |
+
'Epitope_Type': 'T-cell',
|
| 373 |
+
'Epitope_Sequence': epitope,
|
| 374 |
+
'Confidence': confidence,
|
| 375 |
+
'Position_Range': pos_range,
|
| 376 |
+
'Full_Sequence': data['sequence']
|
| 377 |
+
})
|
| 378 |
+
|
| 379 |
+
df = pd.DataFrame(rows)
|
| 380 |
+
logger.info(f"Generated CSV with {len(rows)} epitope predictions")
|
| 381 |
+
|
| 382 |
+
# Create CSV in memory
|
| 383 |
+
output = StringIO()
|
| 384 |
+
df.to_csv(output, index=False)
|
| 385 |
+
output.seek(0)
|
| 386 |
+
|
| 387 |
+
# Convert to bytes
|
| 388 |
+
mem = BytesIO()
|
| 389 |
+
mem.write(output.getvalue().encode('utf-8'))
|
| 390 |
+
mem.seek(0)
|
| 391 |
+
|
| 392 |
+
return send_file(mem,
|
| 393 |
+
as_attachment=True,
|
| 394 |
+
download_name=f'epitope_predictions_{job_id}.csv',
|
| 395 |
+
mimetype='text/csv')
|
| 396 |
+
|
| 397 |
+
def download_json(results_data, job_id):
|
| 398 |
+
"""Generate JSON download"""
|
| 399 |
+
logger.info(f"Generating JSON download for job {job_id}")
|
| 400 |
+
mem = BytesIO()
|
| 401 |
+
mem.write(json.dumps(results_data, indent=2).encode('utf-8'))
|
| 402 |
+
mem.seek(0)
|
| 403 |
+
|
| 404 |
+
return send_file(mem,
|
| 405 |
+
as_attachment=True,
|
| 406 |
+
download_name=f'epitope_predictions_{job_id}.json',
|
| 407 |
+
mimetype='application/json')
|
| 408 |
+
|
| 409 |
+
if __name__ == '__main__':
|
| 410 |
+
# Create upload directory if it doesn't exist
|
| 411 |
+
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
| 412 |
+
logger.info(f"Upload directory created/verified: {app.config['UPLOAD_FOLDER']}")
|
| 413 |
+
|
| 414 |
+
# Log startup information
|
| 415 |
+
logger.info("Starting EpiPred web application")
|
| 416 |
+
logger.info(f"Model status: {'Loaded' if predictor else 'Failed to load'}")
|
| 417 |
+
|
| 418 |
+
if predictor:
|
| 419 |
+
model_info = predictor.get_model_info()
|
| 420 |
+
logger.info(f"Model ready - Window size: {model_info['window_size']}, Threshold: {model_info['confidence_threshold']}")
|
| 421 |
+
|
| 422 |
+
# Run the application
|
| 423 |
+
logger.info("Starting Flask development server on 0.0.0.0:5000")
|
| 424 |
+
app.run(debug=True, host='0.0.0.0', port=5000)
|
app_config.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Configuration settings for EpiPred deployment
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
class Config:
|
| 9 |
+
"""Base configuration"""
|
| 10 |
+
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key-change-in-production'
|
| 11 |
+
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB max file size
|
| 12 |
+
UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER') or 'static/uploads'
|
| 13 |
+
|
| 14 |
+
# Model configuration
|
| 15 |
+
MODEL_PATH = os.environ.get('MODEL_PATH') or 'models'
|
| 16 |
+
CONFIDENCE_THRESHOLD = float(os.environ.get('CONFIDENCE_THRESHOLD', '0.5'))
|
| 17 |
+
|
| 18 |
+
# Performance settings
|
| 19 |
+
MAX_SEQUENCES = int(os.environ.get('MAX_SEQUENCES', '50'))
|
| 20 |
+
MAX_TOTAL_LENGTH = int(os.environ.get('MAX_TOTAL_LENGTH', '300000'))
|
| 21 |
+
MAX_SEQUENCE_LENGTH = int(os.environ.get('MAX_SEQUENCE_LENGTH', '6000'))
|
| 22 |
+
MIN_SEQUENCE_LENGTH = int(os.environ.get('MIN_SEQUENCE_LENGTH', '10'))
|
| 23 |
+
|
| 24 |
+
class DevelopmentConfig(Config):
|
| 25 |
+
"""Development configuration"""
|
| 26 |
+
DEBUG = True
|
| 27 |
+
FLASK_ENV = 'development'
|
| 28 |
+
|
| 29 |
+
class ProductionConfig(Config):
|
| 30 |
+
"""Production configuration"""
|
| 31 |
+
DEBUG = False
|
| 32 |
+
FLASK_ENV = 'production'
|
| 33 |
+
|
| 34 |
+
# Use environment variables for production
|
| 35 |
+
SECRET_KEY = os.environ.get('SECRET_KEY') or os.urandom(32).hex()
|
| 36 |
+
|
| 37 |
+
# Logging configuration
|
| 38 |
+
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')
|
| 39 |
+
|
| 40 |
+
class RenderConfig(ProductionConfig):
|
| 41 |
+
"""Render.com specific configuration"""
|
| 42 |
+
# Render-specific settings
|
| 43 |
+
PORT = int(os.environ.get('PORT', 5000))
|
| 44 |
+
HOST = '0.0.0.0'
|
| 45 |
+
|
| 46 |
+
# Use /tmp for uploads on Render
|
| 47 |
+
UPLOAD_FOLDER = '/tmp/uploads'
|
| 48 |
+
|
| 49 |
+
# Reduced limits for free tier
|
| 50 |
+
MAX_SEQUENCES = 25
|
| 51 |
+
MAX_TOTAL_LENGTH = 150000
|
| 52 |
+
|
| 53 |
+
# Configuration mapping
|
| 54 |
+
config = {
|
| 55 |
+
'development': DevelopmentConfig,
|
| 56 |
+
'production': ProductionConfig,
|
| 57 |
+
'render': RenderConfig,
|
| 58 |
+
'default': DevelopmentConfig
|
| 59 |
+
}
|
app_gradio.py
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
EpiPred - Gradio App for Hugging Face Spaces
|
| 4 |
+
Advanced epitope prediction using deep learning with attention mechanisms
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import gradio as gr
|
| 8 |
+
import pandas as pd
|
| 9 |
+
import numpy as np
|
| 10 |
+
import json
|
| 11 |
+
import tempfile
|
| 12 |
+
import os
|
| 13 |
+
from datetime import datetime
|
| 14 |
+
import logging
|
| 15 |
+
|
| 16 |
+
# Import configuration
|
| 17 |
+
from config_hf import get_config, get_example_sequences, get_custom_css, get_html_template
|
| 18 |
+
|
| 19 |
+
# Set up logging
|
| 20 |
+
logging.basicConfig(level=logging.INFO)
|
| 21 |
+
logger = logging.getLogger(__name__)
|
| 22 |
+
|
| 23 |
+
# Get configuration
|
| 24 |
+
config = get_config()
|
| 25 |
+
MAX_SEQUENCES = config['MAX_SEQUENCES']
|
| 26 |
+
MAX_TOTAL_LENGTH = config['MAX_TOTAL_LENGTH']
|
| 27 |
+
MAX_SEQUENCE_LENGTH = config['MAX_SEQUENCE_LENGTH']
|
| 28 |
+
MIN_SEQUENCE_LENGTH = config['MIN_SEQUENCE_LENGTH']
|
| 29 |
+
|
| 30 |
+
# Import our model predictor
|
| 31 |
+
try:
|
| 32 |
+
from model_predictor import EpitopePredictor
|
| 33 |
+
predictor = EpitopePredictor()
|
| 34 |
+
MODEL_AVAILABLE = predictor.model is not None
|
| 35 |
+
logger.info(f"Model loaded: {MODEL_AVAILABLE}")
|
| 36 |
+
except Exception as e:
|
| 37 |
+
logger.error(f"Failed to load model: {e}")
|
| 38 |
+
predictor = None
|
| 39 |
+
MODEL_AVAILABLE = False
|
| 40 |
+
|
| 41 |
+
def parse_fasta_text(text):
|
| 42 |
+
"""Parse FASTA format text and return dictionary of sequences"""
|
| 43 |
+
sequences = {}
|
| 44 |
+
current_name = None
|
| 45 |
+
current_seq = []
|
| 46 |
+
|
| 47 |
+
for line in text.strip().split('\n'):
|
| 48 |
+
line = line.strip()
|
| 49 |
+
if line.startswith('>'):
|
| 50 |
+
if current_name and current_seq:
|
| 51 |
+
sequences[current_name] = ''.join(current_seq)
|
| 52 |
+
current_name = line[1:].strip() or f"Sequence_{len(sequences)+1}"
|
| 53 |
+
current_seq = []
|
| 54 |
+
elif line and current_name:
|
| 55 |
+
# Remove any non-amino acid characters
|
| 56 |
+
clean_seq = ''.join(c.upper() for c in line if c.upper() in 'ACDEFGHIKLMNPQRSTVWY')
|
| 57 |
+
current_seq.append(clean_seq)
|
| 58 |
+
|
| 59 |
+
if current_name and current_seq:
|
| 60 |
+
sequences[current_name] = ''.join(current_seq)
|
| 61 |
+
|
| 62 |
+
return sequences
|
| 63 |
+
|
| 64 |
+
def validate_sequences(sequences):
|
| 65 |
+
"""Validate input sequences"""
|
| 66 |
+
errors = []
|
| 67 |
+
|
| 68 |
+
if len(sequences) > MAX_SEQUENCES:
|
| 69 |
+
errors.append(f"Maximum {MAX_SEQUENCES} sequences allowed per submission.")
|
| 70 |
+
|
| 71 |
+
total_length = sum(len(seq) for seq in sequences.values())
|
| 72 |
+
if total_length > MAX_TOTAL_LENGTH:
|
| 73 |
+
errors.append(f"Total sequence length exceeds {MAX_TOTAL_LENGTH:,} amino acids.")
|
| 74 |
+
|
| 75 |
+
for name, seq in sequences.items():
|
| 76 |
+
if len(seq) < MIN_SEQUENCE_LENGTH:
|
| 77 |
+
errors.append(f"Sequence '{name}' is too short (minimum {MIN_SEQUENCE_LENGTH} amino acids).")
|
| 78 |
+
elif len(seq) > MAX_SEQUENCE_LENGTH:
|
| 79 |
+
errors.append(f"Sequence '{name}' is too long (maximum {MAX_SEQUENCE_LENGTH:,} amino acids).")
|
| 80 |
+
|
| 81 |
+
# Check for invalid characters
|
| 82 |
+
valid_aa = set('ACDEFGHIKLMNPQRSTVWY')
|
| 83 |
+
invalid_chars = set(seq.upper()) - valid_aa
|
| 84 |
+
if invalid_chars:
|
| 85 |
+
errors.append(f"Sequence '{name}' contains invalid characters: {', '.join(invalid_chars)}")
|
| 86 |
+
|
| 87 |
+
return errors
|
| 88 |
+
|
| 89 |
+
def format_sequence_with_epitopes(sequence, b_epitopes, t_epitopes, threshold=0.5):
|
| 90 |
+
"""Format sequence with epitope markup for display"""
|
| 91 |
+
markup = ['.' for _ in sequence]
|
| 92 |
+
|
| 93 |
+
# Mark B-cell epitopes
|
| 94 |
+
for epitope, confidence, pos_range in b_epitopes:
|
| 95 |
+
if confidence >= threshold:
|
| 96 |
+
try:
|
| 97 |
+
start, end = map(int, pos_range.split('-'))
|
| 98 |
+
start -= 1 # Convert to 0-based indexing
|
| 99 |
+
end -= 1
|
| 100 |
+
for i in range(start, min(end + 1, len(markup))):
|
| 101 |
+
markup[i] = 'B'
|
| 102 |
+
except:
|
| 103 |
+
continue
|
| 104 |
+
|
| 105 |
+
# Mark T-cell epitopes (may override B-cell)
|
| 106 |
+
for epitope, confidence, pos_range in t_epitopes:
|
| 107 |
+
if confidence >= threshold:
|
| 108 |
+
try:
|
| 109 |
+
start, end = map(int, pos_range.split('-'))
|
| 110 |
+
start -= 1 # Convert to 0-based indexing
|
| 111 |
+
end -= 1
|
| 112 |
+
for i in range(start, min(end + 1, len(markup))):
|
| 113 |
+
markup[i] = 'T'
|
| 114 |
+
except:
|
| 115 |
+
continue
|
| 116 |
+
|
| 117 |
+
# Create formatted output
|
| 118 |
+
formatted_lines = []
|
| 119 |
+
line_length = 60
|
| 120 |
+
|
| 121 |
+
for i in range(0, len(sequence), line_length):
|
| 122 |
+
seq_line = sequence[i:i+line_length]
|
| 123 |
+
mark_line = ''.join(markup[i:i+line_length])
|
| 124 |
+
|
| 125 |
+
formatted_lines.append(f"{i+1:>6}: {seq_line}")
|
| 126 |
+
formatted_lines.append(f"{'':>6} {mark_line}")
|
| 127 |
+
formatted_lines.append("")
|
| 128 |
+
|
| 129 |
+
return '\n'.join(formatted_lines)
|
| 130 |
+
|
| 131 |
+
def predict_epitopes(sequence_input, file_input, confidence_threshold, progress=gr.Progress()):
|
| 132 |
+
"""Main prediction function"""
|
| 133 |
+
|
| 134 |
+
if not MODEL_AVAILABLE:
|
| 135 |
+
return (
|
| 136 |
+
"⚠️ **Model not available - Running in demo mode**\n\nThis is a demonstration of the interface. In the full version, advanced deep learning models would analyze your sequences.",
|
| 137 |
+
pd.DataFrame(),
|
| 138 |
+
pd.DataFrame(),
|
| 139 |
+
None,
|
| 140 |
+
None
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
progress(0.1, desc="Processing input...")
|
| 144 |
+
|
| 145 |
+
# Get sequences from input
|
| 146 |
+
sequences = {}
|
| 147 |
+
|
| 148 |
+
# Process text input
|
| 149 |
+
if sequence_input and sequence_input.strip():
|
| 150 |
+
try:
|
| 151 |
+
sequences.update(parse_fasta_text(sequence_input))
|
| 152 |
+
except Exception as e:
|
| 153 |
+
return f"❌ Error parsing sequence text: {str(e)}", pd.DataFrame(), pd.DataFrame(), None, None
|
| 154 |
+
|
| 155 |
+
# Process file input
|
| 156 |
+
if file_input is not None:
|
| 157 |
+
try:
|
| 158 |
+
with open(file_input.name, 'r') as f:
|
| 159 |
+
file_content = f.read()
|
| 160 |
+
sequences.update(parse_fasta_text(file_content))
|
| 161 |
+
except Exception as e:
|
| 162 |
+
return f"❌ Error reading file: {str(e)}", pd.DataFrame(), pd.DataFrame(), None, None
|
| 163 |
+
|
| 164 |
+
if not sequences:
|
| 165 |
+
return "❌ Please provide protein sequences in FASTA format.", pd.DataFrame(), pd.DataFrame(), None, None
|
| 166 |
+
|
| 167 |
+
progress(0.2, desc="Validating sequences...")
|
| 168 |
+
|
| 169 |
+
# Validate sequences
|
| 170 |
+
validation_errors = validate_sequences(sequences)
|
| 171 |
+
if validation_errors:
|
| 172 |
+
error_msg = "❌ **Validation Errors:**\n\n" + "\n".join(f"• {error}" for error in validation_errors)
|
| 173 |
+
return error_msg, pd.DataFrame(), pd.DataFrame(), None, None
|
| 174 |
+
|
| 175 |
+
progress(0.3, desc="Running predictions...")
|
| 176 |
+
|
| 177 |
+
# Perform predictions
|
| 178 |
+
all_results = {}
|
| 179 |
+
b_cell_data = []
|
| 180 |
+
t_cell_data = []
|
| 181 |
+
formatted_sequences = []
|
| 182 |
+
|
| 183 |
+
for i, (seq_name, seq) in enumerate(sequences.items()):
|
| 184 |
+
progress(0.3 + 0.6 * (i / len(sequences)), desc=f"Analyzing {seq_name}...")
|
| 185 |
+
|
| 186 |
+
try:
|
| 187 |
+
b_cell_epitopes, t_cell_epitopes = predictor.predict_epitopes(seq, confidence_threshold)
|
| 188 |
+
|
| 189 |
+
all_results[seq_name] = {
|
| 190 |
+
'sequence': seq,
|
| 191 |
+
'b_cell_epitopes': b_cell_epitopes,
|
| 192 |
+
't_cell_epitopes': t_cell_epitopes,
|
| 193 |
+
'length': len(seq)
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
# Add to data tables
|
| 197 |
+
for epitope, confidence, pos_range in b_cell_epitopes:
|
| 198 |
+
if confidence >= confidence_threshold:
|
| 199 |
+
b_cell_data.append({
|
| 200 |
+
'Sequence': seq_name,
|
| 201 |
+
'Epitope': epitope,
|
| 202 |
+
'Position': pos_range,
|
| 203 |
+
'Confidence': f"{confidence:.3f}",
|
| 204 |
+
'Length': len(epitope)
|
| 205 |
+
})
|
| 206 |
+
|
| 207 |
+
for epitope, confidence, pos_range in t_cell_epitopes:
|
| 208 |
+
if confidence >= confidence_threshold:
|
| 209 |
+
t_cell_data.append({
|
| 210 |
+
'Sequence': seq_name,
|
| 211 |
+
'Epitope': epitope,
|
| 212 |
+
'Position': pos_range,
|
| 213 |
+
'Confidence': f"{confidence:.3f}",
|
| 214 |
+
'Length': len(epitope)
|
| 215 |
+
})
|
| 216 |
+
|
| 217 |
+
# Format sequence with epitopes
|
| 218 |
+
formatted_seq = format_sequence_with_epitopes(seq, b_cell_epitopes, t_cell_epitopes, confidence_threshold)
|
| 219 |
+
formatted_sequences.append(f"**{seq_name}** (Length: {len(seq)} aa)\n\n```\n{formatted_seq}\n```\n")
|
| 220 |
+
|
| 221 |
+
except Exception as e:
|
| 222 |
+
logger.error(f"Prediction failed for {seq_name}: {e}")
|
| 223 |
+
continue
|
| 224 |
+
|
| 225 |
+
progress(0.9, desc="Formatting results...")
|
| 226 |
+
|
| 227 |
+
# Create summary
|
| 228 |
+
total_b_epitopes = len(b_cell_data)
|
| 229 |
+
total_t_epitopes = len(t_cell_data)
|
| 230 |
+
|
| 231 |
+
summary = f"""
|
| 232 |
+
## 🎉 Prediction Complete!
|
| 233 |
+
|
| 234 |
+
**Summary:**
|
| 235 |
+
- **Sequences analyzed:** {len(sequences)}
|
| 236 |
+
- **B-cell epitopes found:** {total_b_epitopes}
|
| 237 |
+
- **T-cell epitopes found:** {total_t_epitopes}
|
| 238 |
+
- **Confidence threshold:** {confidence_threshold:.2f}
|
| 239 |
+
- **Analysis date:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 240 |
+
|
| 241 |
+
**Legend:**
|
| 242 |
+
- `B` = B-cell epitope regions
|
| 243 |
+
- `T` = T-cell epitope regions
|
| 244 |
+
- `.` = Non-epitope regions
|
| 245 |
+
|
| 246 |
+
---
|
| 247 |
+
|
| 248 |
+
### Sequence Analysis:
|
| 249 |
+
|
| 250 |
+
""" + "\n\n".join(formatted_sequences)
|
| 251 |
+
|
| 252 |
+
# Create DataFrames
|
| 253 |
+
b_cell_df = pd.DataFrame(b_cell_data) if b_cell_data else pd.DataFrame(columns=['Sequence', 'Epitope', 'Position', 'Confidence', 'Length'])
|
| 254 |
+
t_cell_df = pd.DataFrame(t_cell_data) if t_cell_data else pd.DataFrame(columns=['Sequence', 'Epitope', 'Position', 'Confidence', 'Length'])
|
| 255 |
+
|
| 256 |
+
# Create download files
|
| 257 |
+
csv_file = None
|
| 258 |
+
json_file = None
|
| 259 |
+
|
| 260 |
+
if all_results:
|
| 261 |
+
# CSV file
|
| 262 |
+
csv_data = []
|
| 263 |
+
for seq_name, data in all_results.items():
|
| 264 |
+
for epitope, confidence, pos_range in data['b_cell_epitopes']:
|
| 265 |
+
if confidence >= confidence_threshold:
|
| 266 |
+
csv_data.append({
|
| 267 |
+
'Sequence_Name': seq_name,
|
| 268 |
+
'Epitope_Type': 'B-cell',
|
| 269 |
+
'Epitope_Sequence': epitope,
|
| 270 |
+
'Confidence': confidence,
|
| 271 |
+
'Position_Range': pos_range,
|
| 272 |
+
'Full_Sequence': data['sequence']
|
| 273 |
+
})
|
| 274 |
+
for epitope, confidence, pos_range in data['t_cell_epitopes']:
|
| 275 |
+
if confidence >= confidence_threshold:
|
| 276 |
+
csv_data.append({
|
| 277 |
+
'Sequence_Name': seq_name,
|
| 278 |
+
'Epitope_Type': 'T-cell',
|
| 279 |
+
'Epitope_Sequence': epitope,
|
| 280 |
+
'Confidence': confidence,
|
| 281 |
+
'Position_Range': pos_range,
|
| 282 |
+
'Full_Sequence': data['sequence']
|
| 283 |
+
})
|
| 284 |
+
|
| 285 |
+
if csv_data:
|
| 286 |
+
csv_df = pd.DataFrame(csv_data)
|
| 287 |
+
csv_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False)
|
| 288 |
+
csv_df.to_csv(csv_file.name, index=False)
|
| 289 |
+
csv_file.close()
|
| 290 |
+
|
| 291 |
+
# JSON file
|
| 292 |
+
json_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
|
| 293 |
+
json.dump({
|
| 294 |
+
'timestamp': datetime.now().isoformat(),
|
| 295 |
+
'confidence_threshold': confidence_threshold,
|
| 296 |
+
'results': all_results,
|
| 297 |
+
'summary': {
|
| 298 |
+
'total_sequences': len(sequences),
|
| 299 |
+
'total_b_epitopes': total_b_epitopes,
|
| 300 |
+
'total_t_epitopes': total_t_epitopes
|
| 301 |
+
}
|
| 302 |
+
}, json_file, indent=2)
|
| 303 |
+
json_file.close()
|
| 304 |
+
|
| 305 |
+
progress(1.0, desc="Complete!")
|
| 306 |
+
|
| 307 |
+
return summary, b_cell_df, t_cell_df, csv_file.name if csv_file else None, json_file.name if json_file else None
|
| 308 |
+
|
| 309 |
+
# Get example sequences from config
|
| 310 |
+
EXAMPLE_SEQUENCES = get_example_sequences('extended')
|
| 311 |
+
|
| 312 |
+
# Create Gradio interface
|
| 313 |
+
def create_interface():
|
| 314 |
+
with gr.Blocks(
|
| 315 |
+
title="EpiPred - Epitope Prediction Tool",
|
| 316 |
+
theme=gr.themes.Soft(),
|
| 317 |
+
css=get_custom_css()
|
| 318 |
+
) as demo:
|
| 319 |
+
|
| 320 |
+
gr.HTML(get_html_template('header'))
|
| 321 |
+
|
| 322 |
+
with gr.Row():
|
| 323 |
+
with gr.Column(scale=2):
|
| 324 |
+
gr.HTML("""
|
| 325 |
+
<div class="info-box">
|
| 326 |
+
<h3>📋 Instructions</h3>
|
| 327 |
+
<ul>
|
| 328 |
+
<li><strong>Input:</strong> Paste FASTA sequences or upload a file</li>
|
| 329 |
+
<li><strong>Format:</strong> Standard amino acid sequences (A-Z)</li>
|
| 330 |
+
<li><strong>Limits:</strong> Max 25 sequences, 150K total amino acids</li>
|
| 331 |
+
<li><strong>Output:</strong> B-cell and T-cell epitope predictions</li>
|
| 332 |
+
</ul>
|
| 333 |
+
</div>
|
| 334 |
+
""")
|
| 335 |
+
|
| 336 |
+
sequence_input = gr.Textbox(
|
| 337 |
+
label="📝 Paste FASTA Sequences",
|
| 338 |
+
placeholder="Paste your protein sequences in FASTA format here...",
|
| 339 |
+
lines=8,
|
| 340 |
+
value=EXAMPLE_SEQUENCES
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
file_input = gr.File(
|
| 344 |
+
label="📁 Or Upload FASTA File",
|
| 345 |
+
file_types=[".txt", ".fasta", ".fa", ".fas"]
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
confidence_threshold = gr.Slider(
|
| 349 |
+
minimum=0.0,
|
| 350 |
+
maximum=1.0,
|
| 351 |
+
value=0.5,
|
| 352 |
+
step=0.01,
|
| 353 |
+
label="🎯 Confidence Threshold",
|
| 354 |
+
info="Minimum confidence score for epitope predictions"
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
predict_btn = gr.Button("🚀 Predict Epitopes", variant="primary", size="lg")
|
| 358 |
+
|
| 359 |
+
with gr.Column(scale=1):
|
| 360 |
+
gr.HTML("""
|
| 361 |
+
<div class="info-box">
|
| 362 |
+
<h3>ℹ️ About</h3>
|
| 363 |
+
<p><strong>Method:</strong> Deep learning with attention mechanisms</p>
|
| 364 |
+
<p><strong>Architecture:</strong> Bidirectional LSTM + Convolutional layers</p>
|
| 365 |
+
<p><strong>Training:</strong> Experimentally validated epitopes</p>
|
| 366 |
+
<p><strong>Output:</strong> B-cell and T-cell epitope predictions with confidence scores</p>
|
| 367 |
+
</div>
|
| 368 |
+
""")
|
| 369 |
+
|
| 370 |
+
gr.HTML("""
|
| 371 |
+
<div class="info-box">
|
| 372 |
+
<h3>📊 Results Legend</h3>
|
| 373 |
+
<p><code>B</code> = B-cell epitope regions</p>
|
| 374 |
+
<p><code>T</code> = T-cell epitope regions</p>
|
| 375 |
+
<p><code>.</code> = Non-epitope regions</p>
|
| 376 |
+
</div>
|
| 377 |
+
""")
|
| 378 |
+
|
| 379 |
+
# Results section
|
| 380 |
+
with gr.Row():
|
| 381 |
+
results_display = gr.Markdown(label="📊 Results")
|
| 382 |
+
|
| 383 |
+
with gr.Row():
|
| 384 |
+
with gr.Column():
|
| 385 |
+
b_cell_table = gr.DataFrame(
|
| 386 |
+
label="🔴 B-cell Epitopes",
|
| 387 |
+
headers=['Sequence', 'Epitope', 'Position', 'Confidence', 'Length']
|
| 388 |
+
)
|
| 389 |
+
with gr.Column():
|
| 390 |
+
t_cell_table = gr.DataFrame(
|
| 391 |
+
label="🔵 T-cell Epitopes",
|
| 392 |
+
headers=['Sequence', 'Epitope', 'Position', 'Confidence', 'Length']
|
| 393 |
+
)
|
| 394 |
+
|
| 395 |
+
with gr.Row():
|
| 396 |
+
csv_download = gr.File(label="📥 Download CSV Results", visible=False)
|
| 397 |
+
json_download = gr.File(label="📥 Download JSON Results", visible=False)
|
| 398 |
+
|
| 399 |
+
# Event handlers
|
| 400 |
+
predict_btn.click(
|
| 401 |
+
fn=predict_epitopes,
|
| 402 |
+
inputs=[sequence_input, file_input, confidence_threshold],
|
| 403 |
+
outputs=[results_display, b_cell_table, t_cell_table, csv_download, json_download],
|
| 404 |
+
show_progress=True
|
| 405 |
+
).then(
|
| 406 |
+
lambda csv, json_data: (gr.update(visible=csv is not None), gr.update(visible=json_data is not None)),
|
| 407 |
+
inputs=[csv_download, json_download],
|
| 408 |
+
outputs=[csv_download, json_download]
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
# Footer
|
| 412 |
+
gr.HTML("""
|
| 413 |
+
<div style="text-align: center; margin-top: 2rem; padding: 1rem; border-top: 1px solid #eee;">
|
| 414 |
+
<p>🤗 <strong>Powered by Hugging Face Spaces</strong></p>
|
| 415 |
+
<p>For questions or support, please visit the <a href="https://huggingface.co/spaces/mercelv/epipred" target="_blank">Space repository</a></p>
|
| 416 |
+
</div>
|
| 417 |
+
""")
|
| 418 |
+
|
| 419 |
+
return demo
|
| 420 |
+
|
| 421 |
+
# Launch the app
|
| 422 |
+
if __name__ == "__main__":
|
| 423 |
+
demo = create_interface()
|
| 424 |
+
demo.launch()
|
config_hf.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Configuration for Hugging Face Spaces deployment
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
# Hugging Face Spaces configuration
|
| 9 |
+
HF_SPACES_CONFIG = {
|
| 10 |
+
# Resource limits for free tier
|
| 11 |
+
'MAX_SEQUENCES': 25,
|
| 12 |
+
'MAX_TOTAL_LENGTH': 150000,
|
| 13 |
+
'MAX_SEQUENCE_LENGTH': 3000,
|
| 14 |
+
'MIN_SEQUENCE_LENGTH': 10,
|
| 15 |
+
'MAX_FILE_SIZE': 10 * 1024 * 1024, # 10MB
|
| 16 |
+
|
| 17 |
+
# Model configuration
|
| 18 |
+
'DEFAULT_CONFIDENCE_THRESHOLD': 0.5,
|
| 19 |
+
'MIN_CONFIDENCE_THRESHOLD': 0.0,
|
| 20 |
+
'MAX_CONFIDENCE_THRESHOLD': 1.0,
|
| 21 |
+
|
| 22 |
+
# UI configuration
|
| 23 |
+
'THEME': 'soft',
|
| 24 |
+
'MAX_WIDTH': '1200px',
|
| 25 |
+
'ENABLE_QUEUE': True,
|
| 26 |
+
'SHOW_ERROR': True,
|
| 27 |
+
'SHOW_PROGRESS': True,
|
| 28 |
+
|
| 29 |
+
# Performance settings
|
| 30 |
+
'BATCH_SIZE': 32,
|
| 31 |
+
'MAX_WORKERS': 1,
|
| 32 |
+
'TIMEOUT': 300, # 5 minutes
|
| 33 |
+
|
| 34 |
+
# Demo mode settings
|
| 35 |
+
'DEMO_MODE': os.environ.get('DEMO_MODE', 'false').lower() == 'true',
|
| 36 |
+
'DEMO_SEQUENCES': 3,
|
| 37 |
+
'DEMO_EPITOPES_PER_SEQUENCE': 2,
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
# Example sequences for the interface
|
| 41 |
+
EXAMPLE_SEQUENCES = {
|
| 42 |
+
'basic': """>Human_Insulin_A_Chain
|
| 43 |
+
GIVEQCCTSICSLYQLENYCN
|
| 44 |
+
|
| 45 |
+
>Human_Insulin_B_Chain
|
| 46 |
+
FVNQHLCGSHLVEALYLVCGERGFFYTPKT""",
|
| 47 |
+
|
| 48 |
+
'extended': """>Human_Insulin_A_Chain
|
| 49 |
+
GIVEQCCTSICSLYQLENYCN
|
| 50 |
+
|
| 51 |
+
>Human_Insulin_B_Chain
|
| 52 |
+
FVNQHLCGSHLVEALYLVCGERGFFYTPKT
|
| 53 |
+
|
| 54 |
+
>SARS_CoV2_Spike_Fragment
|
| 55 |
+
MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL
|
| 56 |
+
|
| 57 |
+
>HIV_gp120_Fragment
|
| 58 |
+
AVNQVVDLMAHMASKDNRAHGDNKFRNRNQRKQRNSTRHIQLGLPAAMADVLFVL""",
|
| 59 |
+
|
| 60 |
+
'comprehensive': """>Human_Insulin_A_Chain
|
| 61 |
+
GIVEQCCTSICSLYQLENYCN
|
| 62 |
+
|
| 63 |
+
>Human_Insulin_B_Chain
|
| 64 |
+
FVNQHLCGSHLVEALYLVCGERGFFYTPKT
|
| 65 |
+
|
| 66 |
+
>SARS_CoV2_Spike_Fragment
|
| 67 |
+
MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL
|
| 68 |
+
CARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD
|
| 69 |
+
|
| 70 |
+
>HIV_gp120_Fragment
|
| 71 |
+
AVNQVVDLMAHMASKDNRAHGDNKFRNRNQRKQRNSTRHIQLGLPAAMADVLFVL
|
| 72 |
+
FGAATGFHSGGIASYNFPKATLQSQVKELQAAQARLGADMEDVCGRLVQRGNQVA
|
| 73 |
+
|
| 74 |
+
>Influenza_Hemagglutinin_Fragment
|
| 75 |
+
YYRGISALQLEECDFFPQHIQVQVQLEQVVGVVKPLLVQVQHQDYLAAVSVAQL
|
| 76 |
+
SRRELEKASREAIIAPMGLAVPEWLSFQRGDVVAITLPKNAGDGTFIDVQCHTT"""
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
# CSS styling for Gradio interface
|
| 80 |
+
CUSTOM_CSS = """
|
| 81 |
+
.gradio-container {
|
| 82 |
+
max-width: 1200px !important;
|
| 83 |
+
margin: 0 auto !important;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.main-header {
|
| 87 |
+
text-align: center;
|
| 88 |
+
margin-bottom: 2rem;
|
| 89 |
+
padding: 2rem;
|
| 90 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 91 |
+
color: white;
|
| 92 |
+
border-radius: 15px;
|
| 93 |
+
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.info-box {
|
| 97 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 98 |
+
color: white;
|
| 99 |
+
padding: 1.5rem;
|
| 100 |
+
border-radius: 10px;
|
| 101 |
+
margin: 1rem 0;
|
| 102 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
.info-box h3 {
|
| 106 |
+
margin-top: 0;
|
| 107 |
+
color: #fff;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
.info-box ul {
|
| 111 |
+
margin-bottom: 0;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.info-box li {
|
| 115 |
+
margin-bottom: 0.5rem;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.info-box code {
|
| 119 |
+
background: rgba(255,255,255,0.2);
|
| 120 |
+
padding: 2px 6px;
|
| 121 |
+
border-radius: 4px;
|
| 122 |
+
font-family: 'Courier New', monospace;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.results-container {
|
| 126 |
+
margin-top: 2rem;
|
| 127 |
+
padding: 1rem;
|
| 128 |
+
border: 2px solid #e1e5e9;
|
| 129 |
+
border-radius: 10px;
|
| 130 |
+
background: #f8f9fa;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.epitope-sequence {
|
| 134 |
+
font-family: 'Courier New', monospace;
|
| 135 |
+
font-size: 14px;
|
| 136 |
+
line-height: 1.6;
|
| 137 |
+
background: #f8f9fa;
|
| 138 |
+
padding: 1rem;
|
| 139 |
+
border-radius: 8px;
|
| 140 |
+
border: 1px solid #dee2e6;
|
| 141 |
+
white-space: pre-wrap;
|
| 142 |
+
overflow-x: auto;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.footer {
|
| 146 |
+
text-align: center;
|
| 147 |
+
margin-top: 3rem;
|
| 148 |
+
padding: 2rem;
|
| 149 |
+
border-top: 1px solid #eee;
|
| 150 |
+
background: #f8f9fa;
|
| 151 |
+
border-radius: 10px;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.prediction-button {
|
| 155 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
| 156 |
+
border: none !important;
|
| 157 |
+
color: white !important;
|
| 158 |
+
font-weight: bold !important;
|
| 159 |
+
padding: 12px 24px !important;
|
| 160 |
+
border-radius: 8px !important;
|
| 161 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.2) !important;
|
| 162 |
+
transition: all 0.3s ease !important;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.prediction-button:hover {
|
| 166 |
+
transform: translateY(-2px) !important;
|
| 167 |
+
box-shadow: 0 6px 20px rgba(0,0,0,0.3) !important;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
.confidence-slider {
|
| 171 |
+
background: linear-gradient(90deg, #dc3545 0%, #ffc107 50%, #28a745 100%) !important;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.download-section {
|
| 175 |
+
margin-top: 1rem;
|
| 176 |
+
padding: 1rem;
|
| 177 |
+
background: #e8f5e8;
|
| 178 |
+
border-radius: 8px;
|
| 179 |
+
border: 1px solid #c3e6c3;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
@media (max-width: 768px) {
|
| 183 |
+
.gradio-container {
|
| 184 |
+
padding: 1rem !important;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
.main-header {
|
| 188 |
+
padding: 1rem !important;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
.info-box {
|
| 192 |
+
padding: 1rem !important;
|
| 193 |
+
}
|
| 194 |
+
}
|
| 195 |
+
"""
|
| 196 |
+
|
| 197 |
+
# HTML templates
|
| 198 |
+
HTML_TEMPLATES = {
|
| 199 |
+
'header': """
|
| 200 |
+
<div class="main-header">
|
| 201 |
+
<h1>🧬 EpiPred - Epitope Prediction Tool</h1>
|
| 202 |
+
<p style="font-size: 1.2rem; margin: 0;">Advanced epitope prediction using deep learning with attention mechanisms and bidirectional LSTM networks</p>
|
| 203 |
+
<p style="font-size: 1rem; margin-top: 1rem; opacity: 0.9;">Predict B-cell and T-cell epitopes from protein sequences with high accuracy</p>
|
| 204 |
+
</div>
|
| 205 |
+
""",
|
| 206 |
+
|
| 207 |
+
'instructions': """
|
| 208 |
+
<div class="info-box">
|
| 209 |
+
<h3>📋 How to Use</h3>
|
| 210 |
+
<ul>
|
| 211 |
+
<li><strong>Input:</strong> Paste FASTA sequences or upload a file</li>
|
| 212 |
+
<li><strong>Format:</strong> Standard amino acid sequences (A-Z)</li>
|
| 213 |
+
<li><strong>Limits:</strong> Max 25 sequences, 150K total amino acids</li>
|
| 214 |
+
<li><strong>Threshold:</strong> Adjust confidence threshold (0.0-1.0)</li>
|
| 215 |
+
<li><strong>Output:</strong> B-cell and T-cell epitope predictions with confidence scores</li>
|
| 216 |
+
</ul>
|
| 217 |
+
</div>
|
| 218 |
+
""",
|
| 219 |
+
|
| 220 |
+
'about': """
|
| 221 |
+
<div class="info-box">
|
| 222 |
+
<h3>ℹ️ About the Method</h3>
|
| 223 |
+
<p><strong>Architecture:</strong> Deep learning with attention mechanisms</p>
|
| 224 |
+
<p><strong>Model:</strong> Bidirectional LSTM + Convolutional layers</p>
|
| 225 |
+
<p><strong>Training:</strong> Experimentally validated epitopes</p>
|
| 226 |
+
<p><strong>Performance:</strong> High accuracy in cross-validation</p>
|
| 227 |
+
<p><strong>Output:</strong> B-cell and T-cell epitope predictions</p>
|
| 228 |
+
</div>
|
| 229 |
+
""",
|
| 230 |
+
|
| 231 |
+
'legend': """
|
| 232 |
+
<div class="info-box">
|
| 233 |
+
<h3>📊 Results Legend</h3>
|
| 234 |
+
<p><code>B</code> = B-cell epitope regions (red)</p>
|
| 235 |
+
<p><code>T</code> = T-cell epitope regions (blue)</p>
|
| 236 |
+
<p><code>.</code> = Non-epitope regions (gray)</p>
|
| 237 |
+
<p><strong>Position:</strong> 1-based amino acid positions</p>
|
| 238 |
+
<p><strong>Confidence:</strong> Prediction confidence (0.0-1.0)</p>
|
| 239 |
+
</div>
|
| 240 |
+
""",
|
| 241 |
+
|
| 242 |
+
'footer': """
|
| 243 |
+
<div class="footer">
|
| 244 |
+
<h3>🤗 Powered by Hugging Face Spaces</h3>
|
| 245 |
+
<p>For questions, support, or to report issues, please visit the
|
| 246 |
+
<a href="https://huggingface.co/spaces/mercelv/epipred" target="_blank">Space repository</a></p>
|
| 247 |
+
<p><strong>Citation:</strong> EpiPred: Advanced Epitope Prediction Using Attention-based Deep Learning (2024)</p>
|
| 248 |
+
<p style="margin-top: 1rem; font-size: 0.9rem; opacity: 0.7;">
|
| 249 |
+
This tool is for research purposes. Please validate predictions experimentally.
|
| 250 |
+
</p>
|
| 251 |
+
</div>
|
| 252 |
+
"""
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
def get_config():
|
| 256 |
+
"""Get configuration for the current environment"""
|
| 257 |
+
return HF_SPACES_CONFIG
|
| 258 |
+
|
| 259 |
+
def get_example_sequences(level='basic'):
|
| 260 |
+
"""Get example sequences for the interface"""
|
| 261 |
+
return EXAMPLE_SEQUENCES.get(level, EXAMPLE_SEQUENCES['basic'])
|
| 262 |
+
|
| 263 |
+
def get_custom_css():
|
| 264 |
+
"""Get custom CSS for the interface"""
|
| 265 |
+
return CUSTOM_CSS
|
| 266 |
+
|
| 267 |
+
def get_html_template(template_name):
|
| 268 |
+
"""Get HTML template by name"""
|
| 269 |
+
return HTML_TEMPLATES.get(template_name, "")
|
deploy_hf.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Deployment script for Hugging Face Spaces
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import shutil
|
| 8 |
+
import subprocess
|
| 9 |
+
import sys
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
def check_requirements():
|
| 13 |
+
"""Check if required files exist"""
|
| 14 |
+
required_files = [
|
| 15 |
+
'app_gradio.py',
|
| 16 |
+
'model_predictor.py',
|
| 17 |
+
'config_hf.py',
|
| 18 |
+
'requirements.txt',
|
| 19 |
+
'README.md'
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
missing_files = []
|
| 23 |
+
for file in required_files:
|
| 24 |
+
if not os.path.exists(file):
|
| 25 |
+
missing_files.append(file)
|
| 26 |
+
|
| 27 |
+
if missing_files:
|
| 28 |
+
print(f"❌ Missing required files: {', '.join(missing_files)}")
|
| 29 |
+
return False
|
| 30 |
+
|
| 31 |
+
print("✅ All required files present")
|
| 32 |
+
return True
|
| 33 |
+
|
| 34 |
+
def check_model_files():
|
| 35 |
+
"""Check if model files exist"""
|
| 36 |
+
model_paths = [
|
| 37 |
+
'models/epitope_model.keras',
|
| 38 |
+
'models/epitope_model.h5',
|
| 39 |
+
'models/epitope_model_savedmodel'
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
model_found = False
|
| 43 |
+
for path in model_paths:
|
| 44 |
+
if os.path.exists(path):
|
| 45 |
+
print(f"✅ Found model: {path}")
|
| 46 |
+
model_found = True
|
| 47 |
+
break
|
| 48 |
+
|
| 49 |
+
if not model_found:
|
| 50 |
+
print("⚠️ No model files found - app will run in demo mode")
|
| 51 |
+
print("Expected locations:")
|
| 52 |
+
for path in model_paths:
|
| 53 |
+
print(f" - {path}")
|
| 54 |
+
|
| 55 |
+
return True # Not critical for deployment
|
| 56 |
+
|
| 57 |
+
def prepare_deployment():
|
| 58 |
+
"""Prepare files for deployment"""
|
| 59 |
+
print("📦 Preparing deployment files...")
|
| 60 |
+
|
| 61 |
+
# Create deployment directory
|
| 62 |
+
deploy_dir = Path("hf_deploy")
|
| 63 |
+
if deploy_dir.exists():
|
| 64 |
+
shutil.rmtree(deploy_dir)
|
| 65 |
+
deploy_dir.mkdir()
|
| 66 |
+
|
| 67 |
+
# Copy essential files
|
| 68 |
+
files_to_copy = [
|
| 69 |
+
'app_gradio.py',
|
| 70 |
+
'model_predictor.py',
|
| 71 |
+
'config_hf.py',
|
| 72 |
+
'requirements.txt',
|
| 73 |
+
'README.md',
|
| 74 |
+
'.gitignore'
|
| 75 |
+
]
|
| 76 |
+
|
| 77 |
+
for file in files_to_copy:
|
| 78 |
+
if os.path.exists(file):
|
| 79 |
+
shutil.copy2(file, deploy_dir / file)
|
| 80 |
+
print(f"✅ Copied {file}")
|
| 81 |
+
|
| 82 |
+
# Copy model files if they exist
|
| 83 |
+
models_dir = Path("models")
|
| 84 |
+
if models_dir.exists():
|
| 85 |
+
deploy_models_dir = deploy_dir / "models"
|
| 86 |
+
shutil.copytree(models_dir, deploy_models_dir)
|
| 87 |
+
print("✅ Copied models directory")
|
| 88 |
+
|
| 89 |
+
# Copy sample files
|
| 90 |
+
sample_files = ['test_sequences.fasta', 'sample_input.fasta']
|
| 91 |
+
for file in sample_files:
|
| 92 |
+
if os.path.exists(file):
|
| 93 |
+
shutil.copy2(file, deploy_dir / file)
|
| 94 |
+
print(f"✅ Copied {file}")
|
| 95 |
+
|
| 96 |
+
print(f"📦 Deployment files prepared in {deploy_dir}")
|
| 97 |
+
return deploy_dir
|
| 98 |
+
|
| 99 |
+
def test_gradio_app(deploy_dir):
|
| 100 |
+
"""Test the Gradio app"""
|
| 101 |
+
print("🧪 Testing Gradio app...")
|
| 102 |
+
|
| 103 |
+
original_dir = os.getcwd()
|
| 104 |
+
try:
|
| 105 |
+
os.chdir(deploy_dir)
|
| 106 |
+
|
| 107 |
+
# Test import
|
| 108 |
+
result = subprocess.run([
|
| 109 |
+
sys.executable, '-c',
|
| 110 |
+
'import app_gradio; print("✅ Gradio app imports successfully")'
|
| 111 |
+
], capture_output=True, text=True)
|
| 112 |
+
|
| 113 |
+
if result.returncode == 0:
|
| 114 |
+
print("✅ Gradio app test passed")
|
| 115 |
+
return True
|
| 116 |
+
else:
|
| 117 |
+
print(f"❌ Gradio app test failed: {result.stderr}")
|
| 118 |
+
return False
|
| 119 |
+
|
| 120 |
+
finally:
|
| 121 |
+
os.chdir(original_dir)
|
| 122 |
+
|
| 123 |
+
def create_space_instructions(deploy_dir):
|
| 124 |
+
"""Create instructions for Hugging Face Spaces"""
|
| 125 |
+
instructions = """
|
| 126 |
+
# 🚀 Hugging Face Spaces Deployment Instructions
|
| 127 |
+
|
| 128 |
+
## Files Ready for Deployment
|
| 129 |
+
|
| 130 |
+
Your EpiPred app is now ready for Hugging Face Spaces! Here's what to do:
|
| 131 |
+
|
| 132 |
+
### 1. Create a New Space
|
| 133 |
+
|
| 134 |
+
1. Go to [Hugging Face Spaces](https://huggingface.co/spaces)
|
| 135 |
+
2. Click "Create new Space"
|
| 136 |
+
3. Fill in the details:
|
| 137 |
+
- **Space name**: `epipred` (or your preferred name)
|
| 138 |
+
- **License**: MIT
|
| 139 |
+
- **SDK**: Gradio
|
| 140 |
+
- **Hardware**: CPU Basic (free tier)
|
| 141 |
+
|
| 142 |
+
### 2. Upload Files
|
| 143 |
+
|
| 144 |
+
Upload all files from this `hf_deploy` directory to your Space:
|
| 145 |
+
|
| 146 |
+
```
|
| 147 |
+
hf_deploy/
|
| 148 |
+
├── app_gradio.py # Main Gradio application
|
| 149 |
+
├── model_predictor.py # Model loading and prediction
|
| 150 |
+
├── config_hf.py # Configuration for HF Spaces
|
| 151 |
+
├── requirements.txt # Python dependencies
|
| 152 |
+
├── README.md # Space description with metadata
|
| 153 |
+
├── .gitignore # Git ignore file
|
| 154 |
+
├── models/ # Model files (if available)
|
| 155 |
+
├── test_sequences.fasta # Sample input files
|
| 156 |
+
└── sample_input.fasta
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
### 3. Key Files Explained
|
| 160 |
+
|
| 161 |
+
- **`README.md`**: Contains the Space metadata (title, emoji, SDK, etc.)
|
| 162 |
+
- **`app_gradio.py`**: Main application file (specified in README.md as `app_file`)
|
| 163 |
+
- **`requirements.txt`**: Dependencies including Gradio and TensorFlow
|
| 164 |
+
- **`models/`**: Your trained model files
|
| 165 |
+
|
| 166 |
+
### 4. Deployment Process
|
| 167 |
+
|
| 168 |
+
1. **Upload files** to your Space repository
|
| 169 |
+
2. **Wait for build** - Hugging Face will automatically install dependencies
|
| 170 |
+
3. **Test the app** - Your Space will be available at `https://huggingface.co/spaces/YOUR_USERNAME/epipred`
|
| 171 |
+
|
| 172 |
+
### 5. Configuration Options
|
| 173 |
+
|
| 174 |
+
The app is configured for Hugging Face Spaces with:
|
| 175 |
+
- **Resource limits**: 25 sequences max, 150K amino acids total
|
| 176 |
+
- **Timeout**: 5 minutes per prediction
|
| 177 |
+
- **Demo mode**: Fallback if models don't load
|
| 178 |
+
- **Responsive design**: Works on mobile and desktop
|
| 179 |
+
|
| 180 |
+
### 6. Troubleshooting
|
| 181 |
+
|
| 182 |
+
If the build fails:
|
| 183 |
+
1. Check the build logs in your Space
|
| 184 |
+
2. Verify all files are uploaded correctly
|
| 185 |
+
3. Ensure model files are not too large (>1GB may cause issues)
|
| 186 |
+
4. The app will run in demo mode if models fail to load
|
| 187 |
+
|
| 188 |
+
### 7. Customization
|
| 189 |
+
|
| 190 |
+
You can customize the app by editing:
|
| 191 |
+
- **`config_hf.py`**: Limits, styling, example sequences
|
| 192 |
+
- **`app_gradio.py`**: Interface layout and functionality
|
| 193 |
+
- **`README.md`**: Space description and metadata
|
| 194 |
+
|
| 195 |
+
### 8. Success!
|
| 196 |
+
|
| 197 |
+
Once deployed, your Space will provide:
|
| 198 |
+
- ✅ Professional web interface for epitope prediction
|
| 199 |
+
- ✅ File upload and text input support
|
| 200 |
+
- ✅ Interactive results visualization
|
| 201 |
+
- ✅ CSV/JSON download functionality
|
| 202 |
+
- ✅ Mobile-responsive design
|
| 203 |
+
- ✅ Automatic scaling and hosting
|
| 204 |
+
|
| 205 |
+
Your EpiPred tool will be publicly available and ready for users! 🎉
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
|
| 209 |
+
**Need help?** Check the [Hugging Face Spaces documentation](https://huggingface.co/docs/hub/spaces) or ask in the community forums.
|
| 210 |
+
"""
|
| 211 |
+
|
| 212 |
+
with open(deploy_dir / "DEPLOYMENT_INSTRUCTIONS.md", "w") as f:
|
| 213 |
+
f.write(instructions)
|
| 214 |
+
|
| 215 |
+
print("📝 Created deployment instructions")
|
| 216 |
+
|
| 217 |
+
def main():
|
| 218 |
+
"""Main deployment preparation function"""
|
| 219 |
+
print("🚀 EpiPred - Hugging Face Spaces Deployment Preparation")
|
| 220 |
+
print("=" * 60)
|
| 221 |
+
|
| 222 |
+
# Check requirements
|
| 223 |
+
if not check_requirements():
|
| 224 |
+
sys.exit(1)
|
| 225 |
+
|
| 226 |
+
# Check model files
|
| 227 |
+
check_model_files()
|
| 228 |
+
|
| 229 |
+
# Prepare deployment
|
| 230 |
+
deploy_dir = prepare_deployment()
|
| 231 |
+
|
| 232 |
+
# Test the app
|
| 233 |
+
if not test_gradio_app(deploy_dir):
|
| 234 |
+
print("⚠️ App test failed, but deployment files are still prepared")
|
| 235 |
+
|
| 236 |
+
# Create instructions
|
| 237 |
+
create_space_instructions(deploy_dir)
|
| 238 |
+
|
| 239 |
+
print("\n" + "=" * 60)
|
| 240 |
+
print("🎉 Deployment preparation complete!")
|
| 241 |
+
print(f"📁 Files ready in: {deploy_dir.absolute()}")
|
| 242 |
+
print("📖 See DEPLOYMENT_INSTRUCTIONS.md for next steps")
|
| 243 |
+
print("\n🚀 Ready to deploy to Hugging Face Spaces!")
|
| 244 |
+
|
| 245 |
+
if __name__ == "__main__":
|
| 246 |
+
main()
|
model_predictor.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Epitope Prediction Model Interface
|
| 4 |
+
Loads and uses the trained deep learning model for epitope prediction
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import logging
|
| 9 |
+
|
| 10 |
+
# Handle TensorFlow import gracefully for deployment
|
| 11 |
+
try:
|
| 12 |
+
import tensorflow as tf
|
| 13 |
+
from tensorflow import keras
|
| 14 |
+
TF_AVAILABLE = True
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
logger.info(f"TensorFlow {tf.__version__} loaded successfully")
|
| 17 |
+
except ImportError as e:
|
| 18 |
+
TF_AVAILABLE = False
|
| 19 |
+
tf = None
|
| 20 |
+
keras = None
|
| 21 |
+
logger = logging.getLogger(__name__)
|
| 22 |
+
logger.warning(f"TensorFlow not available: {e}")
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
import numpy as np
|
| 26 |
+
except ImportError:
|
| 27 |
+
logger.error("NumPy is required but not available")
|
| 28 |
+
raise
|
| 29 |
+
|
| 30 |
+
# Optional imports for deployment compatibility
|
| 31 |
+
try:
|
| 32 |
+
import sklearn
|
| 33 |
+
SKLEARN_AVAILABLE = True
|
| 34 |
+
except ImportError:
|
| 35 |
+
SKLEARN_AVAILABLE = False
|
| 36 |
+
logging.warning("scikit-learn not available - some features may be limited")
|
| 37 |
+
|
| 38 |
+
# Set up logging
|
| 39 |
+
logging.basicConfig(level=logging.INFO)
|
| 40 |
+
logger = logging.getLogger(__name__)
|
| 41 |
+
|
| 42 |
+
class EpitopePredictor:
|
| 43 |
+
"""
|
| 44 |
+
Epitope prediction class that loads the trained model and performs predictions
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
def __init__(self, model_path=None):
|
| 48 |
+
"""
|
| 49 |
+
Initialize the predictor with the trained model
|
| 50 |
+
|
| 51 |
+
Args:
|
| 52 |
+
model_path (str): Path to the model file. If None, uses default path.
|
| 53 |
+
"""
|
| 54 |
+
# Check if TensorFlow is available
|
| 55 |
+
if not TF_AVAILABLE:
|
| 56 |
+
logger.error("TensorFlow is not available. Model prediction will not work.")
|
| 57 |
+
self.model = None
|
| 58 |
+
return
|
| 59 |
+
|
| 60 |
+
# Amino acid to number mapping (same as used in training)
|
| 61 |
+
self.amino_acid_to_num = {
|
| 62 |
+
'A': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8,
|
| 63 |
+
'K': 9, 'L': 10, 'M': 11, 'N': 12, 'P': 13, 'Q': 14, 'R': 15,
|
| 64 |
+
'S': 16, 'T': 17, 'V': 18, 'W': 19, 'Y': 20, 'X': 0 # X for unknown
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
# Class mapping (same as used in training)
|
| 68 |
+
self.class_mapping = {
|
| 69 |
+
'B_cell_negative': 0,
|
| 70 |
+
'B_cell_positive': 1,
|
| 71 |
+
'T_cell_MHC_negative': 2,
|
| 72 |
+
'T_cell_MHC_positive': 3
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
# Reverse mapping for predictions
|
| 76 |
+
self.idx_to_class = {v: k for k, v in self.class_mapping.items()}
|
| 77 |
+
|
| 78 |
+
# Model parameters
|
| 79 |
+
self.window_size = 20
|
| 80 |
+
self.step_size = 1
|
| 81 |
+
self.confidence_threshold = 0.5
|
| 82 |
+
|
| 83 |
+
# Load the model
|
| 84 |
+
try:
|
| 85 |
+
self.model = self._load_model(model_path)
|
| 86 |
+
except Exception as e:
|
| 87 |
+
logger.error(f"Failed to load model: {e}")
|
| 88 |
+
self.model = None
|
| 89 |
+
|
| 90 |
+
def _load_model(self, model_path=None):
|
| 91 |
+
"""
|
| 92 |
+
Load the trained model
|
| 93 |
+
|
| 94 |
+
Args:
|
| 95 |
+
model_path (str): Path to model file
|
| 96 |
+
|
| 97 |
+
Returns:
|
| 98 |
+
tensorflow.keras.Model: Loaded model
|
| 99 |
+
"""
|
| 100 |
+
if model_path is None:
|
| 101 |
+
# Try different model formats in order of preference
|
| 102 |
+
possible_paths = [
|
| 103 |
+
'models/epitope_model.keras',
|
| 104 |
+
'models/epitope_model.h5',
|
| 105 |
+
'models/epitope_model_savedmodel',
|
| 106 |
+
'../epitope_model.keras',
|
| 107 |
+
'../epitope_model.h5',
|
| 108 |
+
'../epitope_model_savedmodel',
|
| 109 |
+
'epitope_model.keras',
|
| 110 |
+
'epitope_model.h5',
|
| 111 |
+
'epitope_model_savedmodel'
|
| 112 |
+
]
|
| 113 |
+
|
| 114 |
+
for path in possible_paths:
|
| 115 |
+
if os.path.exists(path):
|
| 116 |
+
model_path = path
|
| 117 |
+
break
|
| 118 |
+
|
| 119 |
+
if model_path is None:
|
| 120 |
+
raise FileNotFoundError("No trained model found. Please ensure the model file exists.")
|
| 121 |
+
|
| 122 |
+
try:
|
| 123 |
+
logger.info(f"Loading model from: {model_path}")
|
| 124 |
+
|
| 125 |
+
if model_path.endswith('.keras') or model_path.endswith('.h5'):
|
| 126 |
+
model = keras.models.load_model(model_path, compile=False)
|
| 127 |
+
else:
|
| 128 |
+
# Assume SavedModel format
|
| 129 |
+
model = tf.saved_model.load(model_path)
|
| 130 |
+
|
| 131 |
+
logger.info("Model loaded successfully")
|
| 132 |
+
|
| 133 |
+
# Test the model with a dummy input to ensure it works
|
| 134 |
+
try:
|
| 135 |
+
dummy_input = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]])
|
| 136 |
+
if hasattr(model, 'predict'):
|
| 137 |
+
_ = model.predict(dummy_input, verbose=0)
|
| 138 |
+
else:
|
| 139 |
+
_ = model(dummy_input)
|
| 140 |
+
logger.info("Model test prediction successful")
|
| 141 |
+
except Exception as test_error:
|
| 142 |
+
logger.warning(f"Model test failed: {test_error}")
|
| 143 |
+
|
| 144 |
+
return model
|
| 145 |
+
|
| 146 |
+
except Exception as e:
|
| 147 |
+
logger.error(f"Error loading model: {e}")
|
| 148 |
+
raise RuntimeError(f"Failed to load model from {model_path}: {e}")
|
| 149 |
+
|
| 150 |
+
def encode_sequence(self, sequence):
|
| 151 |
+
"""
|
| 152 |
+
Encode amino acid sequence to numerical representation
|
| 153 |
+
|
| 154 |
+
Args:
|
| 155 |
+
sequence (str): Amino acid sequence
|
| 156 |
+
|
| 157 |
+
Returns:
|
| 158 |
+
list: Encoded sequence
|
| 159 |
+
"""
|
| 160 |
+
return [self.amino_acid_to_num.get(aa.upper(), 0) for aa in sequence]
|
| 161 |
+
|
| 162 |
+
def sliding_window_prediction(self, sequence):
|
| 163 |
+
"""
|
| 164 |
+
Perform sliding window prediction on a protein sequence
|
| 165 |
+
|
| 166 |
+
Args:
|
| 167 |
+
sequence (str): Protein sequence
|
| 168 |
+
|
| 169 |
+
Returns:
|
| 170 |
+
tuple: (b_cell_epitopes, t_cell_epitopes) lists with (epitope, confidence, position_range)
|
| 171 |
+
"""
|
| 172 |
+
logger.debug(f"Starting sliding window prediction for sequence of length {len(sequence)}")
|
| 173 |
+
b_cell_epitopes = []
|
| 174 |
+
t_cell_epitopes = []
|
| 175 |
+
|
| 176 |
+
if len(sequence) < self.window_size:
|
| 177 |
+
logger.warning(f"Sequence too short ({len(sequence)} < {self.window_size})")
|
| 178 |
+
return b_cell_epitopes, t_cell_epitopes
|
| 179 |
+
|
| 180 |
+
# Prepare batch data for efficient prediction
|
| 181 |
+
subseq_list = []
|
| 182 |
+
positions = []
|
| 183 |
+
|
| 184 |
+
for i in range(0, len(sequence) - self.window_size + 1, self.step_size):
|
| 185 |
+
sub_seq = sequence[i:i + self.window_size]
|
| 186 |
+
encoded_sub_seq = self.encode_sequence(sub_seq)
|
| 187 |
+
subseq_list.append(encoded_sub_seq)
|
| 188 |
+
positions.append((i, i + self.window_size))
|
| 189 |
+
|
| 190 |
+
if not subseq_list:
|
| 191 |
+
logger.warning("No subsequences generated for prediction")
|
| 192 |
+
return b_cell_epitopes, t_cell_epitopes
|
| 193 |
+
|
| 194 |
+
logger.debug(f"Generated {len(subseq_list)} subsequences for prediction")
|
| 195 |
+
|
| 196 |
+
# Convert to numpy array for batch prediction
|
| 197 |
+
padded_subseq_array = np.array(subseq_list)
|
| 198 |
+
|
| 199 |
+
try:
|
| 200 |
+
logger.debug(f"Performing batch prediction on {padded_subseq_array.shape} array")
|
| 201 |
+
|
| 202 |
+
# Perform batch predictions
|
| 203 |
+
if hasattr(self.model, 'predict'):
|
| 204 |
+
predicted_probs = self.model.predict(padded_subseq_array, batch_size=64, verbose=0)
|
| 205 |
+
else:
|
| 206 |
+
# For SavedModel format
|
| 207 |
+
predicted_probs = self.model(padded_subseq_array).numpy()
|
| 208 |
+
|
| 209 |
+
logger.debug(f"Model prediction completed, output shape: {predicted_probs.shape}")
|
| 210 |
+
|
| 211 |
+
# Process predictions
|
| 212 |
+
for i, (probs, (start_pos, end_pos)) in enumerate(zip(predicted_probs, positions)):
|
| 213 |
+
predicted_class = np.argmax(probs)
|
| 214 |
+
confidence = np.max(probs)
|
| 215 |
+
predicted_label = self.idx_to_class[predicted_class]
|
| 216 |
+
|
| 217 |
+
# Only include predictions above threshold
|
| 218 |
+
if confidence >= self.confidence_threshold:
|
| 219 |
+
sub_seq = sequence[start_pos:end_pos]
|
| 220 |
+
pos_range = f"{start_pos+1}-{end_pos}" # 1-based indexing for display
|
| 221 |
+
|
| 222 |
+
if predicted_label == "B_cell_positive":
|
| 223 |
+
b_cell_epitopes.append((sub_seq, float(confidence), pos_range))
|
| 224 |
+
elif predicted_label == "T_cell_MHC_positive":
|
| 225 |
+
t_cell_epitopes.append((sub_seq, float(confidence), pos_range))
|
| 226 |
+
|
| 227 |
+
logger.debug(f"Prediction processing completed: {len(b_cell_epitopes)} B-cell, {len(t_cell_epitopes)} T-cell epitopes above threshold")
|
| 228 |
+
|
| 229 |
+
except Exception as e:
|
| 230 |
+
logger.error(f"Error during prediction: {e}")
|
| 231 |
+
raise
|
| 232 |
+
|
| 233 |
+
return b_cell_epitopes, t_cell_epitopes
|
| 234 |
+
|
| 235 |
+
def predict_epitopes(self, sequence, threshold=None):
|
| 236 |
+
"""
|
| 237 |
+
Main prediction function
|
| 238 |
+
|
| 239 |
+
Args:
|
| 240 |
+
sequence (str): Protein sequence
|
| 241 |
+
threshold (float): Confidence threshold (optional)
|
| 242 |
+
|
| 243 |
+
Returns:
|
| 244 |
+
tuple: (b_cell_epitopes, t_cell_epitopes)
|
| 245 |
+
"""
|
| 246 |
+
# Check if model is available
|
| 247 |
+
if self.model is None:
|
| 248 |
+
logger.warning("Model not available, returning demo predictions")
|
| 249 |
+
return self._generate_demo_predictions(sequence)
|
| 250 |
+
|
| 251 |
+
logger.info(f"Starting epitope prediction for sequence of length {len(sequence)}")
|
| 252 |
+
|
| 253 |
+
if threshold is not None:
|
| 254 |
+
original_threshold = self.confidence_threshold
|
| 255 |
+
self.confidence_threshold = threshold
|
| 256 |
+
logger.debug(f"Using custom threshold: {threshold}")
|
| 257 |
+
|
| 258 |
+
try:
|
| 259 |
+
# Clean the sequence
|
| 260 |
+
clean_sequence = ''.join(c.upper() for c in sequence if c.upper() in self.amino_acid_to_num)
|
| 261 |
+
|
| 262 |
+
if len(clean_sequence) != len(sequence):
|
| 263 |
+
logger.warning(f"Sequence contained invalid characters. Cleaned: {len(sequence)} -> {len(clean_sequence)}")
|
| 264 |
+
|
| 265 |
+
# Perform prediction
|
| 266 |
+
b_cell_epitopes, t_cell_epitopes = self.sliding_window_prediction(clean_sequence)
|
| 267 |
+
|
| 268 |
+
# Sort by confidence (highest first)
|
| 269 |
+
b_cell_epitopes.sort(key=lambda x: x[1], reverse=True)
|
| 270 |
+
t_cell_epitopes.sort(key=lambda x: x[1], reverse=True)
|
| 271 |
+
|
| 272 |
+
logger.info(f"Prediction completed: {len(b_cell_epitopes)} B-cell, {len(t_cell_epitopes)} T-cell epitopes found")
|
| 273 |
+
|
| 274 |
+
return b_cell_epitopes, t_cell_epitopes
|
| 275 |
+
|
| 276 |
+
finally:
|
| 277 |
+
if threshold is not None:
|
| 278 |
+
self.confidence_threshold = original_threshold
|
| 279 |
+
|
| 280 |
+
def _generate_demo_predictions(self, sequence):
|
| 281 |
+
"""
|
| 282 |
+
Generate demo predictions when model is not available
|
| 283 |
+
|
| 284 |
+
Args:
|
| 285 |
+
sequence (str): Protein sequence
|
| 286 |
+
|
| 287 |
+
Returns:
|
| 288 |
+
tuple: (b_cell_epitopes, t_cell_epitopes) with demo data
|
| 289 |
+
"""
|
| 290 |
+
import random
|
| 291 |
+
random.seed(42) # For consistent demo results
|
| 292 |
+
|
| 293 |
+
b_cell_epitopes = []
|
| 294 |
+
t_cell_epitopes = []
|
| 295 |
+
|
| 296 |
+
# Generate some demo epitopes
|
| 297 |
+
seq_len = len(sequence)
|
| 298 |
+
if seq_len >= 20:
|
| 299 |
+
# Generate a few demo B-cell epitopes
|
| 300 |
+
for i in range(0, min(seq_len - 19, 3)):
|
| 301 |
+
start = i * 25
|
| 302 |
+
if start + 20 <= seq_len:
|
| 303 |
+
epitope = sequence[start:start + 20]
|
| 304 |
+
confidence = 0.6 + random.random() * 0.3 # 0.6-0.9
|
| 305 |
+
pos_range = f"{start + 1}-{start + 20}"
|
| 306 |
+
b_cell_epitopes.append((epitope, confidence, pos_range))
|
| 307 |
+
|
| 308 |
+
# Generate a few demo T-cell epitopes
|
| 309 |
+
for i in range(1, min(seq_len - 19, 3)):
|
| 310 |
+
start = i * 30 + 10
|
| 311 |
+
if start + 20 <= seq_len:
|
| 312 |
+
epitope = sequence[start:start + 20]
|
| 313 |
+
confidence = 0.5 + random.random() * 0.4 # 0.5-0.9
|
| 314 |
+
pos_range = f"{start + 1}-{start + 20}"
|
| 315 |
+
t_cell_epitopes.append((epitope, confidence, pos_range))
|
| 316 |
+
|
| 317 |
+
logger.info(f"Demo predictions generated: {len(b_cell_epitopes)} B-cell, {len(t_cell_epitopes)} T-cell epitopes")
|
| 318 |
+
return b_cell_epitopes, t_cell_epitopes
|
| 319 |
+
|
| 320 |
+
def get_sequence_markup(self, sequence, epitopes, epitope_type='B-cell'):
|
| 321 |
+
"""
|
| 322 |
+
Generate sequence markup for visualization
|
| 323 |
+
|
| 324 |
+
Args:
|
| 325 |
+
sequence (str): Original sequence
|
| 326 |
+
epitopes (list): List of epitopes with positions
|
| 327 |
+
epitope_type (str): Type of epitopes ('B-cell' or 'T-cell')
|
| 328 |
+
|
| 329 |
+
Returns:
|
| 330 |
+
str: Marked up sequence
|
| 331 |
+
"""
|
| 332 |
+
markup = ['.' for _ in sequence] # Default to non-epitope
|
| 333 |
+
|
| 334 |
+
for epitope, confidence, pos_range in epitopes:
|
| 335 |
+
start, end = map(int, pos_range.split('-'))
|
| 336 |
+
start -= 1 # Convert to 0-based indexing
|
| 337 |
+
end -= 1
|
| 338 |
+
|
| 339 |
+
# Mark epitope positions
|
| 340 |
+
marker = 'E' if epitope_type == 'B-cell' else 'T'
|
| 341 |
+
for i in range(start, min(end + 1, len(markup))):
|
| 342 |
+
markup[i] = marker
|
| 343 |
+
|
| 344 |
+
return ''.join(markup)
|
| 345 |
+
|
| 346 |
+
def set_confidence_threshold(self, threshold):
|
| 347 |
+
"""
|
| 348 |
+
Set the confidence threshold for predictions
|
| 349 |
+
|
| 350 |
+
Args:
|
| 351 |
+
threshold (float): New threshold value (0.0 to 1.0)
|
| 352 |
+
"""
|
| 353 |
+
if 0.0 <= threshold <= 1.0:
|
| 354 |
+
self.confidence_threshold = threshold
|
| 355 |
+
else:
|
| 356 |
+
raise ValueError("Threshold must be between 0.0 and 1.0")
|
| 357 |
+
|
| 358 |
+
def get_model_info(self):
|
| 359 |
+
"""
|
| 360 |
+
Get information about the loaded model
|
| 361 |
+
|
| 362 |
+
Returns:
|
| 363 |
+
dict: Model information
|
| 364 |
+
"""
|
| 365 |
+
info = {
|
| 366 |
+
'window_size': self.window_size,
|
| 367 |
+
'step_size': self.step_size,
|
| 368 |
+
'confidence_threshold': self.confidence_threshold,
|
| 369 |
+
'classes': list(self.class_mapping.keys()),
|
| 370 |
+
'amino_acids': list(self.amino_acid_to_num.keys())
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
if hasattr(self.model, 'summary'):
|
| 374 |
+
try:
|
| 375 |
+
# Get model summary as string
|
| 376 |
+
import io
|
| 377 |
+
import sys
|
| 378 |
+
old_stdout = sys.stdout
|
| 379 |
+
sys.stdout = buffer = io.StringIO()
|
| 380 |
+
self.model.summary()
|
| 381 |
+
sys.stdout = old_stdout
|
| 382 |
+
info['model_summary'] = buffer.getvalue()
|
| 383 |
+
except:
|
| 384 |
+
info['model_summary'] = "Model summary not available"
|
| 385 |
+
|
| 386 |
+
return info
|
models/.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
models/epitope_model.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f25e4342eff2075eda6490461148fe95b090f20d0ff3df7806a4f317f43866ea
|
| 3 |
+
size 3001040
|
models/epitope_model.keras
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a35a2c1353c0c0f6c0ad3d5b7ee5c518ff55a6e70dd95c73a698479b48bd53c6
|
| 3 |
+
size 2987040
|
models/epitope_model_savedmodel/.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
models/epitope_model_savedmodel/fingerprint.pb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5ff3b764c1e61750917db74c8c5ed2281d0113210871f065a3fe32008303fcd5
|
| 3 |
+
size 55
|
models/epitope_model_savedmodel/saved_model.pb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fb930a0d073be7d400d6e8bd4fc69036c761ec0fac31568cdbe1caf091eaff39
|
| 3 |
+
size 405924
|
models/epitope_model_savedmodel/variables/variables.data-00000-of-00001
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b20026488bd6a597e15d2e47b1cd0ba408b27aa3f964845011e630bd8e93824c
|
| 3 |
+
size 1902873
|
models/epitope_model_savedmodel/variables/variables.index
ADDED
|
Binary file (5.41 kB). View file
|
|
|
models/epitope_model_single_block.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7ed75234e43139ca97aee429cdc99255122da3bf7a7869b9ba25f502b5064654
|
| 3 |
+
size 2120632
|
models/epitope_model_single_block.keras
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7be6caa8d935513ba722140f6f11cdff98e66d2d3662cad230fd727839eadee0
|
| 3 |
+
size 2109488
|
render.yaml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
- type: web
|
| 3 |
+
name: epitope-predictor
|
| 4 |
+
env: python
|
| 5 |
+
plan: free
|
| 6 |
+
buildCommand: |
|
| 7 |
+
pip install --upgrade pip
|
| 8 |
+
pip install -r requirements.txt
|
| 9 |
+
startCommand: gunicorn --bind 0.0.0.0:$PORT --timeout 300 --workers 1 --max-requests 1000 --preload wsgi:app
|
| 10 |
+
envVars:
|
| 11 |
+
- key: PYTHON_VERSION
|
| 12 |
+
value: 3.12.7
|
| 13 |
+
- key: FLASK_ENV
|
| 14 |
+
value: production
|
| 15 |
+
- key: FLASK_DEBUG
|
| 16 |
+
value: false
|
| 17 |
+
- key: PYTHONPATH
|
| 18 |
+
value: /opt/render/project/src
|
| 19 |
+
healthCheckPath: /
|
| 20 |
+
disk:
|
| 21 |
+
name: epitope-disk
|
| 22 |
+
mountPath: /opt/render/project/src/static/uploads
|
| 23 |
+
sizeGB: 1
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Spaces requirements
|
| 2 |
+
gradio>=4.44.0,<5.0.0
|
| 3 |
+
numpy>=1.24.0,<2.0.0
|
| 4 |
+
pandas>=2.0.0,<3.0.0
|
| 5 |
+
|
| 6 |
+
# Machine Learning - Use latest compatible TensorFlow CPU
|
| 7 |
+
tensorflow-cpu>=2.16.0,<2.21.0
|
| 8 |
+
|
| 9 |
+
# Optional: Keep Flask dependencies for backward compatibility
|
| 10 |
+
Flask>=2.3.0,<4.0.0
|
| 11 |
+
Werkzeug>=2.3.0,<4.0.0
|
| 12 |
+
gunicorn>=21.0.0,<23.0.0
|
run.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
EpiPred Web Application Launcher
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import logging
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
# Add the current directory to Python path
|
| 12 |
+
current_dir = Path(__file__).parent
|
| 13 |
+
sys.path.insert(0, str(current_dir))
|
| 14 |
+
|
| 15 |
+
# Set up logging
|
| 16 |
+
logging.basicConfig(
|
| 17 |
+
level=logging.INFO,
|
| 18 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 19 |
+
)
|
| 20 |
+
logger = logging.getLogger(__name__)
|
| 21 |
+
|
| 22 |
+
# Import the Flask app for WSGI deployment
|
| 23 |
+
try:
|
| 24 |
+
from app import app
|
| 25 |
+
logger.info("Flask app imported successfully for WSGI deployment")
|
| 26 |
+
except ImportError as e:
|
| 27 |
+
logger.error(f"Failed to import Flask app: {e}")
|
| 28 |
+
# Create a dummy app for error handling
|
| 29 |
+
from flask import Flask
|
| 30 |
+
app = Flask(__name__)
|
| 31 |
+
|
| 32 |
+
@app.route('/')
|
| 33 |
+
def error():
|
| 34 |
+
return f"Import Error: {e}", 500
|
| 35 |
+
|
| 36 |
+
def check_dependencies():
|
| 37 |
+
"""Check if required dependencies are installed"""
|
| 38 |
+
required_packages = [
|
| 39 |
+
'flask',
|
| 40 |
+
'tensorflow',
|
| 41 |
+
'numpy',
|
| 42 |
+
'pandas'
|
| 43 |
+
]
|
| 44 |
+
|
| 45 |
+
missing_packages = []
|
| 46 |
+
for package in required_packages:
|
| 47 |
+
try:
|
| 48 |
+
__import__(package)
|
| 49 |
+
except ImportError:
|
| 50 |
+
missing_packages.append(package)
|
| 51 |
+
|
| 52 |
+
if missing_packages:
|
| 53 |
+
logger.error(f"Missing required packages: {', '.join(missing_packages)}")
|
| 54 |
+
logger.error("Please install dependencies with: pip install -r requirements.txt")
|
| 55 |
+
return False
|
| 56 |
+
|
| 57 |
+
return True
|
| 58 |
+
|
| 59 |
+
def check_model_files():
|
| 60 |
+
"""Check if model files exist"""
|
| 61 |
+
model_paths = [
|
| 62 |
+
'models/epitope_model.keras',
|
| 63 |
+
'models/epitope_model.h5',
|
| 64 |
+
'models/epitope_model_savedmodel',
|
| 65 |
+
'../epitope_model.keras',
|
| 66 |
+
'../epitope_model.h5',
|
| 67 |
+
'../epitope_model_savedmodel',
|
| 68 |
+
'epitope_model.keras',
|
| 69 |
+
'epitope_model.h5',
|
| 70 |
+
'epitope_model_savedmodel'
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
for path in model_paths:
|
| 74 |
+
if os.path.exists(path):
|
| 75 |
+
logger.info(f"Found model file: {path}")
|
| 76 |
+
return True
|
| 77 |
+
|
| 78 |
+
logger.warning("No model files found. Please ensure model files are available.")
|
| 79 |
+
logger.warning("Expected locations: models/ directory or current directory")
|
| 80 |
+
return False
|
| 81 |
+
|
| 82 |
+
def create_directories():
|
| 83 |
+
"""Create necessary directories"""
|
| 84 |
+
directories = [
|
| 85 |
+
'static/uploads',
|
| 86 |
+
'static/css',
|
| 87 |
+
'static/js',
|
| 88 |
+
'templates',
|
| 89 |
+
'models'
|
| 90 |
+
]
|
| 91 |
+
|
| 92 |
+
for directory in directories:
|
| 93 |
+
os.makedirs(directory, exist_ok=True)
|
| 94 |
+
logger.info(f"Ensured directory exists: {directory}")
|
| 95 |
+
|
| 96 |
+
def main():
|
| 97 |
+
"""Main function to start the application"""
|
| 98 |
+
logger.info("Starting EpiPred Web Application...")
|
| 99 |
+
|
| 100 |
+
# Check dependencies
|
| 101 |
+
if not check_dependencies():
|
| 102 |
+
sys.exit(1)
|
| 103 |
+
|
| 104 |
+
# Check model files
|
| 105 |
+
if not check_model_files():
|
| 106 |
+
logger.warning("Continuing without model files - predictions may fail")
|
| 107 |
+
|
| 108 |
+
# Create directories
|
| 109 |
+
create_directories()
|
| 110 |
+
|
| 111 |
+
# Configuration
|
| 112 |
+
app.config['DEBUG'] = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true'
|
| 113 |
+
host = os.environ.get('FLASK_HOST', '0.0.0.0')
|
| 114 |
+
port = int(os.environ.get('FLASK_PORT', 5000))
|
| 115 |
+
|
| 116 |
+
logger.info(f"Starting server on {host}:{port}")
|
| 117 |
+
logger.info(f"Debug mode: {app.config['DEBUG']}")
|
| 118 |
+
|
| 119 |
+
# Run the application
|
| 120 |
+
app.run(
|
| 121 |
+
host=host,
|
| 122 |
+
port=port,
|
| 123 |
+
debug=app.config['DEBUG'],
|
| 124 |
+
threaded=True
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
if __name__ == '__main__':
|
| 128 |
+
main()
|
runtime.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
python-3.12.7
|
start.sh
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# EpiPred Web Application Startup Script
|
| 4 |
+
|
| 5 |
+
echo "Starting EpiPred Web Application..."
|
| 6 |
+
echo "=================================="
|
| 7 |
+
|
| 8 |
+
# Check if Python is available
|
| 9 |
+
if ! command -v python3 &> /dev/null; then
|
| 10 |
+
echo "Error: Python 3 is not installed or not in PATH"
|
| 11 |
+
exit 1
|
| 12 |
+
fi
|
| 13 |
+
|
| 14 |
+
# Check if pip is available
|
| 15 |
+
if ! command -v pip3 &> /dev/null && ! command -v pip &> /dev/null; then
|
| 16 |
+
echo "Error: pip is not installed or not in PATH"
|
| 17 |
+
exit 1
|
| 18 |
+
fi
|
| 19 |
+
|
| 20 |
+
# Create virtual environment if it doesn't exist
|
| 21 |
+
if [ ! -d "venv" ]; then
|
| 22 |
+
echo "Creating virtual environment..."
|
| 23 |
+
python3 -m venv venv
|
| 24 |
+
fi
|
| 25 |
+
|
| 26 |
+
# Activate virtual environment
|
| 27 |
+
echo "Activating virtual environment..."
|
| 28 |
+
source venv/bin/activate
|
| 29 |
+
|
| 30 |
+
# Install dependencies
|
| 31 |
+
echo "Installing dependencies..."
|
| 32 |
+
pip install -r requirements.txt
|
| 33 |
+
|
| 34 |
+
# Run tests
|
| 35 |
+
echo "Running application tests..."
|
| 36 |
+
python test_app.py
|
| 37 |
+
|
| 38 |
+
if [ $? -eq 0 ]; then
|
| 39 |
+
echo ""
|
| 40 |
+
echo "Tests passed! Starting the application..."
|
| 41 |
+
echo ""
|
| 42 |
+
echo "The application will be available at: http://localhost:5000"
|
| 43 |
+
echo "Press Ctrl+C to stop the server"
|
| 44 |
+
echo ""
|
| 45 |
+
|
| 46 |
+
# Start the application
|
| 47 |
+
python run.py
|
| 48 |
+
else
|
| 49 |
+
echo ""
|
| 50 |
+
echo "Tests failed. Please check the errors above."
|
| 51 |
+
echo "You can still try to start the application with: python run.py"
|
| 52 |
+
fi
|
start_production.sh
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Production startup script
|
| 3 |
+
|
| 4 |
+
# Set environment variables
|
| 5 |
+
export FLASK_ENV=production
|
| 6 |
+
export FLASK_APP=app.py
|
| 7 |
+
|
| 8 |
+
# Create necessary directories
|
| 9 |
+
mkdir -p static/uploads
|
| 10 |
+
|
| 11 |
+
# Start the application with Gunicorn
|
| 12 |
+
exec gunicorn --bind 0.0.0.0:$PORT --workers 1 --timeout 300 --max-requests 1000 app:app
|
static/.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
static/css/style.css
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* EpiPred - Custom Styles */
|
| 2 |
+
|
| 3 |
+
/* Global Styles */
|
| 4 |
+
:root {
|
| 5 |
+
--primary-color: #007bff;
|
| 6 |
+
--secondary-color: #6c757d;
|
| 7 |
+
--success-color: #28a745;
|
| 8 |
+
--danger-color: #dc3545;
|
| 9 |
+
--warning-color: #ffc107;
|
| 10 |
+
--info-color: #17a2b8;
|
| 11 |
+
--light-color: #f8f9fa;
|
| 12 |
+
--dark-color: #343a40;
|
| 13 |
+
--epitope-b-color: #ff6b6b;
|
| 14 |
+
--epitope-t-color: #4ecdc4;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
body {
|
| 18 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 19 |
+
line-height: 1.6;
|
| 20 |
+
color: #333;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
/* Navigation */
|
| 24 |
+
.navbar-brand {
|
| 25 |
+
font-weight: bold;
|
| 26 |
+
font-size: 1.5rem;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.navbar-brand img {
|
| 30 |
+
transition: transform 0.3s ease;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.navbar-brand:hover img {
|
| 34 |
+
transform: scale(1.05);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.navbar-nav .nav-link {
|
| 38 |
+
font-weight: 500;
|
| 39 |
+
transition: color 0.3s ease;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.navbar-nav .nav-link:hover {
|
| 43 |
+
color: rgba(255, 255, 255, 0.8) !important;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
/* Logo Styles */
|
| 47 |
+
.logo-header {
|
| 48 |
+
max-height: 80px;
|
| 49 |
+
width: auto;
|
| 50 |
+
transition: transform 0.3s ease;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.logo-header:hover {
|
| 54 |
+
transform: scale(1.02);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.logo-footer {
|
| 58 |
+
max-height: 40px;
|
| 59 |
+
width: auto;
|
| 60 |
+
filter: brightness(0.9);
|
| 61 |
+
transition: filter 0.3s ease;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.logo-footer:hover {
|
| 65 |
+
filter: brightness(1.1);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/* Header Section */
|
| 69 |
+
.bg-light {
|
| 70 |
+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%) !important;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.display-6 {
|
| 74 |
+
font-weight: 600;
|
| 75 |
+
color: var(--dark-color);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.lead {
|
| 79 |
+
font-size: 1.1rem;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
/* Cards */
|
| 83 |
+
.card {
|
| 84 |
+
border: none;
|
| 85 |
+
border-radius: 10px;
|
| 86 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.card:hover {
|
| 90 |
+
transform: translateY(-2px);
|
| 91 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1) !important;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.card-header {
|
| 95 |
+
border-radius: 10px 10px 0 0 !important;
|
| 96 |
+
font-weight: 600;
|
| 97 |
+
border-bottom: none;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.card-body {
|
| 101 |
+
padding: 1.5rem;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
/* Form Elements */
|
| 105 |
+
.form-control {
|
| 106 |
+
border-radius: 8px;
|
| 107 |
+
border: 2px solid #e9ecef;
|
| 108 |
+
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.form-control:focus {
|
| 112 |
+
border-color: var(--primary-color);
|
| 113 |
+
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
textarea.form-control {
|
| 117 |
+
resize: vertical;
|
| 118 |
+
min-height: 120px;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
.form-label {
|
| 122 |
+
font-weight: 600;
|
| 123 |
+
color: var(--dark-color);
|
| 124 |
+
margin-bottom: 0.75rem;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.form-text {
|
| 128 |
+
font-size: 0.875rem;
|
| 129 |
+
color: var(--secondary-color);
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/* Buttons */
|
| 133 |
+
.btn {
|
| 134 |
+
border-radius: 8px;
|
| 135 |
+
font-weight: 500;
|
| 136 |
+
padding: 0.5rem 1.5rem;
|
| 137 |
+
transition: all 0.3s ease;
|
| 138 |
+
border: none;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
.btn-lg {
|
| 142 |
+
padding: 0.75rem 2rem;
|
| 143 |
+
font-size: 1.1rem;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
.btn-primary {
|
| 147 |
+
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
|
| 148 |
+
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.btn-primary:hover {
|
| 152 |
+
background: linear-gradient(135deg, #0056b3 0%, #004085 100%);
|
| 153 |
+
transform: translateY(-1px);
|
| 154 |
+
box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4);
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.btn-success {
|
| 158 |
+
background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%);
|
| 159 |
+
box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.btn-success:hover {
|
| 163 |
+
background: linear-gradient(135deg, #1e7e34 0%, #155724 100%);
|
| 164 |
+
transform: translateY(-1px);
|
| 165 |
+
box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4);
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.btn-outline-secondary {
|
| 169 |
+
border: 2px solid var(--secondary-color);
|
| 170 |
+
color: var(--secondary-color);
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.btn-outline-secondary:hover {
|
| 174 |
+
background-color: var(--secondary-color);
|
| 175 |
+
border-color: var(--secondary-color);
|
| 176 |
+
transform: translateY(-1px);
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
/* Alerts */
|
| 180 |
+
.alert {
|
| 181 |
+
border-radius: 8px;
|
| 182 |
+
border: none;
|
| 183 |
+
font-weight: 500;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
.alert-info {
|
| 187 |
+
background: linear-gradient(135deg, #d1ecf1 0%, #bee5eb 100%);
|
| 188 |
+
color: #0c5460;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
.alert-success {
|
| 192 |
+
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
|
| 193 |
+
color: #155724;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
.alert-danger {
|
| 197 |
+
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
|
| 198 |
+
color: #721c24;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
/* Sequence Display */
|
| 202 |
+
.sequence-display {
|
| 203 |
+
font-family: 'Courier New', 'Monaco', 'Menlo', monospace;
|
| 204 |
+
font-size: 14px;
|
| 205 |
+
line-height: 1.6;
|
| 206 |
+
word-break: break-all;
|
| 207 |
+
background: #f8f9fa;
|
| 208 |
+
padding: 20px;
|
| 209 |
+
border-radius: 8px;
|
| 210 |
+
border: 2px solid #e9ecef;
|
| 211 |
+
max-height: 300px;
|
| 212 |
+
overflow-y: auto;
|
| 213 |
+
white-space: pre-wrap;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
.epitope-b {
|
| 217 |
+
background-color: var(--epitope-b-color);
|
| 218 |
+
color: white;
|
| 219 |
+
padding: 2px 3px;
|
| 220 |
+
border-radius: 3px;
|
| 221 |
+
font-weight: bold;
|
| 222 |
+
margin: 0 1px;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.epitope-t {
|
| 226 |
+
background-color: var(--epitope-t-color);
|
| 227 |
+
color: white;
|
| 228 |
+
padding: 2px 3px;
|
| 229 |
+
border-radius: 3px;
|
| 230 |
+
font-weight: bold;
|
| 231 |
+
margin: 0 1px;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.non-epitope {
|
| 235 |
+
color: var(--secondary-color);
|
| 236 |
+
opacity: 0.7;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
/* Confidence Bars */
|
| 240 |
+
.confidence-bar {
|
| 241 |
+
height: 20px;
|
| 242 |
+
background: linear-gradient(90deg, #dc3545 0%, #ffc107 50%, #28a745 100%);
|
| 243 |
+
border-radius: 10px;
|
| 244 |
+
position: relative;
|
| 245 |
+
overflow: hidden;
|
| 246 |
+
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.confidence-indicator {
|
| 250 |
+
position: absolute;
|
| 251 |
+
top: 0;
|
| 252 |
+
left: 0;
|
| 253 |
+
height: 100%;
|
| 254 |
+
background: rgba(255, 255, 255, 0.4);
|
| 255 |
+
border-radius: 10px;
|
| 256 |
+
transition: width 0.3s ease;
|
| 257 |
+
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
/* Tables */
|
| 261 |
+
.table {
|
| 262 |
+
border-radius: 8px;
|
| 263 |
+
overflow: hidden;
|
| 264 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
.table thead th {
|
| 268 |
+
background-color: var(--light-color);
|
| 269 |
+
border-bottom: 2px solid #dee2e6;
|
| 270 |
+
font-weight: 600;
|
| 271 |
+
color: var(--dark-color);
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
.table tbody tr {
|
| 275 |
+
transition: background-color 0.2s ease;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
.table tbody tr:hover {
|
| 279 |
+
background-color: rgba(0, 123, 255, 0.05);
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
/* Threshold Slider */
|
| 283 |
+
.form-range {
|
| 284 |
+
height: 8px;
|
| 285 |
+
background: linear-gradient(90deg, #dc3545 0%, #ffc107 50%, #28a745 100%);
|
| 286 |
+
border-radius: 4px;
|
| 287 |
+
outline: none;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.form-range::-webkit-slider-thumb {
|
| 291 |
+
width: 20px;
|
| 292 |
+
height: 20px;
|
| 293 |
+
background: #007bff;
|
| 294 |
+
border-radius: 50%;
|
| 295 |
+
border: 3px solid white;
|
| 296 |
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
| 297 |
+
cursor: pointer;
|
| 298 |
+
transition: transform 0.2s ease;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
.form-range::-webkit-slider-thumb:hover {
|
| 302 |
+
transform: scale(1.1);
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.form-range::-moz-range-thumb {
|
| 306 |
+
width: 20px;
|
| 307 |
+
height: 20px;
|
| 308 |
+
background: #007bff;
|
| 309 |
+
border-radius: 50%;
|
| 310 |
+
border: 3px solid white;
|
| 311 |
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
| 312 |
+
cursor: pointer;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
/* Badges */
|
| 316 |
+
.badge {
|
| 317 |
+
font-size: 0.8rem;
|
| 318 |
+
padding: 0.5rem 0.75rem;
|
| 319 |
+
border-radius: 20px;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
/* Modal */
|
| 323 |
+
.modal-content {
|
| 324 |
+
border-radius: 10px;
|
| 325 |
+
border: none;
|
| 326 |
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
.modal-header {
|
| 330 |
+
border-bottom: 2px solid #e9ecef;
|
| 331 |
+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
| 332 |
+
border-radius: 10px 10px 0 0;
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
.modal-title {
|
| 336 |
+
font-weight: 600;
|
| 337 |
+
color: var(--dark-color);
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
/* Code blocks */
|
| 341 |
+
pre {
|
| 342 |
+
background: #f8f9fa;
|
| 343 |
+
border: 2px solid #e9ecef;
|
| 344 |
+
border-radius: 8px;
|
| 345 |
+
padding: 1rem;
|
| 346 |
+
font-size: 0.9rem;
|
| 347 |
+
line-height: 1.4;
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
code {
|
| 351 |
+
color: #e83e8c;
|
| 352 |
+
font-size: 0.9rem;
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
/* Footer */
|
| 356 |
+
footer {
|
| 357 |
+
background: linear-gradient(135deg, #343a40 0%, #212529 100%) !important;
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
footer h5 {
|
| 361 |
+
color: #fff;
|
| 362 |
+
font-weight: 600;
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
footer p {
|
| 366 |
+
color: #adb5bd;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
/* Responsive Design */
|
| 370 |
+
@media (max-width: 768px) {
|
| 371 |
+
.display-6 {
|
| 372 |
+
font-size: 1.5rem;
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
.lead {
|
| 376 |
+
font-size: 1rem;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
.card-body {
|
| 380 |
+
padding: 1rem;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
.sequence-display {
|
| 384 |
+
font-size: 12px;
|
| 385 |
+
padding: 15px;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
.btn-lg {
|
| 389 |
+
padding: 0.6rem 1.5rem;
|
| 390 |
+
font-size: 1rem;
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
.logo-header {
|
| 394 |
+
max-height: 60px;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
.navbar-brand img {
|
| 398 |
+
height: 25px;
|
| 399 |
+
}
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
@media (max-width: 576px) {
|
| 403 |
+
.container {
|
| 404 |
+
padding-left: 15px;
|
| 405 |
+
padding-right: 15px;
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
.sequence-display {
|
| 409 |
+
font-size: 11px;
|
| 410 |
+
padding: 10px;
|
| 411 |
+
max-height: 200px;
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
.table-responsive {
|
| 415 |
+
font-size: 0.875rem;
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
.logo-header {
|
| 419 |
+
max-height: 50px;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
.navbar-brand img {
|
| 423 |
+
height: 20px;
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
.logo-footer {
|
| 427 |
+
max-height: 30px;
|
| 428 |
+
}
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
/* Loading Animation */
|
| 432 |
+
.spinner-border-sm {
|
| 433 |
+
width: 1rem;
|
| 434 |
+
height: 1rem;
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
/* Utility Classes */
|
| 438 |
+
.shadow-sm {
|
| 439 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08) !important;
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
.text-monospace {
|
| 443 |
+
font-family: 'Courier New', 'Monaco', 'Menlo', monospace !important;
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
/* Hover Effects */
|
| 447 |
+
.epitope-card {
|
| 448 |
+
transition: all 0.3s ease;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
.epitope-card:hover {
|
| 452 |
+
transform: translateY(-3px);
|
| 453 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15) !important;
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
/* Custom Scrollbar */
|
| 457 |
+
.sequence-display::-webkit-scrollbar {
|
| 458 |
+
width: 8px;
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
.sequence-display::-webkit-scrollbar-track {
|
| 462 |
+
background: #f1f1f1;
|
| 463 |
+
border-radius: 4px;
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
.sequence-display::-webkit-scrollbar-thumb {
|
| 467 |
+
background: #c1c1c1;
|
| 468 |
+
border-radius: 4px;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
.sequence-display::-webkit-scrollbar-thumb:hover {
|
| 472 |
+
background: #a8a8a8;
|
| 473 |
+
}
|
static/js/main.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// EpiPred - Main JavaScript Functions
|
| 2 |
+
|
| 3 |
+
$(document).ready(function() {
|
| 4 |
+
// Initialize tooltips
|
| 5 |
+
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
| 6 |
+
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
| 7 |
+
return new bootstrap.Tooltip(tooltipTriggerEl);
|
| 8 |
+
});
|
| 9 |
+
|
| 10 |
+
// Initialize file upload handler
|
| 11 |
+
initFileUpload();
|
| 12 |
+
|
| 13 |
+
// Initialize form validation
|
| 14 |
+
initFormValidation();
|
| 15 |
+
|
| 16 |
+
// Initialize sequence analysis
|
| 17 |
+
initSequenceAnalysis();
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
// File Upload Functionality
|
| 21 |
+
function initFileUpload() {
|
| 22 |
+
const fileInput = document.getElementById('sequence_file');
|
| 23 |
+
const fileInfo = document.getElementById('file-info');
|
| 24 |
+
|
| 25 |
+
if (fileInput && fileInfo) {
|
| 26 |
+
fileInput.addEventListener('change', function(e) {
|
| 27 |
+
if (e.target.files.length > 0) {
|
| 28 |
+
const file = e.target.files[0];
|
| 29 |
+
const size = (file.size / 1024 / 1024).toFixed(2);
|
| 30 |
+
const maxSize = 16; // MB
|
| 31 |
+
|
| 32 |
+
if (file.size > maxSize * 1024 * 1024) {
|
| 33 |
+
showAlert('File size exceeds 16MB limit. Please choose a smaller file.', 'error');
|
| 34 |
+
e.target.value = '';
|
| 35 |
+
fileInfo.innerHTML = '';
|
| 36 |
+
return;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// Check file extension
|
| 40 |
+
const allowedExtensions = ['txt', 'fasta', 'fa', 'fas'];
|
| 41 |
+
const fileExtension = file.name.split('.').pop().toLowerCase();
|
| 42 |
+
|
| 43 |
+
if (!allowedExtensions.includes(fileExtension)) {
|
| 44 |
+
showAlert('Invalid file format. Please upload a .txt, .fasta, .fa, or .fas file.', 'error');
|
| 45 |
+
e.target.value = '';
|
| 46 |
+
fileInfo.innerHTML = '';
|
| 47 |
+
return;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
fileInfo.innerHTML = `<i class="fas fa-file me-1"></i>Selected: ${file.name} (${size} MB)`;
|
| 51 |
+
fileInfo.className = 'mt-2 text-info';
|
| 52 |
+
|
| 53 |
+
// Preview file content if small enough
|
| 54 |
+
if (file.size < 1024 * 1024) { // 1MB
|
| 55 |
+
previewFile(file);
|
| 56 |
+
}
|
| 57 |
+
} else {
|
| 58 |
+
fileInfo.innerHTML = '';
|
| 59 |
+
}
|
| 60 |
+
});
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// Preview uploaded file content
|
| 65 |
+
function previewFile(file) {
|
| 66 |
+
const reader = new FileReader();
|
| 67 |
+
reader.onload = function(e) {
|
| 68 |
+
const content = e.target.result;
|
| 69 |
+
const lines = content.split('\n').slice(0, 10); // First 10 lines
|
| 70 |
+
|
| 71 |
+
if (lines.some(line => line.startsWith('>'))) {
|
| 72 |
+
showAlert('FASTA file detected. Preview looks good!', 'success');
|
| 73 |
+
} else {
|
| 74 |
+
showAlert('Warning: File may not be in FASTA format. Please check your file.', 'warning');
|
| 75 |
+
}
|
| 76 |
+
};
|
| 77 |
+
reader.readAsText(file);
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// Form Validation
|
| 81 |
+
function initFormValidation() {
|
| 82 |
+
const form = document.getElementById('predictionForm');
|
| 83 |
+
|
| 84 |
+
if (form) {
|
| 85 |
+
form.addEventListener('submit', function(e) {
|
| 86 |
+
const sequenceText = document.getElementById('sequence_text').value.trim();
|
| 87 |
+
const sequenceFile = document.getElementById('sequence_file').files.length > 0;
|
| 88 |
+
|
| 89 |
+
// Check if at least one input method is provided
|
| 90 |
+
if (!sequenceText && !sequenceFile) {
|
| 91 |
+
e.preventDefault();
|
| 92 |
+
showAlert('Please provide protein sequences either by pasting text or uploading a file.', 'error');
|
| 93 |
+
return false;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// Validate sequence text if provided
|
| 97 |
+
if (sequenceText) {
|
| 98 |
+
const validation = validateFastaText(sequenceText);
|
| 99 |
+
if (!validation.valid) {
|
| 100 |
+
e.preventDefault();
|
| 101 |
+
showAlert(validation.message, 'error');
|
| 102 |
+
return false;
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
// Show loading state
|
| 107 |
+
showLoadingState();
|
| 108 |
+
});
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// Validate FASTA text format
|
| 113 |
+
function validateFastaText(text) {
|
| 114 |
+
const lines = text.split('\n').filter(line => line.trim());
|
| 115 |
+
|
| 116 |
+
if (lines.length === 0) {
|
| 117 |
+
return { valid: false, message: 'Please enter some sequence data.' };
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
let hasHeader = false;
|
| 121 |
+
let hasSequence = false;
|
| 122 |
+
let sequenceCount = 0;
|
| 123 |
+
let totalLength = 0;
|
| 124 |
+
|
| 125 |
+
for (let i = 0; i < lines.length; i++) {
|
| 126 |
+
const line = lines[i].trim();
|
| 127 |
+
|
| 128 |
+
if (line.startsWith('>')) {
|
| 129 |
+
hasHeader = true;
|
| 130 |
+
sequenceCount++;
|
| 131 |
+
|
| 132 |
+
if (sequenceCount > 50) {
|
| 133 |
+
return { valid: false, message: 'Maximum 50 sequences allowed per submission.' };
|
| 134 |
+
}
|
| 135 |
+
} else if (line.length > 0) {
|
| 136 |
+
hasSequence = true;
|
| 137 |
+
totalLength += line.length;
|
| 138 |
+
|
| 139 |
+
// Check for invalid characters
|
| 140 |
+
const validAA = /^[ACDEFGHIKLMNPQRSTVWY]+$/i;
|
| 141 |
+
if (!validAA.test(line)) {
|
| 142 |
+
return { valid: false, message: `Invalid amino acid characters found in sequence. Only standard amino acids are allowed.` };
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
if (!hasHeader) {
|
| 148 |
+
return { valid: false, message: 'FASTA format requires header lines starting with ">". Please check your format.' };
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
if (!hasSequence) {
|
| 152 |
+
return { valid: false, message: 'No sequence data found. Please provide amino acid sequences.' };
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
if (totalLength > 300000) {
|
| 156 |
+
return { valid: false, message: 'Total sequence length exceeds 300,000 amino acids limit.' };
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
return { valid: true, message: 'Validation passed.' };
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
// Show loading state during form submission
|
| 163 |
+
function showLoadingState() {
|
| 164 |
+
const submitBtn = document.getElementById('submitBtn');
|
| 165 |
+
if (submitBtn) {
|
| 166 |
+
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Processing...';
|
| 167 |
+
submitBtn.disabled = true;
|
| 168 |
+
|
| 169 |
+
// Add progress indicator
|
| 170 |
+
const progressHtml = `
|
| 171 |
+
<div class="mt-3 text-center" id="loadingProgress">
|
| 172 |
+
<div class="spinner-border text-primary" role="status">
|
| 173 |
+
<span class="visually-hidden">Loading...</span>
|
| 174 |
+
</div>
|
| 175 |
+
<p class="mt-2 text-muted">Analyzing sequences... This may take a few minutes.</p>
|
| 176 |
+
</div>
|
| 177 |
+
`;
|
| 178 |
+
|
| 179 |
+
const form = document.getElementById('predictionForm');
|
| 180 |
+
if (form) {
|
| 181 |
+
form.insertAdjacentHTML('afterend', progressHtml);
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
// Sequence Analysis Functions
|
| 187 |
+
function initSequenceAnalysis() {
|
| 188 |
+
// Real-time sequence statistics
|
| 189 |
+
const sequenceTextarea = document.getElementById('sequence_text');
|
| 190 |
+
if (sequenceTextarea) {
|
| 191 |
+
sequenceTextarea.addEventListener('input', function(e) {
|
| 192 |
+
updateSequenceStats(e.target.value);
|
| 193 |
+
});
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
// Update sequence statistics in real-time
|
| 198 |
+
function updateSequenceStats(text) {
|
| 199 |
+
const lines = text.split('\n').filter(line => line.trim());
|
| 200 |
+
let sequenceCount = 0;
|
| 201 |
+
let totalLength = 0;
|
| 202 |
+
|
| 203 |
+
for (const line of lines) {
|
| 204 |
+
if (line.trim().startsWith('>')) {
|
| 205 |
+
sequenceCount++;
|
| 206 |
+
} else if (line.trim().length > 0) {
|
| 207 |
+
totalLength += line.trim().length;
|
| 208 |
+
}
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
// Update or create stats display
|
| 212 |
+
let statsDiv = document.getElementById('sequence-stats');
|
| 213 |
+
if (!statsDiv && (sequenceCount > 0 || totalLength > 0)) {
|
| 214 |
+
statsDiv = document.createElement('div');
|
| 215 |
+
statsDiv.id = 'sequence-stats';
|
| 216 |
+
statsDiv.className = 'mt-2 p-2 bg-light rounded';
|
| 217 |
+
|
| 218 |
+
const textarea = document.getElementById('sequence_text');
|
| 219 |
+
textarea.parentNode.insertBefore(statsDiv, textarea.nextSibling);
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
if (statsDiv) {
|
| 223 |
+
if (sequenceCount > 0 || totalLength > 0) {
|
| 224 |
+
const warningClass = sequenceCount > 50 || totalLength > 300000 ? 'text-danger' : 'text-info';
|
| 225 |
+
statsDiv.innerHTML = `
|
| 226 |
+
<small class="${warningClass}">
|
| 227 |
+
<i class="fas fa-info-circle me-1"></i>
|
| 228 |
+
Sequences: ${sequenceCount}/50 | Total length: ${totalLength.toLocaleString()}/300,000 amino acids
|
| 229 |
+
</small>
|
| 230 |
+
`;
|
| 231 |
+
statsDiv.style.display = 'block';
|
| 232 |
+
} else {
|
| 233 |
+
statsDiv.style.display = 'none';
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
// Utility Functions
|
| 239 |
+
function showAlert(message, type = 'info') {
|
| 240 |
+
const alertClass = type === 'error' ? 'alert-danger' :
|
| 241 |
+
type === 'success' ? 'alert-success' :
|
| 242 |
+
type === 'warning' ? 'alert-warning' : 'alert-info';
|
| 243 |
+
|
| 244 |
+
const iconClass = type === 'error' ? 'fa-exclamation-triangle' :
|
| 245 |
+
type === 'success' ? 'fa-check-circle' :
|
| 246 |
+
type === 'warning' ? 'fa-exclamation-triangle' : 'fa-info-circle';
|
| 247 |
+
|
| 248 |
+
const alertHtml = `
|
| 249 |
+
<div class="alert ${alertClass} alert-dismissible fade show mt-3" role="alert">
|
| 250 |
+
<i class="fas ${iconClass} me-2"></i>
|
| 251 |
+
${message}
|
| 252 |
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
| 253 |
+
</div>
|
| 254 |
+
`;
|
| 255 |
+
|
| 256 |
+
// Insert at the top of the main container
|
| 257 |
+
const container = document.querySelector('.container.my-4');
|
| 258 |
+
if (container) {
|
| 259 |
+
container.insertAdjacentHTML('afterbegin', alertHtml);
|
| 260 |
+
|
| 261 |
+
// Auto-dismiss after 5 seconds for success messages
|
| 262 |
+
if (type === 'success') {
|
| 263 |
+
setTimeout(() => {
|
| 264 |
+
const alert = container.querySelector('.alert');
|
| 265 |
+
if (alert) {
|
| 266 |
+
const bsAlert = new bootstrap.Alert(alert);
|
| 267 |
+
bsAlert.close();
|
| 268 |
+
}
|
| 269 |
+
}, 5000);
|
| 270 |
+
}
|
| 271 |
+
}
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
// Copy text to clipboard
|
| 275 |
+
function copyToClipboard(text) {
|
| 276 |
+
if (navigator.clipboard) {
|
| 277 |
+
navigator.clipboard.writeText(text).then(() => {
|
| 278 |
+
showAlert('Copied to clipboard!', 'success');
|
| 279 |
+
}).catch(() => {
|
| 280 |
+
fallbackCopyToClipboard(text);
|
| 281 |
+
});
|
| 282 |
+
} else {
|
| 283 |
+
fallbackCopyToClipboard(text);
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
// Fallback copy function for older browsers
|
| 288 |
+
function fallbackCopyToClipboard(text) {
|
| 289 |
+
const textArea = document.createElement('textarea');
|
| 290 |
+
textArea.value = text;
|
| 291 |
+
textArea.style.position = 'fixed';
|
| 292 |
+
textArea.style.left = '-999999px';
|
| 293 |
+
textArea.style.top = '-999999px';
|
| 294 |
+
document.body.appendChild(textArea);
|
| 295 |
+
textArea.focus();
|
| 296 |
+
textArea.select();
|
| 297 |
+
|
| 298 |
+
try {
|
| 299 |
+
document.execCommand('copy');
|
| 300 |
+
showAlert('Copied to clipboard!', 'success');
|
| 301 |
+
} catch (err) {
|
| 302 |
+
showAlert('Failed to copy to clipboard. Please copy manually.', 'error');
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
document.body.removeChild(textArea);
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
// Format confidence score for display
|
| 309 |
+
function formatConfidence(confidence) {
|
| 310 |
+
return (confidence * 100).toFixed(1) + '%';
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
// Format position range for display
|
| 314 |
+
function formatPositionRange(range) {
|
| 315 |
+
return range.replace('-', ' - ');
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
// Smooth scroll to element
|
| 319 |
+
function scrollToElement(elementId) {
|
| 320 |
+
const element = document.getElementById(elementId);
|
| 321 |
+
if (element) {
|
| 322 |
+
element.scrollIntoView({
|
| 323 |
+
behavior: 'smooth',
|
| 324 |
+
block: 'start'
|
| 325 |
+
});
|
| 326 |
+
}
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
// Export functions for global access
|
| 330 |
+
window.EpiPred = {
|
| 331 |
+
showAlert,
|
| 332 |
+
copyToClipboard,
|
| 333 |
+
formatConfidence,
|
| 334 |
+
formatPositionRange,
|
| 335 |
+
scrollToElement,
|
| 336 |
+
validateFastaText
|
| 337 |
+
};
|
static/logo.png
ADDED
|
Git LFS Details
|
static/uploads/27f92020-4c76-4a4d-a2d7-ae9e5e1688db_results.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"job_id": "27f92020-4c76-4a4d-a2d7-ae9e5e1688db",
|
| 3 |
+
"timestamp": "2025-08-15T05:41:40.475657",
|
| 4 |
+
"results": {
|
| 5 |
+
"Human_Insulin_A_Chain": {
|
| 6 |
+
"sequence": "GIVEQCCTSICSLYQLENYCN",
|
| 7 |
+
"b_cell_epitopes": [],
|
| 8 |
+
"t_cell_epitopes": [],
|
| 9 |
+
"length": 21
|
| 10 |
+
},
|
| 11 |
+
"Human_Insulin_B_Chain": {
|
| 12 |
+
"sequence": "FVNQHLCGSHLVEALYLVCGERGFFYTPKT",
|
| 13 |
+
"b_cell_epitopes": [
|
| 14 |
+
[
|
| 15 |
+
"VNQHLCGSHLVEALYLVCGE",
|
| 16 |
+
0.9999999403953552,
|
| 17 |
+
"2-21"
|
| 18 |
+
],
|
| 19 |
+
[
|
| 20 |
+
"NQHLCGSHLVEALYLVCGER",
|
| 21 |
+
0.9999999403953552,
|
| 22 |
+
"3-22"
|
| 23 |
+
],
|
| 24 |
+
[
|
| 25 |
+
"CGSHLVEALYLVCGERGFFY",
|
| 26 |
+
0.9999999403953552,
|
| 27 |
+
"7-26"
|
| 28 |
+
]
|
| 29 |
+
],
|
| 30 |
+
"t_cell_epitopes": [],
|
| 31 |
+
"length": 30
|
| 32 |
+
},
|
| 33 |
+
"Example_Antigen": {
|
| 34 |
+
"sequence": "MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNELCARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD",
|
| 35 |
+
"b_cell_epitopes": [
|
| 36 |
+
[
|
| 37 |
+
"HQGVMVPGVGVPQALQKYNP",
|
| 38 |
+
1.0,
|
| 39 |
+
"89-108"
|
| 40 |
+
],
|
| 41 |
+
[
|
| 42 |
+
"QGVMVPGVGVPQALQKYNPD",
|
| 43 |
+
1.0,
|
| 44 |
+
"90-109"
|
| 45 |
+
],
|
| 46 |
+
[
|
| 47 |
+
"KLLILTCLVAVALARPKHPI",
|
| 48 |
+
0.9999999403953552,
|
| 49 |
+
"2-21"
|
| 50 |
+
],
|
| 51 |
+
[
|
| 52 |
+
"RPKHPIKHQGLPQEVLNENL",
|
| 53 |
+
0.9999999403953552,
|
| 54 |
+
"16-35"
|
| 55 |
+
],
|
| 56 |
+
[
|
| 57 |
+
"HPIKHQGLPQEVLNENLLRF",
|
| 58 |
+
0.9999999403953552,
|
| 59 |
+
"19-38"
|
| 60 |
+
],
|
| 61 |
+
[
|
| 62 |
+
"VLNENLLRFFVAPFPEVFGK",
|
| 63 |
+
0.9999999403953552,
|
| 64 |
+
"30-49"
|
| 65 |
+
],
|
| 66 |
+
[
|
| 67 |
+
"LNENLLRFFVAPFPEVFGKE",
|
| 68 |
+
0.9999999403953552,
|
| 69 |
+
"31-50"
|
| 70 |
+
],
|
| 71 |
+
[
|
| 72 |
+
"VFGKEKVNELCARFASLIYG",
|
| 73 |
+
0.9999999403953552,
|
| 74 |
+
"46-65"
|
| 75 |
+
],
|
| 76 |
+
[
|
| 77 |
+
"GKEKVNELCARFASLIYGKF",
|
| 78 |
+
0.9999999403953552,
|
| 79 |
+
"48-67"
|
| 80 |
+
],
|
| 81 |
+
[
|
| 82 |
+
"KEKVNELCARFASLIYGKFV",
|
| 83 |
+
0.9999999403953552,
|
| 84 |
+
"49-68"
|
| 85 |
+
],
|
| 86 |
+
[
|
| 87 |
+
"LIYGKFVRQPQVWLRIQNYS",
|
| 88 |
+
0.9999999403953552,
|
| 89 |
+
"62-81"
|
| 90 |
+
],
|
| 91 |
+
[
|
| 92 |
+
"YGKFVRQPQVWLRIQNYSVM",
|
| 93 |
+
0.9999999403953552,
|
| 94 |
+
"64-83"
|
| 95 |
+
],
|
| 96 |
+
[
|
| 97 |
+
"QPQVWLRIQNYSVMDICDEH",
|
| 98 |
+
0.9999999403953552,
|
| 99 |
+
"70-89"
|
| 100 |
+
],
|
| 101 |
+
[
|
| 102 |
+
"PQVWLRIQNYSVMDICDEHQ",
|
| 103 |
+
0.9999999403953552,
|
| 104 |
+
"71-90"
|
| 105 |
+
],
|
| 106 |
+
[
|
| 107 |
+
"RIQNYSVMDICDEHQGVMVP",
|
| 108 |
+
0.9999999403953552,
|
| 109 |
+
"76-95"
|
| 110 |
+
],
|
| 111 |
+
[
|
| 112 |
+
"DEHQGVMVPGVGVPQALQKY",
|
| 113 |
+
0.9999999403953552,
|
| 114 |
+
"87-106"
|
| 115 |
+
]
|
| 116 |
+
],
|
| 117 |
+
"t_cell_epitopes": [],
|
| 118 |
+
"length": 109
|
| 119 |
+
}
|
| 120 |
+
},
|
| 121 |
+
"total_sequences": 3
|
| 122 |
+
}
|
static/uploads/2dbe50b9-c42e-4889-8461-682ddf95b9d7_results.json
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"job_id": "2dbe50b9-c42e-4889-8461-682ddf95b9d7",
|
| 3 |
+
"timestamp": "2025-08-15T06:12:16.214814",
|
| 4 |
+
"results": {
|
| 5 |
+
"Human_Insulin_A_Chain": {
|
| 6 |
+
"sequence": "GIVEQCCTSICSLYQLENYCN",
|
| 7 |
+
"b_cell_epitopes": [],
|
| 8 |
+
"t_cell_epitopes": [],
|
| 9 |
+
"length": 21
|
| 10 |
+
},
|
| 11 |
+
"Human_Insulin_B_Chain": {
|
| 12 |
+
"sequence": "FVNQHLCGSHLVEALYLVCGERGFFYTPKT",
|
| 13 |
+
"b_cell_epitopes": [
|
| 14 |
+
[
|
| 15 |
+
"VNQHLCGSHLVEALYLVCGE",
|
| 16 |
+
0.9999999403953552,
|
| 17 |
+
"2-21"
|
| 18 |
+
],
|
| 19 |
+
[
|
| 20 |
+
"NQHLCGSHLVEALYLVCGER",
|
| 21 |
+
0.9999999403953552,
|
| 22 |
+
"3-22"
|
| 23 |
+
],
|
| 24 |
+
[
|
| 25 |
+
"CGSHLVEALYLVCGERGFFY",
|
| 26 |
+
0.9999999403953552,
|
| 27 |
+
"7-26"
|
| 28 |
+
]
|
| 29 |
+
],
|
| 30 |
+
"t_cell_epitopes": [],
|
| 31 |
+
"length": 30
|
| 32 |
+
},
|
| 33 |
+
"SARS_CoV2_Spike_Protein_Fragment": {
|
| 34 |
+
"sequence": "MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNELCARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD",
|
| 35 |
+
"b_cell_epitopes": [
|
| 36 |
+
[
|
| 37 |
+
"HQGVMVPGVGVPQALQKYNP",
|
| 38 |
+
1.0,
|
| 39 |
+
"89-108"
|
| 40 |
+
],
|
| 41 |
+
[
|
| 42 |
+
"QGVMVPGVGVPQALQKYNPD",
|
| 43 |
+
1.0,
|
| 44 |
+
"90-109"
|
| 45 |
+
],
|
| 46 |
+
[
|
| 47 |
+
"KLLILTCLVAVALARPKHPI",
|
| 48 |
+
0.9999999403953552,
|
| 49 |
+
"2-21"
|
| 50 |
+
],
|
| 51 |
+
[
|
| 52 |
+
"RPKHPIKHQGLPQEVLNENL",
|
| 53 |
+
0.9999999403953552,
|
| 54 |
+
"16-35"
|
| 55 |
+
],
|
| 56 |
+
[
|
| 57 |
+
"HPIKHQGLPQEVLNENLLRF",
|
| 58 |
+
0.9999999403953552,
|
| 59 |
+
"19-38"
|
| 60 |
+
],
|
| 61 |
+
[
|
| 62 |
+
"VLNENLLRFFVAPFPEVFGK",
|
| 63 |
+
0.9999999403953552,
|
| 64 |
+
"30-49"
|
| 65 |
+
],
|
| 66 |
+
[
|
| 67 |
+
"LNENLLRFFVAPFPEVFGKE",
|
| 68 |
+
0.9999999403953552,
|
| 69 |
+
"31-50"
|
| 70 |
+
],
|
| 71 |
+
[
|
| 72 |
+
"VFGKEKVNELCARFASLIYG",
|
| 73 |
+
0.9999999403953552,
|
| 74 |
+
"46-65"
|
| 75 |
+
],
|
| 76 |
+
[
|
| 77 |
+
"GKEKVNELCARFASLIYGKF",
|
| 78 |
+
0.9999999403953552,
|
| 79 |
+
"48-67"
|
| 80 |
+
],
|
| 81 |
+
[
|
| 82 |
+
"KEKVNELCARFASLIYGKFV",
|
| 83 |
+
0.9999999403953552,
|
| 84 |
+
"49-68"
|
| 85 |
+
],
|
| 86 |
+
[
|
| 87 |
+
"LIYGKFVRQPQVWLRIQNYS",
|
| 88 |
+
0.9999999403953552,
|
| 89 |
+
"62-81"
|
| 90 |
+
],
|
| 91 |
+
[
|
| 92 |
+
"YGKFVRQPQVWLRIQNYSVM",
|
| 93 |
+
0.9999999403953552,
|
| 94 |
+
"64-83"
|
| 95 |
+
],
|
| 96 |
+
[
|
| 97 |
+
"QPQVWLRIQNYSVMDICDEH",
|
| 98 |
+
0.9999999403953552,
|
| 99 |
+
"70-89"
|
| 100 |
+
],
|
| 101 |
+
[
|
| 102 |
+
"PQVWLRIQNYSVMDICDEHQ",
|
| 103 |
+
0.9999999403953552,
|
| 104 |
+
"71-90"
|
| 105 |
+
],
|
| 106 |
+
[
|
| 107 |
+
"RIQNYSVMDICDEHQGVMVP",
|
| 108 |
+
0.9999999403953552,
|
| 109 |
+
"76-95"
|
| 110 |
+
],
|
| 111 |
+
[
|
| 112 |
+
"DEHQGVMVPGVGVPQALQKY",
|
| 113 |
+
0.9999999403953552,
|
| 114 |
+
"87-106"
|
| 115 |
+
]
|
| 116 |
+
],
|
| 117 |
+
"t_cell_epitopes": [],
|
| 118 |
+
"length": 109
|
| 119 |
+
},
|
| 120 |
+
"HIV_gp120_Fragment": {
|
| 121 |
+
"sequence": "AVNQVVDLMAHMASKDNRAHGDNKFRNRNQRKQRNSTRHIQLGLPAAMADVLFVLFGAATGFHSGGIASYNFPKATLQSQVKELQAAQARLGADMEDVCGRLVQRGNQVA",
|
| 122 |
+
"b_cell_epitopes": [
|
| 123 |
+
[
|
| 124 |
+
"HMASKDNRAHGDNKFRNRNQ",
|
| 125 |
+
0.9999999403953552,
|
| 126 |
+
"11-30"
|
| 127 |
+
],
|
| 128 |
+
[
|
| 129 |
+
"DNKFRNRNQRKQRNSTRHIQ",
|
| 130 |
+
0.9999999403953552,
|
| 131 |
+
"22-41"
|
| 132 |
+
],
|
| 133 |
+
[
|
| 134 |
+
"RNRNQRKQRNSTRHIQLGLP",
|
| 135 |
+
0.9999999403953552,
|
| 136 |
+
"26-45"
|
| 137 |
+
],
|
| 138 |
+
[
|
| 139 |
+
"RNQRKQRNSTRHIQLGLPAA",
|
| 140 |
+
0.9999999403953552,
|
| 141 |
+
"28-47"
|
| 142 |
+
],
|
| 143 |
+
[
|
| 144 |
+
"LPAAMADVLFVLFGAATGFH",
|
| 145 |
+
0.9999999403953552,
|
| 146 |
+
"44-63"
|
| 147 |
+
],
|
| 148 |
+
[
|
| 149 |
+
"PAAMADVLFVLFGAATGFHS",
|
| 150 |
+
0.9999999403953552,
|
| 151 |
+
"45-64"
|
| 152 |
+
],
|
| 153 |
+
[
|
| 154 |
+
"AAMADVLFVLFGAATGFHSG",
|
| 155 |
+
0.9999999403953552,
|
| 156 |
+
"46-65"
|
| 157 |
+
],
|
| 158 |
+
[
|
| 159 |
+
"AMADVLFVLFGAATGFHSGG",
|
| 160 |
+
0.9999999403953552,
|
| 161 |
+
"47-66"
|
| 162 |
+
],
|
| 163 |
+
[
|
| 164 |
+
"GFHSGGIASYNFPKATLQSQ",
|
| 165 |
+
0.9999999403953552,
|
| 166 |
+
"61-80"
|
| 167 |
+
],
|
| 168 |
+
[
|
| 169 |
+
"FHSGGIASYNFPKATLQSQV",
|
| 170 |
+
0.9999999403953552,
|
| 171 |
+
"62-81"
|
| 172 |
+
],
|
| 173 |
+
[
|
| 174 |
+
"HSGGIASYNFPKATLQSQVK",
|
| 175 |
+
0.9999999403953552,
|
| 176 |
+
"63-82"
|
| 177 |
+
],
|
| 178 |
+
[
|
| 179 |
+
"KELQAAQARLGADMEDVCGR",
|
| 180 |
+
0.9999999403953552,
|
| 181 |
+
"82-101"
|
| 182 |
+
]
|
| 183 |
+
],
|
| 184 |
+
"t_cell_epitopes": [],
|
| 185 |
+
"length": 110
|
| 186 |
+
},
|
| 187 |
+
"Influenza_Hemagglutinin_Fragment": {
|
| 188 |
+
"sequence": "YYRGISALQLEECDFFPQHIQVQVQLEQVVGVVKPLLVQVQHQDYLAAVSVAQLSRRELEKASREAIIAPMGLAVPEWLSFQRGDVVAITLPKNAGDGTFIDVQCHTT",
|
| 189 |
+
"b_cell_epitopes": [
|
| 190 |
+
[
|
| 191 |
+
"IQVQVQLEQVVGVVKPLLVQ",
|
| 192 |
+
0.9999999403953552,
|
| 193 |
+
"20-39"
|
| 194 |
+
],
|
| 195 |
+
[
|
| 196 |
+
"QVQVQLEQVVGVVKPLLVQV",
|
| 197 |
+
0.9999999403953552,
|
| 198 |
+
"21-40"
|
| 199 |
+
],
|
| 200 |
+
[
|
| 201 |
+
"VQVQLEQVVGVVKPLLVQVQ",
|
| 202 |
+
0.9999999403953552,
|
| 203 |
+
"22-41"
|
| 204 |
+
],
|
| 205 |
+
[
|
| 206 |
+
"LLVQVQHQDYLAAVSVAQLS",
|
| 207 |
+
0.9999999403953552,
|
| 208 |
+
"36-55"
|
| 209 |
+
],
|
| 210 |
+
[
|
| 211 |
+
"VQVQHQDYLAAVSVAQLSRR",
|
| 212 |
+
0.9999999403953552,
|
| 213 |
+
"38-57"
|
| 214 |
+
],
|
| 215 |
+
[
|
| 216 |
+
"HQDYLAAVSVAQLSRRELEK",
|
| 217 |
+
0.9999999403953552,
|
| 218 |
+
"42-61"
|
| 219 |
+
],
|
| 220 |
+
[
|
| 221 |
+
"AAVSVAQLSRRELEKASREA",
|
| 222 |
+
0.9999999403953552,
|
| 223 |
+
"47-66"
|
| 224 |
+
],
|
| 225 |
+
[
|
| 226 |
+
"SVAQLSRRELEKASREAIIA",
|
| 227 |
+
0.9999999403953552,
|
| 228 |
+
"50-69"
|
| 229 |
+
],
|
| 230 |
+
[
|
| 231 |
+
"LSRRELEKASREAIIAPMGL",
|
| 232 |
+
0.9999999403953552,
|
| 233 |
+
"54-73"
|
| 234 |
+
],
|
| 235 |
+
[
|
| 236 |
+
"RELEKASREAIIAPMGLAVP",
|
| 237 |
+
0.9999999403953552,
|
| 238 |
+
"57-76"
|
| 239 |
+
],
|
| 240 |
+
[
|
| 241 |
+
"SREAIIAPMGLAVPEWLSFQ",
|
| 242 |
+
0.9999999403953552,
|
| 243 |
+
"63-82"
|
| 244 |
+
],
|
| 245 |
+
[
|
| 246 |
+
"REAIIAPMGLAVPEWLSFQR",
|
| 247 |
+
0.9999999403953552,
|
| 248 |
+
"64-83"
|
| 249 |
+
],
|
| 250 |
+
[
|
| 251 |
+
"EAIIAPMGLAVPEWLSFQRG",
|
| 252 |
+
0.9999999403953552,
|
| 253 |
+
"65-84"
|
| 254 |
+
],
|
| 255 |
+
[
|
| 256 |
+
"IIAPMGLAVPEWLSFQRGDV",
|
| 257 |
+
0.9999999403953552,
|
| 258 |
+
"67-86"
|
| 259 |
+
],
|
| 260 |
+
[
|
| 261 |
+
"IAPMGLAVPEWLSFQRGDVV",
|
| 262 |
+
0.9999999403953552,
|
| 263 |
+
"68-87"
|
| 264 |
+
]
|
| 265 |
+
],
|
| 266 |
+
"t_cell_epitopes": [],
|
| 267 |
+
"length": 108
|
| 268 |
+
},
|
| 269 |
+
"Hepatitis_B_Surface_Antigen_Fragment": {
|
| 270 |
+
"sequence": "MGGWSSKPRQGMGTNLSVPNPLGFFPDHQLDPAFGANSNNPDWDFNPNKDHWPEANKVGAGAFGPGFTPPHGGLLGWSPQAQGILQTLPGANPPDEAQFPLPKSATKG",
|
| 271 |
+
"b_cell_epitopes": [
|
| 272 |
+
[
|
| 273 |
+
"PRQGMGTNLSVPNPLGFFPD",
|
| 274 |
+
0.9999999403953552,
|
| 275 |
+
"8-27"
|
| 276 |
+
],
|
| 277 |
+
[
|
| 278 |
+
"NPLGFFPDHQLDPAFGANSN",
|
| 279 |
+
0.9999999403953552,
|
| 280 |
+
"20-39"
|
| 281 |
+
],
|
| 282 |
+
[
|
| 283 |
+
"LDPAFGANSNNPDWDFNPNK",
|
| 284 |
+
0.9999999403953552,
|
| 285 |
+
"30-49"
|
| 286 |
+
],
|
| 287 |
+
[
|
| 288 |
+
"WDFNPNKDHWPEANKVGAGA",
|
| 289 |
+
0.9999999403953552,
|
| 290 |
+
"43-62"
|
| 291 |
+
],
|
| 292 |
+
[
|
| 293 |
+
"NKVGAGAFGPGFTPPHGGLL",
|
| 294 |
+
0.9999999403953552,
|
| 295 |
+
"56-75"
|
| 296 |
+
],
|
| 297 |
+
[
|
| 298 |
+
"KVGAGAFGPGFTPPHGGLLG",
|
| 299 |
+
0.9999999403953552,
|
| 300 |
+
"57-76"
|
| 301 |
+
],
|
| 302 |
+
[
|
| 303 |
+
"AGAFGPGFTPPHGGLLGWSP",
|
| 304 |
+
0.9999999403953552,
|
| 305 |
+
"60-79"
|
| 306 |
+
],
|
| 307 |
+
[
|
| 308 |
+
"FGPGFTPPHGGLLGWSPQAQ",
|
| 309 |
+
0.9999999403953552,
|
| 310 |
+
"63-82"
|
| 311 |
+
]
|
| 312 |
+
],
|
| 313 |
+
"t_cell_epitopes": [],
|
| 314 |
+
"length": 108
|
| 315 |
+
},
|
| 316 |
+
"Malaria_CSP_Fragment": {
|
| 317 |
+
"sequence": "NANPNANPNANPNANPNANPNANPNANPNANPNANPNANPNANPNANPNANPNANPKLKQPADGNPDPNANPNVDPNANPNVDPNANPNVDPNANPNVDPNANPNVDPNAN",
|
| 318 |
+
"b_cell_epitopes": [
|
| 319 |
+
[
|
| 320 |
+
"PNANPNANPNANPKLKQPAD",
|
| 321 |
+
0.9999999403953552,
|
| 322 |
+
"44-63"
|
| 323 |
+
],
|
| 324 |
+
[
|
| 325 |
+
"NANPNANPNANPKLKQPADG",
|
| 326 |
+
0.9999999403953552,
|
| 327 |
+
"45-64"
|
| 328 |
+
],
|
| 329 |
+
[
|
| 330 |
+
"ANPKLKQPADGNPDPNANPN",
|
| 331 |
+
0.9999999403953552,
|
| 332 |
+
"54-73"
|
| 333 |
+
],
|
| 334 |
+
[
|
| 335 |
+
"QPADGNPDPNANPNVDPNAN",
|
| 336 |
+
0.9999999403953552,
|
| 337 |
+
"60-79"
|
| 338 |
+
]
|
| 339 |
+
],
|
| 340 |
+
"t_cell_epitopes": [],
|
| 341 |
+
"length": 111
|
| 342 |
+
},
|
| 343 |
+
"Tuberculosis_Antigen_85B_Fragment": {
|
| 344 |
+
"sequence": "MTEQQWNFAGIEAAASAIQGNVTSIHSLLDEGKQSLTKLAAAWGGSGSEAYQGVQQKWDATATELNNALQNLARTISEAGQAMASTEGNVTGMFAHLNQNGRDQIYVVPV",
|
| 345 |
+
"b_cell_epitopes": [
|
| 346 |
+
[
|
| 347 |
+
"MTEQQWNFAGIEAAASAIQG",
|
| 348 |
+
0.9999999403953552,
|
| 349 |
+
"1-20"
|
| 350 |
+
],
|
| 351 |
+
[
|
| 352 |
+
"TEQQWNFAGIEAAASAIQGN",
|
| 353 |
+
0.9999999403953552,
|
| 354 |
+
"2-21"
|
| 355 |
+
],
|
| 356 |
+
[
|
| 357 |
+
"GIEAAASAIQGNVTSIHSLL",
|
| 358 |
+
0.9999999403953552,
|
| 359 |
+
"10-29"
|
| 360 |
+
],
|
| 361 |
+
[
|
| 362 |
+
"IEAAASAIQGNVTSIHSLLD",
|
| 363 |
+
0.9999999403953552,
|
| 364 |
+
"11-30"
|
| 365 |
+
],
|
| 366 |
+
[
|
| 367 |
+
"EAAASAIQGNVTSIHSLLDE",
|
| 368 |
+
0.9999999403953552,
|
| 369 |
+
"12-31"
|
| 370 |
+
],
|
| 371 |
+
[
|
| 372 |
+
"SAIQGNVTSIHSLLDEGKQS",
|
| 373 |
+
0.9999999403953552,
|
| 374 |
+
"16-35"
|
| 375 |
+
],
|
| 376 |
+
[
|
| 377 |
+
"GNVTSIHSLLDEGKQSLTKL",
|
| 378 |
+
0.9999999403953552,
|
| 379 |
+
"20-39"
|
| 380 |
+
],
|
| 381 |
+
[
|
| 382 |
+
"EGKQSLTKLAAAWGGSGSEA",
|
| 383 |
+
0.9999999403953552,
|
| 384 |
+
"31-50"
|
| 385 |
+
],
|
| 386 |
+
[
|
| 387 |
+
"GKQSLTKLAAAWGGSGSEAY",
|
| 388 |
+
0.9999999403953552,
|
| 389 |
+
"32-51"
|
| 390 |
+
],
|
| 391 |
+
[
|
| 392 |
+
"TKLAAAWGGSGSEAYQGVQQ",
|
| 393 |
+
0.9999999403953552,
|
| 394 |
+
"37-56"
|
| 395 |
+
],
|
| 396 |
+
[
|
| 397 |
+
"GGSGSEAYQGVQQKWDATAT",
|
| 398 |
+
0.9999999403953552,
|
| 399 |
+
"44-63"
|
| 400 |
+
],
|
| 401 |
+
[
|
| 402 |
+
"GSEAYQGVQQKWDATATELN",
|
| 403 |
+
0.9999999403953552,
|
| 404 |
+
"47-66"
|
| 405 |
+
],
|
| 406 |
+
[
|
| 407 |
+
"DATATELNNALQNLARTISE",
|
| 408 |
+
0.9999999403953552,
|
| 409 |
+
"59-78"
|
| 410 |
+
],
|
| 411 |
+
[
|
| 412 |
+
"NLARTISEAGQAMASTEGNV",
|
| 413 |
+
0.9999999403953552,
|
| 414 |
+
"71-90"
|
| 415 |
+
],
|
| 416 |
+
[
|
| 417 |
+
"LARTISEAGQAMASTEGNVT",
|
| 418 |
+
0.9999999403953552,
|
| 419 |
+
"72-91"
|
| 420 |
+
],
|
| 421 |
+
[
|
| 422 |
+
"QAMASTEGNVTGMFAHLNQN",
|
| 423 |
+
0.9999999403953552,
|
| 424 |
+
"81-100"
|
| 425 |
+
]
|
| 426 |
+
],
|
| 427 |
+
"t_cell_epitopes": [],
|
| 428 |
+
"length": 110
|
| 429 |
+
},
|
| 430 |
+
"Cancer_p53_Tumor_Suppressor_Fragment": {
|
| 431 |
+
"sequence": "MEEPQSDPSVEPPLSQETFSDLWKLLPENNVLSPLPSQAMDDLMLSPDDIEQWFTEDPGPDEAPRMPEAAPPVAPAPAAPTPAAPAPAPSWPLSSSVPSQKTYQGSYGFR",
|
| 432 |
+
"b_cell_epitopes": [
|
| 433 |
+
[
|
| 434 |
+
"SWPLSSSVPSQKTYQGSYGF",
|
| 435 |
+
1.0,
|
| 436 |
+
"90-109"
|
| 437 |
+
],
|
| 438 |
+
[
|
| 439 |
+
"LSQETFSDLWKLLPENNVLS",
|
| 440 |
+
0.9999999403953552,
|
| 441 |
+
"14-33"
|
| 442 |
+
],
|
| 443 |
+
[
|
| 444 |
+
"ETFSDLWKLLPENNVLSPLP",
|
| 445 |
+
0.9999999403953552,
|
| 446 |
+
"17-36"
|
| 447 |
+
],
|
| 448 |
+
[
|
| 449 |
+
"TFSDLWKLLPENNVLSPLPS",
|
| 450 |
+
0.9999999403953552,
|
| 451 |
+
"18-37"
|
| 452 |
+
],
|
| 453 |
+
[
|
| 454 |
+
"WKLLPENNVLSPLPSQAMDD",
|
| 455 |
+
0.9999999403953552,
|
| 456 |
+
"23-42"
|
| 457 |
+
],
|
| 458 |
+
[
|
| 459 |
+
"NNVLSPLPSQAMDDLMLSPD",
|
| 460 |
+
0.9999999403953552,
|
| 461 |
+
"29-48"
|
| 462 |
+
],
|
| 463 |
+
[
|
| 464 |
+
"PAPAAPTPAAPAPAPSWPLS",
|
| 465 |
+
0.9999999403953552,
|
| 466 |
+
"75-94"
|
| 467 |
+
],
|
| 468 |
+
[
|
| 469 |
+
"TPAAPAPAPSWPLSSSVPSQ",
|
| 470 |
+
0.9999999403953552,
|
| 471 |
+
"81-100"
|
| 472 |
+
],
|
| 473 |
+
[
|
| 474 |
+
"APAPAPSWPLSSSVPSQKTY",
|
| 475 |
+
0.9999999403953552,
|
| 476 |
+
"84-103"
|
| 477 |
+
],
|
| 478 |
+
[
|
| 479 |
+
"APAPSWPLSSSVPSQKTYQG",
|
| 480 |
+
0.9999999403953552,
|
| 481 |
+
"86-105"
|
| 482 |
+
],
|
| 483 |
+
[
|
| 484 |
+
"PAPSWPLSSSVPSQKTYQGS",
|
| 485 |
+
0.9999999403953552,
|
| 486 |
+
"87-106"
|
| 487 |
+
],
|
| 488 |
+
[
|
| 489 |
+
"APSWPLSSSVPSQKTYQGSY",
|
| 490 |
+
0.9999999403953552,
|
| 491 |
+
"88-107"
|
| 492 |
+
]
|
| 493 |
+
],
|
| 494 |
+
"t_cell_epitopes": [],
|
| 495 |
+
"length": 110
|
| 496 |
+
},
|
| 497 |
+
"Zika_Virus_Envelope_Protein_Fragment": {
|
| 498 |
+
"sequence": "MRCVGIGNRDFVEGLSGATWVDVVLEHGSCVTTMAKNKPTLDFELIKTEAKQPATLRKYCIEAKLTNTTTESRCPTQGEPSLNEEQDKRFVCKHSMVDRGWGNGCGLFG",
|
| 499 |
+
"b_cell_epitopes": [
|
| 500 |
+
[
|
| 501 |
+
"NRDFVEGLSGATWVDVVLEH",
|
| 502 |
+
0.9999999403953552,
|
| 503 |
+
"8-27"
|
| 504 |
+
],
|
| 505 |
+
[
|
| 506 |
+
"RDFVEGLSGATWVDVVLEHG",
|
| 507 |
+
0.9999999403953552,
|
| 508 |
+
"9-28"
|
| 509 |
+
],
|
| 510 |
+
[
|
| 511 |
+
"TWVDVVLEHGSCVTTMAKNK",
|
| 512 |
+
0.9999999403953552,
|
| 513 |
+
"19-38"
|
| 514 |
+
],
|
| 515 |
+
[
|
| 516 |
+
"VLEHGSCVTTMAKNKPTLDF",
|
| 517 |
+
0.9999999403953552,
|
| 518 |
+
"24-43"
|
| 519 |
+
],
|
| 520 |
+
[
|
| 521 |
+
"PTLDFELIKTEAKQPATLRK",
|
| 522 |
+
0.9999999403953552,
|
| 523 |
+
"39-58"
|
| 524 |
+
],
|
| 525 |
+
[
|
| 526 |
+
"TLDFELIKTEAKQPATLRKY",
|
| 527 |
+
0.9999999403953552,
|
| 528 |
+
"40-59"
|
| 529 |
+
],
|
| 530 |
+
[
|
| 531 |
+
"IKTEAKQPATLRKYCIEAKL",
|
| 532 |
+
0.9999999403953552,
|
| 533 |
+
"46-65"
|
| 534 |
+
],
|
| 535 |
+
[
|
| 536 |
+
"AKQPATLRKYCIEAKLTNTT",
|
| 537 |
+
0.9999999403953552,
|
| 538 |
+
"50-69"
|
| 539 |
+
],
|
| 540 |
+
[
|
| 541 |
+
"KQPATLRKYCIEAKLTNTTT",
|
| 542 |
+
0.9999999403953552,
|
| 543 |
+
"51-70"
|
| 544 |
+
],
|
| 545 |
+
[
|
| 546 |
+
"QPATLRKYCIEAKLTNTTTE",
|
| 547 |
+
0.9999999403953552,
|
| 548 |
+
"52-71"
|
| 549 |
+
],
|
| 550 |
+
[
|
| 551 |
+
"PATLRKYCIEAKLTNTTTES",
|
| 552 |
+
0.9999999403953552,
|
| 553 |
+
"53-72"
|
| 554 |
+
]
|
| 555 |
+
],
|
| 556 |
+
"t_cell_epitopes": [],
|
| 557 |
+
"length": 109
|
| 558 |
+
}
|
| 559 |
+
},
|
| 560 |
+
"total_sequences": 10
|
| 561 |
+
}
|
static/uploads/c803e953-00e7-49e3-ae1c-4228f3e7f02f_results.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"job_id": "c803e953-00e7-49e3-ae1c-4228f3e7f02f",
|
| 3 |
+
"timestamp": "2025-08-15T05:57:31.609950",
|
| 4 |
+
"results": {
|
| 5 |
+
"Human_Insulin_A_Chain": {
|
| 6 |
+
"sequence": "GIVEQCCTSICSLYQLENYCN",
|
| 7 |
+
"b_cell_epitopes": [],
|
| 8 |
+
"t_cell_epitopes": [],
|
| 9 |
+
"length": 21
|
| 10 |
+
},
|
| 11 |
+
"Human_Insulin_B_Chain": {
|
| 12 |
+
"sequence": "FVNQHLCGSHLVEALYLVCGERGFFYTPKT",
|
| 13 |
+
"b_cell_epitopes": [
|
| 14 |
+
[
|
| 15 |
+
"VNQHLCGSHLVEALYLVCGE",
|
| 16 |
+
0.9999999403953552,
|
| 17 |
+
"2-21"
|
| 18 |
+
],
|
| 19 |
+
[
|
| 20 |
+
"NQHLCGSHLVEALYLVCGER",
|
| 21 |
+
0.9999999403953552,
|
| 22 |
+
"3-22"
|
| 23 |
+
],
|
| 24 |
+
[
|
| 25 |
+
"CGSHLVEALYLVCGERGFFY",
|
| 26 |
+
0.9999999403953552,
|
| 27 |
+
"7-26"
|
| 28 |
+
]
|
| 29 |
+
],
|
| 30 |
+
"t_cell_epitopes": [],
|
| 31 |
+
"length": 30
|
| 32 |
+
},
|
| 33 |
+
"Example_Antigen": {
|
| 34 |
+
"sequence": "MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNELCARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD",
|
| 35 |
+
"b_cell_epitopes": [
|
| 36 |
+
[
|
| 37 |
+
"HQGVMVPGVGVPQALQKYNP",
|
| 38 |
+
1.0,
|
| 39 |
+
"89-108"
|
| 40 |
+
],
|
| 41 |
+
[
|
| 42 |
+
"QGVMVPGVGVPQALQKYNPD",
|
| 43 |
+
1.0,
|
| 44 |
+
"90-109"
|
| 45 |
+
],
|
| 46 |
+
[
|
| 47 |
+
"KLLILTCLVAVALARPKHPI",
|
| 48 |
+
0.9999999403953552,
|
| 49 |
+
"2-21"
|
| 50 |
+
],
|
| 51 |
+
[
|
| 52 |
+
"RPKHPIKHQGLPQEVLNENL",
|
| 53 |
+
0.9999999403953552,
|
| 54 |
+
"16-35"
|
| 55 |
+
],
|
| 56 |
+
[
|
| 57 |
+
"HPIKHQGLPQEVLNENLLRF",
|
| 58 |
+
0.9999999403953552,
|
| 59 |
+
"19-38"
|
| 60 |
+
],
|
| 61 |
+
[
|
| 62 |
+
"VLNENLLRFFVAPFPEVFGK",
|
| 63 |
+
0.9999999403953552,
|
| 64 |
+
"30-49"
|
| 65 |
+
],
|
| 66 |
+
[
|
| 67 |
+
"LNENLLRFFVAPFPEVFGKE",
|
| 68 |
+
0.9999999403953552,
|
| 69 |
+
"31-50"
|
| 70 |
+
],
|
| 71 |
+
[
|
| 72 |
+
"VFGKEKVNELCARFASLIYG",
|
| 73 |
+
0.9999999403953552,
|
| 74 |
+
"46-65"
|
| 75 |
+
],
|
| 76 |
+
[
|
| 77 |
+
"GKEKVNELCARFASLIYGKF",
|
| 78 |
+
0.9999999403953552,
|
| 79 |
+
"48-67"
|
| 80 |
+
],
|
| 81 |
+
[
|
| 82 |
+
"KEKVNELCARFASLIYGKFV",
|
| 83 |
+
0.9999999403953552,
|
| 84 |
+
"49-68"
|
| 85 |
+
],
|
| 86 |
+
[
|
| 87 |
+
"LIYGKFVRQPQVWLRIQNYS",
|
| 88 |
+
0.9999999403953552,
|
| 89 |
+
"62-81"
|
| 90 |
+
],
|
| 91 |
+
[
|
| 92 |
+
"YGKFVRQPQVWLRIQNYSVM",
|
| 93 |
+
0.9999999403953552,
|
| 94 |
+
"64-83"
|
| 95 |
+
],
|
| 96 |
+
[
|
| 97 |
+
"QPQVWLRIQNYSVMDICDEH",
|
| 98 |
+
0.9999999403953552,
|
| 99 |
+
"70-89"
|
| 100 |
+
],
|
| 101 |
+
[
|
| 102 |
+
"PQVWLRIQNYSVMDICDEHQ",
|
| 103 |
+
0.9999999403953552,
|
| 104 |
+
"71-90"
|
| 105 |
+
],
|
| 106 |
+
[
|
| 107 |
+
"RIQNYSVMDICDEHQGVMVP",
|
| 108 |
+
0.9999999403953552,
|
| 109 |
+
"76-95"
|
| 110 |
+
],
|
| 111 |
+
[
|
| 112 |
+
"DEHQGVMVPGVGVPQALQKY",
|
| 113 |
+
0.9999999403953552,
|
| 114 |
+
"87-106"
|
| 115 |
+
]
|
| 116 |
+
],
|
| 117 |
+
"t_cell_epitopes": [],
|
| 118 |
+
"length": 109
|
| 119 |
+
}
|
| 120 |
+
},
|
| 121 |
+
"total_sequences": 3
|
| 122 |
+
}
|
static/uploads/d5dc50da-3f8b-4277-a90a-4711f03435a4_results.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"job_id": "d5dc50da-3f8b-4277-a90a-4711f03435a4",
|
| 3 |
+
"timestamp": "2025-08-15T06:14:29.195288",
|
| 4 |
+
"results": {
|
| 5 |
+
"Human_Insulin_A_Chain": {
|
| 6 |
+
"sequence": "GIVEQCCTSICSLYQLENYCN",
|
| 7 |
+
"b_cell_epitopes": [],
|
| 8 |
+
"t_cell_epitopes": [],
|
| 9 |
+
"length": 21
|
| 10 |
+
},
|
| 11 |
+
"Human_Insulin_B_Chain": {
|
| 12 |
+
"sequence": "FVNQHLCGSHLVEALYLVCGERGFFYTPKT",
|
| 13 |
+
"b_cell_epitopes": [
|
| 14 |
+
[
|
| 15 |
+
"VNQHLCGSHLVEALYLVCGE",
|
| 16 |
+
0.9999999403953552,
|
| 17 |
+
"2-21"
|
| 18 |
+
],
|
| 19 |
+
[
|
| 20 |
+
"NQHLCGSHLVEALYLVCGER",
|
| 21 |
+
0.9999999403953552,
|
| 22 |
+
"3-22"
|
| 23 |
+
],
|
| 24 |
+
[
|
| 25 |
+
"CGSHLVEALYLVCGERGFFY",
|
| 26 |
+
0.9999999403953552,
|
| 27 |
+
"7-26"
|
| 28 |
+
]
|
| 29 |
+
],
|
| 30 |
+
"t_cell_epitopes": [],
|
| 31 |
+
"length": 30
|
| 32 |
+
},
|
| 33 |
+
"Example_Antigen": {
|
| 34 |
+
"sequence": "MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNELCARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD",
|
| 35 |
+
"b_cell_epitopes": [
|
| 36 |
+
[
|
| 37 |
+
"HQGVMVPGVGVPQALQKYNP",
|
| 38 |
+
1.0,
|
| 39 |
+
"89-108"
|
| 40 |
+
],
|
| 41 |
+
[
|
| 42 |
+
"QGVMVPGVGVPQALQKYNPD",
|
| 43 |
+
1.0,
|
| 44 |
+
"90-109"
|
| 45 |
+
],
|
| 46 |
+
[
|
| 47 |
+
"KLLILTCLVAVALARPKHPI",
|
| 48 |
+
0.9999999403953552,
|
| 49 |
+
"2-21"
|
| 50 |
+
],
|
| 51 |
+
[
|
| 52 |
+
"RPKHPIKHQGLPQEVLNENL",
|
| 53 |
+
0.9999999403953552,
|
| 54 |
+
"16-35"
|
| 55 |
+
],
|
| 56 |
+
[
|
| 57 |
+
"HPIKHQGLPQEVLNENLLRF",
|
| 58 |
+
0.9999999403953552,
|
| 59 |
+
"19-38"
|
| 60 |
+
],
|
| 61 |
+
[
|
| 62 |
+
"VLNENLLRFFVAPFPEVFGK",
|
| 63 |
+
0.9999999403953552,
|
| 64 |
+
"30-49"
|
| 65 |
+
],
|
| 66 |
+
[
|
| 67 |
+
"LNENLLRFFVAPFPEVFGKE",
|
| 68 |
+
0.9999999403953552,
|
| 69 |
+
"31-50"
|
| 70 |
+
],
|
| 71 |
+
[
|
| 72 |
+
"VFGKEKVNELCARFASLIYG",
|
| 73 |
+
0.9999999403953552,
|
| 74 |
+
"46-65"
|
| 75 |
+
],
|
| 76 |
+
[
|
| 77 |
+
"GKEKVNELCARFASLIYGKF",
|
| 78 |
+
0.9999999403953552,
|
| 79 |
+
"48-67"
|
| 80 |
+
],
|
| 81 |
+
[
|
| 82 |
+
"KEKVNELCARFASLIYGKFV",
|
| 83 |
+
0.9999999403953552,
|
| 84 |
+
"49-68"
|
| 85 |
+
],
|
| 86 |
+
[
|
| 87 |
+
"LIYGKFVRQPQVWLRIQNYS",
|
| 88 |
+
0.9999999403953552,
|
| 89 |
+
"62-81"
|
| 90 |
+
],
|
| 91 |
+
[
|
| 92 |
+
"YGKFVRQPQVWLRIQNYSVM",
|
| 93 |
+
0.9999999403953552,
|
| 94 |
+
"64-83"
|
| 95 |
+
],
|
| 96 |
+
[
|
| 97 |
+
"QPQVWLRIQNYSVMDICDEH",
|
| 98 |
+
0.9999999403953552,
|
| 99 |
+
"70-89"
|
| 100 |
+
],
|
| 101 |
+
[
|
| 102 |
+
"PQVWLRIQNYSVMDICDEHQ",
|
| 103 |
+
0.9999999403953552,
|
| 104 |
+
"71-90"
|
| 105 |
+
],
|
| 106 |
+
[
|
| 107 |
+
"RIQNYSVMDICDEHQGVMVP",
|
| 108 |
+
0.9999999403953552,
|
| 109 |
+
"76-95"
|
| 110 |
+
],
|
| 111 |
+
[
|
| 112 |
+
"DEHQGVMVPGVGVPQALQKY",
|
| 113 |
+
0.9999999403953552,
|
| 114 |
+
"87-106"
|
| 115 |
+
]
|
| 116 |
+
],
|
| 117 |
+
"t_cell_epitopes": [],
|
| 118 |
+
"length": 109
|
| 119 |
+
}
|
| 120 |
+
},
|
| 121 |
+
"total_sequences": 3
|
| 122 |
+
}
|
static/uploads/f20900ed-1799-4b0c-a32b-368b667109f7_results.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"job_id": "f20900ed-1799-4b0c-a32b-368b667109f7",
|
| 3 |
+
"timestamp": "2025-08-15T06:23:27.377247",
|
| 4 |
+
"results": {
|
| 5 |
+
"Human_Insulin_A_Chain": {
|
| 6 |
+
"sequence": "GIVEQCCTSICSLYQLENYCN",
|
| 7 |
+
"b_cell_epitopes": [],
|
| 8 |
+
"t_cell_epitopes": [],
|
| 9 |
+
"length": 21
|
| 10 |
+
},
|
| 11 |
+
"Human_Insulin_B_Chain": {
|
| 12 |
+
"sequence": "FVNQHLCGSHLVEALYLVCGERGFFYTPKT",
|
| 13 |
+
"b_cell_epitopes": [
|
| 14 |
+
[
|
| 15 |
+
"VNQHLCGSHLVEALYLVCGE",
|
| 16 |
+
0.9999999403953552,
|
| 17 |
+
"2-21"
|
| 18 |
+
],
|
| 19 |
+
[
|
| 20 |
+
"NQHLCGSHLVEALYLVCGER",
|
| 21 |
+
0.9999999403953552,
|
| 22 |
+
"3-22"
|
| 23 |
+
],
|
| 24 |
+
[
|
| 25 |
+
"CGSHLVEALYLVCGERGFFY",
|
| 26 |
+
0.9999999403953552,
|
| 27 |
+
"7-26"
|
| 28 |
+
]
|
| 29 |
+
],
|
| 30 |
+
"t_cell_epitopes": [],
|
| 31 |
+
"length": 30
|
| 32 |
+
},
|
| 33 |
+
"Example_Antigen": {
|
| 34 |
+
"sequence": "MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNELCARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD",
|
| 35 |
+
"b_cell_epitopes": [
|
| 36 |
+
[
|
| 37 |
+
"HQGVMVPGVGVPQALQKYNP",
|
| 38 |
+
1.0,
|
| 39 |
+
"89-108"
|
| 40 |
+
],
|
| 41 |
+
[
|
| 42 |
+
"QGVMVPGVGVPQALQKYNPD",
|
| 43 |
+
1.0,
|
| 44 |
+
"90-109"
|
| 45 |
+
],
|
| 46 |
+
[
|
| 47 |
+
"KLLILTCLVAVALARPKHPI",
|
| 48 |
+
0.9999999403953552,
|
| 49 |
+
"2-21"
|
| 50 |
+
],
|
| 51 |
+
[
|
| 52 |
+
"RPKHPIKHQGLPQEVLNENL",
|
| 53 |
+
0.9999999403953552,
|
| 54 |
+
"16-35"
|
| 55 |
+
],
|
| 56 |
+
[
|
| 57 |
+
"HPIKHQGLPQEVLNENLLRF",
|
| 58 |
+
0.9999999403953552,
|
| 59 |
+
"19-38"
|
| 60 |
+
],
|
| 61 |
+
[
|
| 62 |
+
"VLNENLLRFFVAPFPEVFGK",
|
| 63 |
+
0.9999999403953552,
|
| 64 |
+
"30-49"
|
| 65 |
+
],
|
| 66 |
+
[
|
| 67 |
+
"LNENLLRFFVAPFPEVFGKE",
|
| 68 |
+
0.9999999403953552,
|
| 69 |
+
"31-50"
|
| 70 |
+
],
|
| 71 |
+
[
|
| 72 |
+
"VFGKEKVNELCARFASLIYG",
|
| 73 |
+
0.9999999403953552,
|
| 74 |
+
"46-65"
|
| 75 |
+
],
|
| 76 |
+
[
|
| 77 |
+
"GKEKVNELCARFASLIYGKF",
|
| 78 |
+
0.9999999403953552,
|
| 79 |
+
"48-67"
|
| 80 |
+
],
|
| 81 |
+
[
|
| 82 |
+
"KEKVNELCARFASLIYGKFV",
|
| 83 |
+
0.9999999403953552,
|
| 84 |
+
"49-68"
|
| 85 |
+
],
|
| 86 |
+
[
|
| 87 |
+
"LIYGKFVRQPQVWLRIQNYS",
|
| 88 |
+
0.9999999403953552,
|
| 89 |
+
"62-81"
|
| 90 |
+
],
|
| 91 |
+
[
|
| 92 |
+
"YGKFVRQPQVWLRIQNYSVM",
|
| 93 |
+
0.9999999403953552,
|
| 94 |
+
"64-83"
|
| 95 |
+
],
|
| 96 |
+
[
|
| 97 |
+
"QPQVWLRIQNYSVMDICDEH",
|
| 98 |
+
0.9999999403953552,
|
| 99 |
+
"70-89"
|
| 100 |
+
],
|
| 101 |
+
[
|
| 102 |
+
"PQVWLRIQNYSVMDICDEHQ",
|
| 103 |
+
0.9999999403953552,
|
| 104 |
+
"71-90"
|
| 105 |
+
],
|
| 106 |
+
[
|
| 107 |
+
"RIQNYSVMDICDEHQGVMVP",
|
| 108 |
+
0.9999999403953552,
|
| 109 |
+
"76-95"
|
| 110 |
+
],
|
| 111 |
+
[
|
| 112 |
+
"DEHQGVMVPGVGVPQALQKY",
|
| 113 |
+
0.9999999403953552,
|
| 114 |
+
"87-106"
|
| 115 |
+
]
|
| 116 |
+
],
|
| 117 |
+
"t_cell_epitopes": [],
|
| 118 |
+
"length": 109
|
| 119 |
+
}
|
| 120 |
+
},
|
| 121 |
+
"total_sequences": 3
|
| 122 |
+
}
|
templates/about.html
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block page_title %}About EpiPred{% endblock %}
|
| 4 |
+
{% block page_subtitle %}Advanced epitope prediction using deep learning and attention mechanisms{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<div class="row">
|
| 8 |
+
<div class="col-lg-8">
|
| 9 |
+
<!-- Method Overview -->
|
| 10 |
+
<div class="card shadow-sm mb-4">
|
| 11 |
+
<div class="card-header bg-primary text-white">
|
| 12 |
+
<h4 class="mb-0"><i class="fas fa-brain me-2"></i>Method Overview</h4>
|
| 13 |
+
</div>
|
| 14 |
+
<div class="card-body">
|
| 15 |
+
<p class="lead">
|
| 16 |
+
EpiPred employs a state-of-the-art deep learning architecture combining attention mechanisms
|
| 17 |
+
with bidirectional LSTM networks to predict both B-cell and T-cell epitopes from protein sequences.
|
| 18 |
+
</p>
|
| 19 |
+
|
| 20 |
+
<h5 class="mt-4 mb-3">Key Features:</h5>
|
| 21 |
+
<div class="row">
|
| 22 |
+
<div class="col-md-6">
|
| 23 |
+
<ul class="list-unstyled">
|
| 24 |
+
<li class="mb-2">
|
| 25 |
+
<i class="fas fa-check-circle text-success me-2"></i>
|
| 26 |
+
<strong>Attention Mechanisms:</strong> Focus on relevant sequence regions
|
| 27 |
+
</li>
|
| 28 |
+
<li class="mb-2">
|
| 29 |
+
<i class="fas fa-check-circle text-success me-2"></i>
|
| 30 |
+
<strong>Bidirectional LSTM:</strong> Capture long-range dependencies
|
| 31 |
+
</li>
|
| 32 |
+
<li class="mb-2">
|
| 33 |
+
<i class="fas fa-check-circle text-success me-2"></i>
|
| 34 |
+
<strong>Convolutional Layers:</strong> Extract local sequence patterns
|
| 35 |
+
</li>
|
| 36 |
+
</ul>
|
| 37 |
+
</div>
|
| 38 |
+
<div class="col-md-6">
|
| 39 |
+
<ul class="list-unstyled">
|
| 40 |
+
<li class="mb-2">
|
| 41 |
+
<i class="fas fa-check-circle text-success me-2"></i>
|
| 42 |
+
<strong>Multi-class Prediction:</strong> B-cell and T-cell epitopes
|
| 43 |
+
</li>
|
| 44 |
+
<li class="mb-2">
|
| 45 |
+
<i class="fas fa-check-circle text-success me-2"></i>
|
| 46 |
+
<strong>Sliding Window:</strong> Comprehensive sequence analysis
|
| 47 |
+
</li>
|
| 48 |
+
<li class="mb-2">
|
| 49 |
+
<i class="fas fa-check-circle text-success me-2"></i>
|
| 50 |
+
<strong>Confidence Scoring:</strong> Reliable prediction assessment
|
| 51 |
+
</li>
|
| 52 |
+
</ul>
|
| 53 |
+
</div>
|
| 54 |
+
</div>
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
+
|
| 58 |
+
<!-- Model Architecture -->
|
| 59 |
+
<div class="card shadow-sm mb-4">
|
| 60 |
+
<div class="card-header bg-info text-white">
|
| 61 |
+
<h4 class="mb-0"><i class="fas fa-sitemap me-2"></i>Model Architecture</h4>
|
| 62 |
+
</div>
|
| 63 |
+
<div class="card-body">
|
| 64 |
+
<h5>Deep Learning Pipeline:</h5>
|
| 65 |
+
<div class="row">
|
| 66 |
+
<div class="col-12">
|
| 67 |
+
<div class="bg-light p-3 rounded">
|
| 68 |
+
<div class="text-center">
|
| 69 |
+
<div class="d-inline-block border rounded p-2 m-1 bg-white">
|
| 70 |
+
<strong>Input Layer</strong><br>
|
| 71 |
+
<small>Amino Acid Sequences</small>
|
| 72 |
+
</div>
|
| 73 |
+
<div class="mx-2">↓</div>
|
| 74 |
+
<div class="d-inline-block border rounded p-2 m-1 bg-white">
|
| 75 |
+
<strong>Embedding Layer</strong><br>
|
| 76 |
+
<small>128-dimensional vectors</small>
|
| 77 |
+
</div>
|
| 78 |
+
<div class="mx-2">↓</div>
|
| 79 |
+
<div class="d-inline-block border rounded p-2 m-1 bg-white">
|
| 80 |
+
<strong>Conv1D Layers</strong><br>
|
| 81 |
+
<small>Feature extraction</small>
|
| 82 |
+
</div>
|
| 83 |
+
<div class="mx-2">↓</div>
|
| 84 |
+
<div class="d-inline-block border rounded p-2 m-1 bg-white">
|
| 85 |
+
<strong>Attention Layer</strong><br>
|
| 86 |
+
<small>Focus mechanism</small>
|
| 87 |
+
</div>
|
| 88 |
+
<div class="mx-2">↓</div>
|
| 89 |
+
<div class="d-inline-block border rounded p-2 m-1 bg-white">
|
| 90 |
+
<strong>BiLSTM Layers</strong><br>
|
| 91 |
+
<small>Sequence modeling</small>
|
| 92 |
+
</div>
|
| 93 |
+
<div class="mx-2">↓</div>
|
| 94 |
+
<div class="d-inline-block border rounded p-2 m-1 bg-white">
|
| 95 |
+
<strong>Dense Layers</strong><br>
|
| 96 |
+
<small>Classification</small>
|
| 97 |
+
</div>
|
| 98 |
+
<div class="mx-2">↓</div>
|
| 99 |
+
<div class="d-inline-block border rounded p-2 m-1 bg-success text-white">
|
| 100 |
+
<strong>Output</strong><br>
|
| 101 |
+
<small>Epitope Predictions</small>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
</div>
|
| 105 |
+
</div>
|
| 106 |
+
</div>
|
| 107 |
+
|
| 108 |
+
<h5 class="mt-4">Technical Specifications:</h5>
|
| 109 |
+
<div class="row">
|
| 110 |
+
<div class="col-md-6">
|
| 111 |
+
<ul>
|
| 112 |
+
<li><strong>Window Size:</strong> 20 amino acids</li>
|
| 113 |
+
<li><strong>Step Size:</strong> 1 amino acid (overlapping)</li>
|
| 114 |
+
<li><strong>Embedding Dimension:</strong> 128</li>
|
| 115 |
+
<li><strong>LSTM Units:</strong> 64 (bidirectional)</li>
|
| 116 |
+
</ul>
|
| 117 |
+
</div>
|
| 118 |
+
<div class="col-md-6">
|
| 119 |
+
<ul>
|
| 120 |
+
<li><strong>Attention Heads:</strong> Self-attention</li>
|
| 121 |
+
<li><strong>Dropout Rate:</strong> 0.3</li>
|
| 122 |
+
<li><strong>Activation:</strong> ReLU, Softmax</li>
|
| 123 |
+
<li><strong>Optimizer:</strong> Adam</li>
|
| 124 |
+
</ul>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
</div>
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
+
<!-- Training Data -->
|
| 131 |
+
<div class="card shadow-sm mb-4">
|
| 132 |
+
<div class="card-header bg-success text-white">
|
| 133 |
+
<h4 class="mb-0"><i class="fas fa-database me-2"></i>Training Data</h4>
|
| 134 |
+
</div>
|
| 135 |
+
<div class="card-body">
|
| 136 |
+
<p>
|
| 137 |
+
The model was trained on a comprehensive dataset of experimentally validated epitopes:
|
| 138 |
+
</p>
|
| 139 |
+
|
| 140 |
+
<div class="row">
|
| 141 |
+
<div class="col-md-6">
|
| 142 |
+
<h6 class="text-success">B-cell Epitopes:</h6>
|
| 143 |
+
<ul>
|
| 144 |
+
<li>Positive examples: 109 sequences</li>
|
| 145 |
+
<li>Negative examples: 123 sequences</li>
|
| 146 |
+
<li>Source: Experimental validation</li>
|
| 147 |
+
</ul>
|
| 148 |
+
</div>
|
| 149 |
+
<div class="col-md-6">
|
| 150 |
+
<h6 class="text-success">T-cell Epitopes:</h6>
|
| 151 |
+
<ul>
|
| 152 |
+
<li>Positive examples: 237 sequences</li>
|
| 153 |
+
<li>Negative examples: 1,147 sequences</li>
|
| 154 |
+
<li>Source: MHC binding data</li>
|
| 155 |
+
</ul>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
|
| 159 |
+
<div class="alert alert-info mt-3">
|
| 160 |
+
<i class="fas fa-info-circle me-2"></i>
|
| 161 |
+
<strong>Data Quality:</strong> All training data consists of experimentally validated
|
| 162 |
+
epitopes from peer-reviewed publications and curated databases.
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
|
| 167 |
+
<!-- Performance -->
|
| 168 |
+
<div class="card shadow-sm">
|
| 169 |
+
<div class="card-header bg-warning text-dark">
|
| 170 |
+
<h4 class="mb-0"><i class="fas fa-chart-line me-2"></i>Model Performance</h4>
|
| 171 |
+
</div>
|
| 172 |
+
<div class="card-body">
|
| 173 |
+
<p>
|
| 174 |
+
The model has been rigorously evaluated using cross-validation and independent test sets:
|
| 175 |
+
</p>
|
| 176 |
+
|
| 177 |
+
<div class="row">
|
| 178 |
+
<div class="col-md-4 text-center">
|
| 179 |
+
<div class="border rounded p-3">
|
| 180 |
+
<h3 class="text-primary">85%+</h3>
|
| 181 |
+
<small class="text-muted">Overall Accuracy</small>
|
| 182 |
+
</div>
|
| 183 |
+
</div>
|
| 184 |
+
<div class="col-md-4 text-center">
|
| 185 |
+
<div class="border rounded p-3">
|
| 186 |
+
<h3 class="text-success">0.82</h3>
|
| 187 |
+
<small class="text-muted">F1 Score</small>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
<div class="col-md-4 text-center">
|
| 191 |
+
<div class="border rounded p-3">
|
| 192 |
+
<h3 class="text-info">0.88</h3>
|
| 193 |
+
<small class="text-muted">ROC-AUC</small>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
</div>
|
| 197 |
+
|
| 198 |
+
<h6 class="mt-4">Key Performance Metrics:</h6>
|
| 199 |
+
<ul>
|
| 200 |
+
<li><strong>Precision:</strong> High specificity in epitope identification</li>
|
| 201 |
+
<li><strong>Recall:</strong> Comprehensive detection of true epitopes</li>
|
| 202 |
+
<li><strong>Matthews Correlation Coefficient:</strong> Balanced performance across classes</li>
|
| 203 |
+
<li><strong>Cross-validation:</strong> Robust performance across different data splits</li>
|
| 204 |
+
</ul>
|
| 205 |
+
</div>
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
+
|
| 209 |
+
<div class="col-lg-4">
|
| 210 |
+
<!-- Quick Facts -->
|
| 211 |
+
<div class="card shadow-sm mb-4">
|
| 212 |
+
<div class="card-header bg-dark text-white">
|
| 213 |
+
<h5 class="mb-0"><i class="fas fa-info-circle me-2"></i>Quick Facts</h5>
|
| 214 |
+
</div>
|
| 215 |
+
<div class="card-body">
|
| 216 |
+
<table class="table table-sm">
|
| 217 |
+
<tr>
|
| 218 |
+
<td><strong>Model Type:</strong></td>
|
| 219 |
+
<td>Deep Neural Network</td>
|
| 220 |
+
</tr>
|
| 221 |
+
<tr>
|
| 222 |
+
<td><strong>Architecture:</strong></td>
|
| 223 |
+
<td>Attention + BiLSTM</td>
|
| 224 |
+
</tr>
|
| 225 |
+
<tr>
|
| 226 |
+
<td><strong>Input:</strong></td>
|
| 227 |
+
<td>Protein sequences</td>
|
| 228 |
+
</tr>
|
| 229 |
+
<tr>
|
| 230 |
+
<td><strong>Output:</strong></td>
|
| 231 |
+
<td>B-cell & T-cell epitopes</td>
|
| 232 |
+
</tr>
|
| 233 |
+
<tr>
|
| 234 |
+
<td><strong>Window Size:</strong></td>
|
| 235 |
+
<td>20 amino acids</td>
|
| 236 |
+
</tr>
|
| 237 |
+
<tr>
|
| 238 |
+
<td><strong>Framework:</strong></td>
|
| 239 |
+
<td>TensorFlow/Keras</td>
|
| 240 |
+
</tr>
|
| 241 |
+
</table>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
|
| 245 |
+
<!-- Advantages -->
|
| 246 |
+
<div class="card shadow-sm mb-4">
|
| 247 |
+
<div class="card-header bg-success text-white">
|
| 248 |
+
<h5 class="mb-0"><i class="fas fa-thumbs-up me-2"></i>Advantages</h5>
|
| 249 |
+
</div>
|
| 250 |
+
<div class="card-body">
|
| 251 |
+
<ul class="list-unstyled">
|
| 252 |
+
<li class="mb-2">
|
| 253 |
+
<i class="fas fa-star text-warning me-2"></i>
|
| 254 |
+
<strong>State-of-the-art:</strong> Latest deep learning techniques
|
| 255 |
+
</li>
|
| 256 |
+
<li class="mb-2">
|
| 257 |
+
<i class="fas fa-star text-warning me-2"></i>
|
| 258 |
+
<strong>Multi-target:</strong> Predicts both B-cell and T-cell epitopes
|
| 259 |
+
</li>
|
| 260 |
+
<li class="mb-2">
|
| 261 |
+
<i class="fas fa-star text-warning me-2"></i>
|
| 262 |
+
<strong>Fast:</strong> Rapid prediction for multiple sequences
|
| 263 |
+
</li>
|
| 264 |
+
<li class="mb-2">
|
| 265 |
+
<i class="fas fa-star text-warning me-2"></i>
|
| 266 |
+
<strong>Interpretable:</strong> Confidence scores and visualizations
|
| 267 |
+
</li>
|
| 268 |
+
<li class="mb-0">
|
| 269 |
+
<i class="fas fa-star text-warning me-2"></i>
|
| 270 |
+
<strong>User-friendly:</strong> Easy-to-use web interface
|
| 271 |
+
</li>
|
| 272 |
+
</ul>
|
| 273 |
+
</div>
|
| 274 |
+
</div>
|
| 275 |
+
|
| 276 |
+
<!-- Limitations -->
|
| 277 |
+
<div class="card shadow-sm mb-4">
|
| 278 |
+
<div class="card-header bg-warning text-dark">
|
| 279 |
+
<h5 class="mb-0"><i class="fas fa-exclamation-triangle me-2"></i>Limitations</h5>
|
| 280 |
+
</div>
|
| 281 |
+
<div class="card-body">
|
| 282 |
+
<ul class="list-unstyled">
|
| 283 |
+
<li class="mb-2">
|
| 284 |
+
<i class="fas fa-info-circle text-info me-2"></i>
|
| 285 |
+
Predictions are computational - experimental validation recommended
|
| 286 |
+
</li>
|
| 287 |
+
<li class="mb-2">
|
| 288 |
+
<i class="fas fa-info-circle text-info me-2"></i>
|
| 289 |
+
Performance may vary for highly divergent sequences
|
| 290 |
+
</li>
|
| 291 |
+
<li class="mb-2">
|
| 292 |
+
<i class="fas fa-info-circle text-info me-2"></i>
|
| 293 |
+
Limited to standard amino acids (20 canonical AAs)
|
| 294 |
+
</li>
|
| 295 |
+
<li class="mb-0">
|
| 296 |
+
<i class="fas fa-info-circle text-info me-2"></i>
|
| 297 |
+
Does not consider 3D structure information
|
| 298 |
+
</li>
|
| 299 |
+
</ul>
|
| 300 |
+
</div>
|
| 301 |
+
</div>
|
| 302 |
+
|
| 303 |
+
<!-- Citation -->
|
| 304 |
+
<div class="card shadow-sm">
|
| 305 |
+
<div class="card-header bg-secondary text-white">
|
| 306 |
+
<h5 class="mb-0"><i class="fas fa-quote-right me-2"></i>Citation</h5>
|
| 307 |
+
</div>
|
| 308 |
+
<div class="card-body">
|
| 309 |
+
<p class="mb-3">
|
| 310 |
+
If you use EpiPred in your research, please cite:
|
| 311 |
+
</p>
|
| 312 |
+
<div class="bg-light p-3 rounded">
|
| 313 |
+
<small class="text-muted">
|
| 314 |
+
<strong>EpiPred: Advanced Epitope Prediction Using Attention-based Deep Learning.</strong><br>
|
| 315 |
+
<em>Bioinformatics and Computational Biology</em> (2024)<br>
|
| 316 |
+
DOI: 10.xxxx/xxxxxx
|
| 317 |
+
</small>
|
| 318 |
+
</div>
|
| 319 |
+
<button class="btn btn-outline-secondary btn-sm mt-2" onclick="copyToClipboard('EpiPred citation')">
|
| 320 |
+
<i class="fas fa-copy me-1"></i>Copy Citation
|
| 321 |
+
</button>
|
| 322 |
+
</div>
|
| 323 |
+
</div>
|
| 324 |
+
</div>
|
| 325 |
+
</div>
|
| 326 |
+
{% endblock %}
|
templates/base.html
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>{% block title %}EpiPred - Epitope Prediction Tool{% endblock %}</title>
|
| 7 |
+
|
| 8 |
+
<!-- Favicon -->
|
| 9 |
+
<link rel="icon" type="image/png" href="{{ url_for('static', filename='logo.png') }}">
|
| 10 |
+
|
| 11 |
+
<!-- Bootstrap CSS -->
|
| 12 |
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
| 13 |
+
<!-- Font Awesome -->
|
| 14 |
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
| 15 |
+
<!-- Custom CSS -->
|
| 16 |
+
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
|
| 17 |
+
|
| 18 |
+
{% block extra_head %}{% endblock %}
|
| 19 |
+
</head>
|
| 20 |
+
<body>
|
| 21 |
+
<!-- Navigation -->
|
| 22 |
+
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
| 23 |
+
<div class="container">
|
| 24 |
+
<a class="navbar-brand" href="{{ url_for('index') }}">
|
| 25 |
+
<img src="{{ url_for('static', filename='logo.png') }}" alt="EpiPred" height="30" class="d-inline-block align-text-top me-2">
|
| 26 |
+
EpiPred
|
| 27 |
+
</a>
|
| 28 |
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
| 29 |
+
<span class="navbar-toggler-icon"></span>
|
| 30 |
+
</button>
|
| 31 |
+
<div class="collapse navbar-collapse" id="navbarNav">
|
| 32 |
+
<ul class="navbar-nav ms-auto">
|
| 33 |
+
<li class="nav-item">
|
| 34 |
+
<a class="nav-link" href="{{ url_for('index') }}">
|
| 35 |
+
<i class="fas fa-home me-1"></i>Home
|
| 36 |
+
</a>
|
| 37 |
+
</li>
|
| 38 |
+
<li class="nav-item">
|
| 39 |
+
<a class="nav-link" href="{{ url_for('instructions') }}">
|
| 40 |
+
<i class="fas fa-info-circle me-1"></i>Instructions
|
| 41 |
+
</a>
|
| 42 |
+
</li>
|
| 43 |
+
<li class="nav-item">
|
| 44 |
+
<a class="nav-link" href="{{ url_for('about') }}">
|
| 45 |
+
<i class="fas fa-question-circle me-1"></i>About
|
| 46 |
+
</a>
|
| 47 |
+
</li>
|
| 48 |
+
</ul>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
</nav>
|
| 52 |
+
|
| 53 |
+
<!-- Header -->
|
| 54 |
+
<div class="bg-light py-4">
|
| 55 |
+
<div class="container">
|
| 56 |
+
<div class="row">
|
| 57 |
+
<div class="col-md-8">
|
| 58 |
+
<h1 class="display-6 mb-2">{% block page_title %}EpiPred - Epitope Prediction Tool{% endblock %}</h1>
|
| 59 |
+
<p class="lead text-muted">{% block page_subtitle %}Prediction of potential B-cell and T-cell epitopes{% endblock %}</p>
|
| 60 |
+
</div>
|
| 61 |
+
<div class="col-md-4 text-end">
|
| 62 |
+
<img src="{{ url_for('static', filename='logo.png') }}" alt="EpiPred Logo" class="img-fluid logo-header">
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
<!-- Flash Messages -->
|
| 69 |
+
{% with messages = get_flashed_messages(with_categories=true) %}
|
| 70 |
+
{% if messages %}
|
| 71 |
+
<div class="container mt-3">
|
| 72 |
+
{% for category, message in messages %}
|
| 73 |
+
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' if category == 'success' else 'info' }} alert-dismissible fade show" role="alert">
|
| 74 |
+
<i class="fas fa-{{ 'exclamation-triangle' if category == 'error' else 'check-circle' if category == 'success' else 'info-circle' }} me-2"></i>
|
| 75 |
+
{{ message }}
|
| 76 |
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
| 77 |
+
</div>
|
| 78 |
+
{% endfor %}
|
| 79 |
+
</div>
|
| 80 |
+
{% endif %}
|
| 81 |
+
{% endwith %}
|
| 82 |
+
|
| 83 |
+
<!-- Main Content -->
|
| 84 |
+
<main class="container my-4">
|
| 85 |
+
{% block content %}{% endblock %}
|
| 86 |
+
</main>
|
| 87 |
+
|
| 88 |
+
<!-- Footer -->
|
| 89 |
+
<footer class="bg-dark text-light py-4 mt-5">
|
| 90 |
+
<div class="container">
|
| 91 |
+
<div class="row">
|
| 92 |
+
<div class="col-md-6">
|
| 93 |
+
<div class="d-flex align-items-center">
|
| 94 |
+
<img src="{{ url_for('static', filename='logo.png') }}" alt="EpiPred" class="me-3 logo-footer">
|
| 95 |
+
<div>
|
| 96 |
+
<h5>EpiPred - Epitope Prediction Tool</h5>
|
| 97 |
+
<p class="mb-0">Advanced machine learning for epitope prediction</p>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
<div class="col-md-6 text-end">
|
| 102 |
+
<p class="mb-0">
|
| 103 |
+
<small>
|
| 104 |
+
Powered by Deep Learning & Attention Mechanisms<br>
|
| 105 |
+
<i class="fas fa-code me-1"></i>Built with Flask & TensorFlow
|
| 106 |
+
</small>
|
| 107 |
+
</p>
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
</div>
|
| 111 |
+
</footer>
|
| 112 |
+
|
| 113 |
+
<!-- Bootstrap JS -->
|
| 114 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
| 115 |
+
<!-- jQuery -->
|
| 116 |
+
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
| 117 |
+
<!-- Custom JS -->
|
| 118 |
+
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
| 119 |
+
|
| 120 |
+
{% block extra_scripts %}{% endblock %}
|
| 121 |
+
</body>
|
| 122 |
+
</html>
|
templates/index.html
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block page_title %}EpiPred - Epitope Prediction{% endblock %}
|
| 4 |
+
{% block page_subtitle %}Paste or upload protein sequence(s) in FASTA format to predict potential B-cell and T-cell epitopes{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<div class="row">
|
| 8 |
+
<div class="col-lg-8">
|
| 9 |
+
<!-- Submission Form -->
|
| 10 |
+
<div class="card shadow-sm">
|
| 11 |
+
<div class="card-header bg-primary text-white">
|
| 12 |
+
<h4 class="mb-0"><i class="fas fa-upload me-2"></i>Submit Data</h4>
|
| 13 |
+
</div>
|
| 14 |
+
<div class="card-body">
|
| 15 |
+
<form method="POST" action="{{ url_for('predict') }}" enctype="multipart/form-data" id="predictionForm">
|
| 16 |
+
<!-- Sequence Input -->
|
| 17 |
+
<div class="mb-4">
|
| 18 |
+
<label for="sequence_text" class="form-label">
|
| 19 |
+
<strong>A. Paste protein sequence(s) in FASTA format:</strong>
|
| 20 |
+
</label>
|
| 21 |
+
<textarea class="form-control font-monospace"
|
| 22 |
+
id="sequence_text"
|
| 23 |
+
name="sequence_text"
|
| 24 |
+
rows="8"
|
| 25 |
+
placeholder=">Example_Protein_1 MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL CARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD >Example_Protein_2 MKTVRQERLKSIVRILERSKEPVSGAQLAEELSVSRQVIVQDIAYLRSLGYNIVATPRGYVLAGG"></textarea>
|
| 26 |
+
<div class="form-text">
|
| 27 |
+
<i class="fas fa-info-circle me-1"></i>
|
| 28 |
+
Enter protein sequences in FASTA format. Each sequence should start with a header line beginning with '>' followed by the amino acid sequence.
|
| 29 |
+
</div>
|
| 30 |
+
</div>
|
| 31 |
+
|
| 32 |
+
<!-- File Upload -->
|
| 33 |
+
<div class="mb-4">
|
| 34 |
+
<label for="sequence_file" class="form-label">
|
| 35 |
+
<strong>B. Or upload a FASTA file:</strong>
|
| 36 |
+
</label>
|
| 37 |
+
<div class="input-group">
|
| 38 |
+
<input type="file"
|
| 39 |
+
class="form-control"
|
| 40 |
+
id="sequence_file"
|
| 41 |
+
name="sequence_file"
|
| 42 |
+
accept=".txt,.fasta,.fa,.fas">
|
| 43 |
+
<label class="input-group-text" for="sequence_file">
|
| 44 |
+
<i class="fas fa-file-upload"></i>
|
| 45 |
+
</label>
|
| 46 |
+
</div>
|
| 47 |
+
<div class="form-text">
|
| 48 |
+
<i class="fas fa-info-circle me-1"></i>
|
| 49 |
+
Supported formats: .txt, .fasta, .fa, .fas (Max file size: 16MB)
|
| 50 |
+
</div>
|
| 51 |
+
<div id="file-info" class="mt-2 text-muted"></div>
|
| 52 |
+
</div>
|
| 53 |
+
|
| 54 |
+
<!-- Submit Button -->
|
| 55 |
+
<div class="d-grid">
|
| 56 |
+
<button type="submit" class="btn btn-success btn-lg" id="submitBtn">
|
| 57 |
+
<i class="fas fa-play me-2"></i>Predict Epitopes
|
| 58 |
+
</button>
|
| 59 |
+
</div>
|
| 60 |
+
</form>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<!-- Example File Link -->
|
| 65 |
+
<div class="mt-3 text-center">
|
| 66 |
+
<a href="#" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#exampleModal">
|
| 67 |
+
<i class="fas fa-download me-1"></i>Example FASTA File
|
| 68 |
+
</a>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
|
| 72 |
+
<div class="col-lg-4">
|
| 73 |
+
<!-- Information Panel -->
|
| 74 |
+
<div class="card shadow-sm mb-4">
|
| 75 |
+
<div class="card-header bg-info text-white">
|
| 76 |
+
<h5 class="mb-0"><i class="fas fa-info-circle me-2"></i>Submission Limits</h5>
|
| 77 |
+
</div>
|
| 78 |
+
<div class="card-body">
|
| 79 |
+
<ul class="list-unstyled mb-0">
|
| 80 |
+
<li class="mb-2">
|
| 81 |
+
<i class="fas fa-check-circle text-success me-2"></i>
|
| 82 |
+
<strong>Maximum:</strong> 50 sequences per submission
|
| 83 |
+
</li>
|
| 84 |
+
<li class="mb-2">
|
| 85 |
+
<i class="fas fa-check-circle text-success me-2"></i>
|
| 86 |
+
<strong>Total length:</strong> Up to 300,000 amino acids
|
| 87 |
+
</li>
|
| 88 |
+
<li class="mb-2">
|
| 89 |
+
<i class="fas fa-check-circle text-success me-2"></i>
|
| 90 |
+
<strong>Sequence length:</strong> 10-6000 amino acids each
|
| 91 |
+
</li>
|
| 92 |
+
<li class="mb-0">
|
| 93 |
+
<i class="fas fa-clock text-warning me-2"></i>
|
| 94 |
+
<strong>Processing time:</strong> A few minutes per sequence
|
| 95 |
+
</li>
|
| 96 |
+
</ul>
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
|
| 100 |
+
<!-- Method Information -->
|
| 101 |
+
<div class="card shadow-sm">
|
| 102 |
+
<div class="card-header bg-secondary text-white">
|
| 103 |
+
<h5 class="mb-0"><i class="fas fa-brain me-2"></i>Method</h5>
|
| 104 |
+
</div>
|
| 105 |
+
<div class="card-body">
|
| 106 |
+
<p class="card-text">
|
| 107 |
+
EpiPred uses an advanced deep learning model with attention mechanisms
|
| 108 |
+
and bidirectional LSTM networks to predict B-cell and T-cell epitopes
|
| 109 |
+
from protein sequences.
|
| 110 |
+
</p>
|
| 111 |
+
<p class="card-text">
|
| 112 |
+
<small class="text-muted">
|
| 113 |
+
The model employs sliding window analysis with overlapping segments
|
| 114 |
+
to provide comprehensive epitope predictions with confidence scores.
|
| 115 |
+
</small>
|
| 116 |
+
</p>
|
| 117 |
+
<a href="{{ url_for('about') }}" class="btn btn-outline-secondary btn-sm">
|
| 118 |
+
<i class="fas fa-arrow-right me-1"></i>Learn More
|
| 119 |
+
</a>
|
| 120 |
+
</div>
|
| 121 |
+
</div>
|
| 122 |
+
</div>
|
| 123 |
+
</div>
|
| 124 |
+
|
| 125 |
+
<!-- Example Modal -->
|
| 126 |
+
<div class="modal fade" id="exampleModal" tabindex="-1">
|
| 127 |
+
<div class="modal-dialog modal-lg">
|
| 128 |
+
<div class="modal-content">
|
| 129 |
+
<div class="modal-header">
|
| 130 |
+
<h5 class="modal-title">Example FASTA File</h5>
|
| 131 |
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| 132 |
+
</div>
|
| 133 |
+
<div class="modal-body">
|
| 134 |
+
<p>Here's an example of properly formatted FASTA input:</p>
|
| 135 |
+
<pre class="bg-light p-3 rounded"><code>>Human_Insulin_A_Chain
|
| 136 |
+
GIVEQCCTSICSLYQLENYCN
|
| 137 |
+
|
| 138 |
+
>Human_Insulin_B_Chain
|
| 139 |
+
FVNQHLCGSHLVEALYLVCGERGFFYTPKT
|
| 140 |
+
|
| 141 |
+
>Example_Antigen
|
| 142 |
+
MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL
|
| 143 |
+
CARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD
|
| 144 |
+
AVNQVVDLMAHMASKDNRAHGDNKFRNRNQRKQRNSTRHIQLGLPAAMADVLFVL
|
| 145 |
+
FGAATGFHSGGIASYNFPKATLQSQVKELQAAQARLGADMEDVCGRLVQRGNQVA
|
| 146 |
+
YYRGISALQLEECDFFPQHIQVQVQLEQVVGVVKPLLVQVQHQDYLAAVSVAQL</code></pre>
|
| 147 |
+
<div class="alert alert-info">
|
| 148 |
+
<i class="fas fa-lightbulb me-2"></i>
|
| 149 |
+
<strong>Tips:</strong>
|
| 150 |
+
<ul class="mb-0 mt-2">
|
| 151 |
+
<li>Each sequence must start with a header line beginning with '>'</li>
|
| 152 |
+
<li>Header lines can contain sequence names and descriptions</li>
|
| 153 |
+
<li>Sequence lines should contain only standard amino acid letters</li>
|
| 154 |
+
<li>Multiple sequences can be included in a single submission</li>
|
| 155 |
+
</ul>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
<div class="modal-footer">
|
| 159 |
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
| 160 |
+
<button type="button" class="btn btn-primary" onclick="copyExample()">
|
| 161 |
+
<i class="fas fa-copy me-1"></i>Copy Example
|
| 162 |
+
</button>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
{% endblock %}
|
| 168 |
+
|
| 169 |
+
{% block extra_scripts %}
|
| 170 |
+
<script>
|
| 171 |
+
// File upload info display
|
| 172 |
+
document.getElementById('sequence_file').addEventListener('change', function(e) {
|
| 173 |
+
const fileInfo = document.getElementById('file-info');
|
| 174 |
+
if (e.target.files.length > 0) {
|
| 175 |
+
const file = e.target.files[0];
|
| 176 |
+
const size = (file.size / 1024 / 1024).toFixed(2);
|
| 177 |
+
fileInfo.innerHTML = `<i class="fas fa-file me-1"></i>Selected: ${file.name} (${size} MB)`;
|
| 178 |
+
fileInfo.className = 'mt-2 text-info';
|
| 179 |
+
} else {
|
| 180 |
+
fileInfo.innerHTML = '';
|
| 181 |
+
}
|
| 182 |
+
});
|
| 183 |
+
|
| 184 |
+
// Copy example function
|
| 185 |
+
function copyExample() {
|
| 186 |
+
const exampleText = `>Human_Insulin_A_Chain
|
| 187 |
+
GIVEQCCTSICSLYQLENYCN
|
| 188 |
+
|
| 189 |
+
>Human_Insulin_B_Chain
|
| 190 |
+
FVNQHLCGSHLVEALYLVCGERGFFYTPKT
|
| 191 |
+
|
| 192 |
+
>Example_Antigen
|
| 193 |
+
MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL
|
| 194 |
+
CARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD`;
|
| 195 |
+
|
| 196 |
+
document.getElementById('sequence_text').value = exampleText;
|
| 197 |
+
$('#exampleModal').modal('hide');
|
| 198 |
+
|
| 199 |
+
// Show success message
|
| 200 |
+
const alert = document.createElement('div');
|
| 201 |
+
alert.className = 'alert alert-success alert-dismissible fade show mt-3';
|
| 202 |
+
alert.innerHTML = `
|
| 203 |
+
<i class="fas fa-check-circle me-2"></i>
|
| 204 |
+
Example sequences copied to input field!
|
| 205 |
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
| 206 |
+
`;
|
| 207 |
+
document.querySelector('.container.my-4').insertBefore(alert, document.querySelector('.row'));
|
| 208 |
+
|
| 209 |
+
// Auto-dismiss after 3 seconds
|
| 210 |
+
setTimeout(() => {
|
| 211 |
+
if (alert.parentNode) {
|
| 212 |
+
alert.remove();
|
| 213 |
+
}
|
| 214 |
+
}, 3000);
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
// Form validation
|
| 218 |
+
document.getElementById('predictionForm').addEventListener('submit', function(e) {
|
| 219 |
+
const sequenceText = document.getElementById('sequence_text').value.trim();
|
| 220 |
+
const sequenceFile = document.getElementById('sequence_file').files.length > 0;
|
| 221 |
+
|
| 222 |
+
if (!sequenceText && !sequenceFile) {
|
| 223 |
+
e.preventDefault();
|
| 224 |
+
alert('Please provide protein sequences either by pasting text or uploading a file.');
|
| 225 |
+
return false;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
// Show loading state
|
| 229 |
+
const submitBtn = document.getElementById('submitBtn');
|
| 230 |
+
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Processing...';
|
| 231 |
+
submitBtn.disabled = true;
|
| 232 |
+
});
|
| 233 |
+
</script>
|
| 234 |
+
{% endblock %}
|
templates/instructions.html
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block page_title %}Instructions{% endblock %}
|
| 4 |
+
{% block page_subtitle %}How to use the EpiPred epitope prediction tool{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<div class="row">
|
| 8 |
+
<div class="col-lg-8">
|
| 9 |
+
<!-- Main Instructions -->
|
| 10 |
+
<div class="card shadow-sm mb-4">
|
| 11 |
+
<div class="card-header bg-primary text-white">
|
| 12 |
+
<h4 class="mb-0"><i class="fas fa-book me-2"></i>How to Use EpiPred</h4>
|
| 13 |
+
</div>
|
| 14 |
+
<div class="card-body">
|
| 15 |
+
<p class="lead">
|
| 16 |
+
The EpiPred server predicts B-cell and T-cell epitopes from protein sequences using
|
| 17 |
+
an advanced deep learning model with attention mechanisms and bidirectional LSTM networks.
|
| 18 |
+
</p>
|
| 19 |
+
|
| 20 |
+
<h5 class="mt-4 mb-3"><i class="fas fa-upload me-2"></i>Step 1: Input Your Sequences</h5>
|
| 21 |
+
<p>
|
| 22 |
+
EpiPred requires protein sequence(s) in FASTA format and cannot handle nucleic acid sequences.
|
| 23 |
+
</p>
|
| 24 |
+
|
| 25 |
+
<div class="row mb-4">
|
| 26 |
+
<div class="col-md-6">
|
| 27 |
+
<div class="border rounded p-3 h-100">
|
| 28 |
+
<h6 class="text-primary">Option A: Paste Sequences</h6>
|
| 29 |
+
<p class="mb-0">
|
| 30 |
+
Paste protein sequence(s) in FASTA format into the text field marked by arrow A.
|
| 31 |
+
</p>
|
| 32 |
+
</div>
|
| 33 |
+
</div>
|
| 34 |
+
<div class="col-md-6">
|
| 35 |
+
<div class="border rounded p-3 h-100">
|
| 36 |
+
<h6 class="text-primary">Option B: Upload File</h6>
|
| 37 |
+
<p class="mb-0">
|
| 38 |
+
Upload a FASTA file using the file upload button marked by arrow B.
|
| 39 |
+
</p>
|
| 40 |
+
</div>
|
| 41 |
+
</div>
|
| 42 |
+
</div>
|
| 43 |
+
|
| 44 |
+
<h5 class="mt-4 mb-3"><i class="fas fa-play me-2"></i>Step 2: Submit for Analysis</h5>
|
| 45 |
+
<p>
|
| 46 |
+
Click the "Predict Epitopes" button (marked by arrow C) when protein sequences are entered.
|
| 47 |
+
The analysis may take a few minutes depending on the number and length of sequences.
|
| 48 |
+
</p>
|
| 49 |
+
|
| 50 |
+
<h5 class="mt-4 mb-3"><i class="fas fa-chart-line me-2"></i>Step 3: View Results</h5>
|
| 51 |
+
<p>
|
| 52 |
+
After the server successfully finishes the job, a results page will appear showing:
|
| 53 |
+
</p>
|
| 54 |
+
<ul>
|
| 55 |
+
<li><strong>Summary statistics</strong> - Total number of sequences analyzed and epitopes found</li>
|
| 56 |
+
<li><strong>Interactive threshold control</strong> - Adjust confidence threshold to filter results</li>
|
| 57 |
+
<li><strong>Sequence visualization</strong> - View sequences with epitope markup</li>
|
| 58 |
+
<li><strong>Detailed tables</strong> - B-cell and T-cell epitopes with confidence scores</li>
|
| 59 |
+
</ul>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
|
| 63 |
+
<!-- FASTA Format Guide -->
|
| 64 |
+
<div class="card shadow-sm mb-4">
|
| 65 |
+
<div class="card-header bg-info text-white">
|
| 66 |
+
<h4 class="mb-0"><i class="fas fa-file-alt me-2"></i>FASTA Format Guide</h4>
|
| 67 |
+
</div>
|
| 68 |
+
<div class="card-body">
|
| 69 |
+
<p>FASTA format is a text-based format for representing protein sequences. Here's what you need to know:</p>
|
| 70 |
+
|
| 71 |
+
<h6 class="text-info">Basic Structure:</h6>
|
| 72 |
+
<pre class="bg-light p-3 rounded"><code>>Header_Line_Starting_With_Greater_Than
|
| 73 |
+
AMINO_ACID_SEQUENCE_HERE
|
| 74 |
+
>Another_Sequence_Header
|
| 75 |
+
ANOTHER_AMINO_ACID_SEQUENCE</code></pre>
|
| 76 |
+
|
| 77 |
+
<h6 class="text-info mt-3">Rules:</h6>
|
| 78 |
+
<ul>
|
| 79 |
+
<li>Each sequence must start with a header line beginning with '<strong>></strong>'</li>
|
| 80 |
+
<li>Header lines can contain sequence names and descriptions</li>
|
| 81 |
+
<li>Sequence lines should contain only standard amino acid letters (A-Z)</li>
|
| 82 |
+
<li>Multiple sequences can be included in a single submission</li>
|
| 83 |
+
<li>Blank lines are ignored</li>
|
| 84 |
+
</ul>
|
| 85 |
+
|
| 86 |
+
<h6 class="text-info mt-3">Example:</h6>
|
| 87 |
+
<pre class="bg-light p-3 rounded"><code>>Human_Insulin_A_Chain
|
| 88 |
+
GIVEQCCTSICSLYQLENYCN
|
| 89 |
+
|
| 90 |
+
>Human_Insulin_B_Chain
|
| 91 |
+
FVNQHLCGSHLVEALYLVCGERGFFYTPKT
|
| 92 |
+
|
| 93 |
+
>Example_Antigen_Protein
|
| 94 |
+
MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL
|
| 95 |
+
CARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD</code></pre>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
<!-- Results Interpretation -->
|
| 100 |
+
<div class="card shadow-sm">
|
| 101 |
+
<div class="card-header bg-success text-white">
|
| 102 |
+
<h4 class="mb-0"><i class="fas fa-chart-bar me-2"></i>Interpreting Results</h4>
|
| 103 |
+
</div>
|
| 104 |
+
<div class="card-body">
|
| 105 |
+
<h6 class="text-success">Confidence Scores:</h6>
|
| 106 |
+
<p>
|
| 107 |
+
Each predicted epitope comes with a confidence score between 0.0 and 1.0:
|
| 108 |
+
</p>
|
| 109 |
+
<ul>
|
| 110 |
+
<li><strong>0.8 - 1.0:</strong> High confidence predictions</li>
|
| 111 |
+
<li><strong>0.6 - 0.8:</strong> Medium confidence predictions</li>
|
| 112 |
+
<li><strong>0.5 - 0.6:</strong> Lower confidence predictions</li>
|
| 113 |
+
<li><strong>Below 0.5:</strong> Not shown by default (adjust threshold to view)</li>
|
| 114 |
+
</ul>
|
| 115 |
+
|
| 116 |
+
<h6 class="text-success mt-3">Sequence Markup:</h6>
|
| 117 |
+
<div class="d-flex align-items-center mb-2">
|
| 118 |
+
<span class="epitope-b me-2">B</span>
|
| 119 |
+
<span>B-cell epitope regions</span>
|
| 120 |
+
</div>
|
| 121 |
+
<div class="d-flex align-items-center mb-2">
|
| 122 |
+
<span class="epitope-t me-2">T</span>
|
| 123 |
+
<span>T-cell epitope regions</span>
|
| 124 |
+
</div>
|
| 125 |
+
<div class="d-flex align-items-center">
|
| 126 |
+
<span class="non-epitope me-2">.</span>
|
| 127 |
+
<span>Non-epitope regions</span>
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
+
<h6 class="text-success mt-3">Position Ranges:</h6>
|
| 131 |
+
<p>
|
| 132 |
+
Position ranges are given in 1-based indexing (first amino acid is position 1).
|
| 133 |
+
For example, "15-34" means the epitope spans from the 15th to 34th amino acid.
|
| 134 |
+
</p>
|
| 135 |
+
</div>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
<div class="col-lg-4">
|
| 140 |
+
<!-- Quick Tips -->
|
| 141 |
+
<div class="card shadow-sm mb-4">
|
| 142 |
+
<div class="card-header bg-warning text-dark">
|
| 143 |
+
<h5 class="mb-0"><i class="fas fa-lightbulb me-2"></i>Quick Tips</h5>
|
| 144 |
+
</div>
|
| 145 |
+
<div class="card-body">
|
| 146 |
+
<div class="alert alert-info">
|
| 147 |
+
<i class="fas fa-info-circle me-2"></i>
|
| 148 |
+
<strong>Best Practices:</strong>
|
| 149 |
+
<ul class="mb-0 mt-2">
|
| 150 |
+
<li>Use descriptive sequence names in headers</li>
|
| 151 |
+
<li>Check sequences for typos before submission</li>
|
| 152 |
+
<li>Start with high confidence threshold (0.7-0.8)</li>
|
| 153 |
+
<li>Consider biological context when interpreting results</li>
|
| 154 |
+
</ul>
|
| 155 |
+
</div>
|
| 156 |
+
|
| 157 |
+
<div class="alert alert-warning">
|
| 158 |
+
<i class="fas fa-exclamation-triangle me-2"></i>
|
| 159 |
+
<strong>Common Issues:</strong>
|
| 160 |
+
<ul class="mb-0 mt-2">
|
| 161 |
+
<li>Missing '>' at start of header lines</li>
|
| 162 |
+
<li>Non-standard amino acid characters</li>
|
| 163 |
+
<li>Sequences that are too short (<10 AA)</li>
|
| 164 |
+
<li>Mixed protein and DNA sequences</li>
|
| 165 |
+
</ul>
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
</div>
|
| 169 |
+
|
| 170 |
+
<!-- Submission Limits -->
|
| 171 |
+
<div class="card shadow-sm mb-4">
|
| 172 |
+
<div class="card-header bg-secondary text-white">
|
| 173 |
+
<h5 class="mb-0"><i class="fas fa-ruler me-2"></i>Submission Limits</h5>
|
| 174 |
+
</div>
|
| 175 |
+
<div class="card-body">
|
| 176 |
+
<table class="table table-sm">
|
| 177 |
+
<tr>
|
| 178 |
+
<td><strong>Max sequences:</strong></td>
|
| 179 |
+
<td>50 per submission</td>
|
| 180 |
+
</tr>
|
| 181 |
+
<tr>
|
| 182 |
+
<td><strong>Max total length:</strong></td>
|
| 183 |
+
<td>300,000 amino acids</td>
|
| 184 |
+
</tr>
|
| 185 |
+
<tr>
|
| 186 |
+
<td><strong>Min sequence length:</strong></td>
|
| 187 |
+
<td>10 amino acids</td>
|
| 188 |
+
</tr>
|
| 189 |
+
<tr>
|
| 190 |
+
<td><strong>Max sequence length:</strong></td>
|
| 191 |
+
<td>6,000 amino acids</td>
|
| 192 |
+
</tr>
|
| 193 |
+
<tr>
|
| 194 |
+
<td><strong>Max file size:</strong></td>
|
| 195 |
+
<td>16 MB</td>
|
| 196 |
+
</tr>
|
| 197 |
+
</table>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
|
| 201 |
+
<!-- Navigation -->
|
| 202 |
+
<div class="card shadow-sm">
|
| 203 |
+
<div class="card-header bg-dark text-white">
|
| 204 |
+
<h5 class="mb-0"><i class="fas fa-compass me-2"></i>Navigation</h5>
|
| 205 |
+
</div>
|
| 206 |
+
<div class="card-body">
|
| 207 |
+
<div class="d-grid gap-2">
|
| 208 |
+
<a href="{{ url_for('index') }}" class="btn btn-primary">
|
| 209 |
+
<i class="fas fa-home me-2"></i>Start Analysis
|
| 210 |
+
</a>
|
| 211 |
+
<a href="{{ url_for('about') }}" class="btn btn-outline-secondary">
|
| 212 |
+
<i class="fas fa-info-circle me-2"></i>About the Method
|
| 213 |
+
</a>
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
</div>
|
| 218 |
+
</div>
|
| 219 |
+
{% endblock %}
|
templates/results.html
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block page_title %}Prediction Results{% endblock %}
|
| 4 |
+
{% block page_subtitle %}Epitope prediction results for {{ results.total_sequences }} sequence(s){% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block extra_head %}
|
| 7 |
+
<style>
|
| 8 |
+
.sequence-display {
|
| 9 |
+
font-family: 'Courier New', monospace;
|
| 10 |
+
font-size: 14px;
|
| 11 |
+
line-height: 1.4;
|
| 12 |
+
word-break: break-all;
|
| 13 |
+
background: #f8f9fa;
|
| 14 |
+
padding: 15px;
|
| 15 |
+
border-radius: 5px;
|
| 16 |
+
border: 1px solid #dee2e6;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
.epitope-b { background-color: #ff6b6b; color: white; padding: 1px 2px; border-radius: 2px; }
|
| 20 |
+
.epitope-t { background-color: #4ecdc4; color: white; padding: 1px 2px; border-radius: 2px; }
|
| 21 |
+
.non-epitope { color: #6c757d; }
|
| 22 |
+
|
| 23 |
+
.confidence-bar {
|
| 24 |
+
height: 20px;
|
| 25 |
+
background: linear-gradient(90deg, #dc3545 0%, #ffc107 50%, #28a745 100%);
|
| 26 |
+
border-radius: 10px;
|
| 27 |
+
position: relative;
|
| 28 |
+
overflow: hidden;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
.confidence-indicator {
|
| 32 |
+
position: absolute;
|
| 33 |
+
top: 0;
|
| 34 |
+
left: 0;
|
| 35 |
+
height: 100%;
|
| 36 |
+
background: rgba(255,255,255,0.3);
|
| 37 |
+
border-radius: 10px;
|
| 38 |
+
transition: width 0.3s ease;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.epitope-card {
|
| 42 |
+
transition: transform 0.2s ease;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.epitope-card:hover {
|
| 46 |
+
transform: translateY(-2px);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.threshold-slider {
|
| 50 |
+
width: 100%;
|
| 51 |
+
}
|
| 52 |
+
</style>
|
| 53 |
+
{% endblock %}
|
| 54 |
+
|
| 55 |
+
{% block content %}
|
| 56 |
+
<div class="row">
|
| 57 |
+
<div class="col-12">
|
| 58 |
+
<!-- Results Header -->
|
| 59 |
+
<div class="card shadow-sm mb-4">
|
| 60 |
+
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
|
| 61 |
+
<div>
|
| 62 |
+
<h4 class="mb-0">
|
| 63 |
+
<i class="fas fa-check-circle me-2"></i>Prediction Complete
|
| 64 |
+
</h4>
|
| 65 |
+
<small>Job ID: {{ job_id }}</small>
|
| 66 |
+
</div>
|
| 67 |
+
<div class="dropdown">
|
| 68 |
+
<button class="btn btn-light dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
| 69 |
+
<i class="fas fa-download me-1"></i>Download Results
|
| 70 |
+
</button>
|
| 71 |
+
<ul class="dropdown-menu">
|
| 72 |
+
<li><a class="dropdown-item" href="{{ url_for('download_results', job_id=job_id, format='csv') }}">
|
| 73 |
+
<i class="fas fa-file-csv me-2"></i>CSV Format
|
| 74 |
+
</a></li>
|
| 75 |
+
<li><a class="dropdown-item" href="{{ url_for('download_results', job_id=job_id, format='json') }}">
|
| 76 |
+
<i class="fas fa-file-code me-2"></i>JSON Format
|
| 77 |
+
</a></li>
|
| 78 |
+
</ul>
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
<div class="card-body">
|
| 82 |
+
<div class="row text-center">
|
| 83 |
+
<div class="col-md-3">
|
| 84 |
+
<h5 class="text-primary">{{ results.total_sequences }}</h5>
|
| 85 |
+
<small class="text-muted">Sequences Analyzed</small>
|
| 86 |
+
</div>
|
| 87 |
+
<div class="col-md-3">
|
| 88 |
+
<h5 class="text-danger" id="total-b-cell">0</h5>
|
| 89 |
+
<small class="text-muted">B-cell Epitopes</small>
|
| 90 |
+
</div>
|
| 91 |
+
<div class="col-md-3">
|
| 92 |
+
<h5 class="text-info" id="total-t-cell">0</h5>
|
| 93 |
+
<small class="text-muted">T-cell Epitopes</small>
|
| 94 |
+
</div>
|
| 95 |
+
<div class="col-md-3">
|
| 96 |
+
<h5 class="text-success">{{ results.timestamp.split('T')[0] }}</h5>
|
| 97 |
+
<small class="text-muted">Analysis Date</small>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
|
| 103 |
+
<!-- Threshold Control -->
|
| 104 |
+
<div class="card shadow-sm mb-4">
|
| 105 |
+
<div class="card-header">
|
| 106 |
+
<h5 class="mb-0">
|
| 107 |
+
<i class="fas fa-sliders-h me-2"></i>Epitope Threshold Control
|
| 108 |
+
</h5>
|
| 109 |
+
</div>
|
| 110 |
+
<div class="card-body">
|
| 111 |
+
<div class="row align-items-center">
|
| 112 |
+
<div class="col-md-8">
|
| 113 |
+
<label for="thresholdSlider" class="form-label">
|
| 114 |
+
Confidence Threshold: <span id="thresholdValue">0.50</span>
|
| 115 |
+
</label>
|
| 116 |
+
<input type="range" class="form-range threshold-slider"
|
| 117 |
+
id="thresholdSlider"
|
| 118 |
+
min="0" max="1" step="0.01" value="0.5">
|
| 119 |
+
<div class="d-flex justify-content-between">
|
| 120 |
+
<small class="text-muted">0.0 (Low)</small>
|
| 121 |
+
<small class="text-muted">1.0 (High)</small>
|
| 122 |
+
</div>
|
| 123 |
+
</div>
|
| 124 |
+
<div class="col-md-4">
|
| 125 |
+
<div class="alert alert-info mb-0">
|
| 126 |
+
<i class="fas fa-info-circle me-1"></i>
|
| 127 |
+
<small>Adjust threshold to filter epitopes by confidence score</small>
|
| 128 |
+
</div>
|
| 129 |
+
</div>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<!-- Results for each sequence -->
|
| 135 |
+
{% for seq_name, data in results.results.items() %}
|
| 136 |
+
<div class="card shadow-sm mb-4 sequence-result">
|
| 137 |
+
<div class="card-header">
|
| 138 |
+
<div class="d-flex justify-content-between align-items-center">
|
| 139 |
+
<h5 class="mb-0">
|
| 140 |
+
<i class="fas fa-dna me-2"></i>{{ seq_name }}
|
| 141 |
+
</h5>
|
| 142 |
+
<span class="badge bg-secondary">{{ data.length }} amino acids</span>
|
| 143 |
+
</div>
|
| 144 |
+
</div>
|
| 145 |
+
<div class="card-body">
|
| 146 |
+
<!-- Sequence Visualization -->
|
| 147 |
+
<div class="mb-4">
|
| 148 |
+
<h6>Sequence with Epitope Markup:</h6>
|
| 149 |
+
<div class="sequence-display" id="sequence-{{ loop.index }}">
|
| 150 |
+
<!-- Will be populated by JavaScript -->
|
| 151 |
+
</div>
|
| 152 |
+
<div class="mt-2">
|
| 153 |
+
<small class="text-muted">
|
| 154 |
+
<span class="epitope-b">B</span> B-cell epitopes
|
| 155 |
+
<span class="epitope-t">T</span> T-cell epitopes
|
| 156 |
+
<span class="non-epitope">.</span> Non-epitope regions
|
| 157 |
+
</small>
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
|
| 161 |
+
<!-- Epitope Tables -->
|
| 162 |
+
<div class="row">
|
| 163 |
+
<!-- B-cell Epitopes -->
|
| 164 |
+
<div class="col-md-6">
|
| 165 |
+
<h6 class="text-danger">
|
| 166 |
+
<i class="fas fa-circle me-1"></i>B-cell Epitopes
|
| 167 |
+
<span class="badge bg-danger" id="b-count-{{ loop.index }}">{{ data.b_cell_epitopes|length }}</span>
|
| 168 |
+
</h6>
|
| 169 |
+
<div class="table-responsive">
|
| 170 |
+
<table class="table table-sm">
|
| 171 |
+
<thead class="table-light">
|
| 172 |
+
<tr>
|
| 173 |
+
<th>Sequence</th>
|
| 174 |
+
<th>Position</th>
|
| 175 |
+
<th>Confidence</th>
|
| 176 |
+
</tr>
|
| 177 |
+
</thead>
|
| 178 |
+
<tbody id="b-epitopes-{{ loop.index }}">
|
| 179 |
+
{% for epitope, confidence, pos_range in data.b_cell_epitopes %}
|
| 180 |
+
<tr class="epitope-row" data-confidence="{{ confidence }}">
|
| 181 |
+
<td class="font-monospace">{{ epitope }}</td>
|
| 182 |
+
<td>{{ pos_range }}</td>
|
| 183 |
+
<td>
|
| 184 |
+
<div class="d-flex align-items-center">
|
| 185 |
+
<div class="confidence-bar me-2" style="width: 60px;">
|
| 186 |
+
<div class="confidence-indicator" style="width: {{ (confidence * 100)|round }}%;"></div>
|
| 187 |
+
</div>
|
| 188 |
+
<small>{{ "%.3f"|format(confidence) }}</small>
|
| 189 |
+
</div>
|
| 190 |
+
</td>
|
| 191 |
+
</tr>
|
| 192 |
+
{% endfor %}
|
| 193 |
+
</tbody>
|
| 194 |
+
</table>
|
| 195 |
+
</div>
|
| 196 |
+
</div>
|
| 197 |
+
|
| 198 |
+
<!-- T-cell Epitopes -->
|
| 199 |
+
<div class="col-md-6">
|
| 200 |
+
<h6 class="text-info">
|
| 201 |
+
<i class="fas fa-circle me-1"></i>T-cell Epitopes
|
| 202 |
+
<span class="badge bg-info" id="t-count-{{ loop.index }}">{{ data.t_cell_epitopes|length }}</span>
|
| 203 |
+
</h6>
|
| 204 |
+
<div class="table-responsive">
|
| 205 |
+
<table class="table table-sm">
|
| 206 |
+
<thead class="table-light">
|
| 207 |
+
<tr>
|
| 208 |
+
<th>Sequence</th>
|
| 209 |
+
<th>Position</th>
|
| 210 |
+
<th>Confidence</th>
|
| 211 |
+
</tr>
|
| 212 |
+
</thead>
|
| 213 |
+
<tbody id="t-epitopes-{{ loop.index }}">
|
| 214 |
+
{% for epitope, confidence, pos_range in data.t_cell_epitopes %}
|
| 215 |
+
<tr class="epitope-row" data-confidence="{{ confidence }}">
|
| 216 |
+
<td class="font-monospace">{{ epitope }}</td>
|
| 217 |
+
<td>{{ pos_range }}</td>
|
| 218 |
+
<td>
|
| 219 |
+
<div class="d-flex align-items-center">
|
| 220 |
+
<div class="confidence-bar me-2" style="width: 60px;">
|
| 221 |
+
<div class="confidence-indicator" style="width: {{ (confidence * 100)|round }}%;"></div>
|
| 222 |
+
</div>
|
| 223 |
+
<small>{{ "%.3f"|format(confidence) }}</small>
|
| 224 |
+
</div>
|
| 225 |
+
</td>
|
| 226 |
+
</tr>
|
| 227 |
+
{% endfor %}
|
| 228 |
+
</tbody>
|
| 229 |
+
</table>
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
+
</div>
|
| 233 |
+
</div>
|
| 234 |
+
</div>
|
| 235 |
+
|
| 236 |
+
<!-- Store data for JavaScript -->
|
| 237 |
+
<script type="application/json" id="sequence-data-{{ loop.index }}">
|
| 238 |
+
{
|
| 239 |
+
"sequence": {{ data.sequence|tojson }},
|
| 240 |
+
"b_cell_epitopes": {{ data.b_cell_epitopes|tojson }},
|
| 241 |
+
"t_cell_epitopes": {{ data.t_cell_epitopes|tojson }}
|
| 242 |
+
}
|
| 243 |
+
</script>
|
| 244 |
+
{% endfor %}
|
| 245 |
+
|
| 246 |
+
<!-- Back to Home -->
|
| 247 |
+
<div class="text-center mt-4">
|
| 248 |
+
<a href="{{ url_for('index') }}" class="btn btn-primary">
|
| 249 |
+
<i class="fas fa-arrow-left me-2"></i>Analyze New Sequences
|
| 250 |
+
</a>
|
| 251 |
+
</div>
|
| 252 |
+
</div>
|
| 253 |
+
</div>
|
| 254 |
+
{% endblock %}
|
| 255 |
+
|
| 256 |
+
{% block extra_scripts %}
|
| 257 |
+
<script>
|
| 258 |
+
// Store all sequence data
|
| 259 |
+
const sequenceData = {};
|
| 260 |
+
{% for seq_name, data in results.results.items() %}
|
| 261 |
+
sequenceData[{{ loop.index }}] = JSON.parse(document.getElementById('sequence-data-{{ loop.index }}').textContent);
|
| 262 |
+
{% endfor %}
|
| 263 |
+
|
| 264 |
+
// Initialize sequence displays
|
| 265 |
+
function initializeSequenceDisplays() {
|
| 266 |
+
Object.keys(sequenceData).forEach(index => {
|
| 267 |
+
updateSequenceDisplay(index, 0.5);
|
| 268 |
+
});
|
| 269 |
+
updateTotalCounts(0.5);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
// Update sequence display with epitope markup
|
| 273 |
+
function updateSequenceDisplay(index, threshold) {
|
| 274 |
+
const data = sequenceData[index];
|
| 275 |
+
const sequence = data.sequence;
|
| 276 |
+
let markup = new Array(sequence.length).fill('.');
|
| 277 |
+
|
| 278 |
+
// Mark B-cell epitopes
|
| 279 |
+
data.b_cell_epitopes.forEach(([epitope, confidence, pos_range]) => {
|
| 280 |
+
if (confidence >= threshold) {
|
| 281 |
+
const [start, end] = pos_range.split('-').map(x => parseInt(x) - 1);
|
| 282 |
+
for (let i = start; i <= Math.min(end, sequence.length - 1); i++) {
|
| 283 |
+
markup[i] = 'B';
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
});
|
| 287 |
+
|
| 288 |
+
// Mark T-cell epitopes (may override B-cell)
|
| 289 |
+
data.t_cell_epitopes.forEach(([epitope, confidence, pos_range]) => {
|
| 290 |
+
if (confidence >= threshold) {
|
| 291 |
+
const [start, end] = pos_range.split('-').map(x => parseInt(x) - 1);
|
| 292 |
+
for (let i = start; i <= Math.min(end, sequence.length - 1); i++) {
|
| 293 |
+
markup[i] = 'T';
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
});
|
| 297 |
+
|
| 298 |
+
// Create HTML with markup
|
| 299 |
+
let html = '';
|
| 300 |
+
for (let i = 0; i < sequence.length; i++) {
|
| 301 |
+
const aa = sequence[i];
|
| 302 |
+
if (markup[i] === 'B') {
|
| 303 |
+
html += `<span class="epitope-b">${aa}</span>`;
|
| 304 |
+
} else if (markup[i] === 'T') {
|
| 305 |
+
html += `<span class="epitope-t">${aa}</span>`;
|
| 306 |
+
} else {
|
| 307 |
+
html += `<span class="non-epitope">${aa}</span>`;
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
// Add line breaks every 60 characters
|
| 311 |
+
if ((i + 1) % 60 === 0) {
|
| 312 |
+
html += '<br>';
|
| 313 |
+
}
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
document.getElementById(`sequence-${index}`).innerHTML = html;
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
// Update epitope tables based on threshold
|
| 320 |
+
function updateEpitopeTables(threshold) {
|
| 321 |
+
Object.keys(sequenceData).forEach(index => {
|
| 322 |
+
const data = sequenceData[index];
|
| 323 |
+
|
| 324 |
+
// Update B-cell epitopes
|
| 325 |
+
const bRows = document.querySelectorAll(`#b-epitopes-${index} .epitope-row`);
|
| 326 |
+
let bCount = 0;
|
| 327 |
+
bRows.forEach(row => {
|
| 328 |
+
const confidence = parseFloat(row.dataset.confidence);
|
| 329 |
+
if (confidence >= threshold) {
|
| 330 |
+
row.style.display = '';
|
| 331 |
+
bCount++;
|
| 332 |
+
} else {
|
| 333 |
+
row.style.display = 'none';
|
| 334 |
+
}
|
| 335 |
+
});
|
| 336 |
+
document.getElementById(`b-count-${index}`).textContent = bCount;
|
| 337 |
+
|
| 338 |
+
// Update T-cell epitopes
|
| 339 |
+
const tRows = document.querySelectorAll(`#t-epitopes-${index} .epitope-row`);
|
| 340 |
+
let tCount = 0;
|
| 341 |
+
tRows.forEach(row => {
|
| 342 |
+
const confidence = parseFloat(row.dataset.confidence);
|
| 343 |
+
if (confidence >= threshold) {
|
| 344 |
+
row.style.display = '';
|
| 345 |
+
tCount++;
|
| 346 |
+
} else {
|
| 347 |
+
row.style.display = 'none';
|
| 348 |
+
}
|
| 349 |
+
});
|
| 350 |
+
document.getElementById(`t-count-${index}`).textContent = tCount;
|
| 351 |
+
});
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
// Update total counts
|
| 355 |
+
function updateTotalCounts(threshold) {
|
| 356 |
+
let totalB = 0, totalT = 0;
|
| 357 |
+
|
| 358 |
+
Object.keys(sequenceData).forEach(index => {
|
| 359 |
+
const data = sequenceData[index];
|
| 360 |
+
|
| 361 |
+
data.b_cell_epitopes.forEach(([epitope, confidence, pos_range]) => {
|
| 362 |
+
if (confidence >= threshold) totalB++;
|
| 363 |
+
});
|
| 364 |
+
|
| 365 |
+
data.t_cell_epitopes.forEach(([epitope, confidence, pos_range]) => {
|
| 366 |
+
if (confidence >= threshold) totalT++;
|
| 367 |
+
});
|
| 368 |
+
});
|
| 369 |
+
|
| 370 |
+
document.getElementById('total-b-cell').textContent = totalB;
|
| 371 |
+
document.getElementById('total-t-cell').textContent = totalT;
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
// Threshold slider event
|
| 375 |
+
document.getElementById('thresholdSlider').addEventListener('input', function(e) {
|
| 376 |
+
const threshold = parseFloat(e.target.value);
|
| 377 |
+
document.getElementById('thresholdValue').textContent = threshold.toFixed(2);
|
| 378 |
+
|
| 379 |
+
// Update displays
|
| 380 |
+
Object.keys(sequenceData).forEach(index => {
|
| 381 |
+
updateSequenceDisplay(index, threshold);
|
| 382 |
+
});
|
| 383 |
+
updateEpitopeTables(threshold);
|
| 384 |
+
updateTotalCounts(threshold);
|
| 385 |
+
});
|
| 386 |
+
|
| 387 |
+
// Initialize on page load
|
| 388 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 389 |
+
initializeSequenceDisplays();
|
| 390 |
+
});
|
| 391 |
+
</script>
|
| 392 |
+
{% endblock %}
|
test_app.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for EpiPred web application
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import tempfile
|
| 9 |
+
import json
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
# Add current directory to path
|
| 13 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 14 |
+
|
| 15 |
+
def test_imports():
|
| 16 |
+
"""Test if all required modules can be imported"""
|
| 17 |
+
print("Testing imports...")
|
| 18 |
+
|
| 19 |
+
try:
|
| 20 |
+
import flask
|
| 21 |
+
print(f"✓ Flask {flask.__version__}")
|
| 22 |
+
except ImportError as e:
|
| 23 |
+
print(f"✗ Flask import failed: {e}")
|
| 24 |
+
return False
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
import tensorflow as tf
|
| 28 |
+
print(f"✓ TensorFlow {tf.__version__}")
|
| 29 |
+
except ImportError as e:
|
| 30 |
+
print(f"✗ TensorFlow import failed: {e}")
|
| 31 |
+
return False
|
| 32 |
+
|
| 33 |
+
try:
|
| 34 |
+
import numpy as np
|
| 35 |
+
print(f"✓ NumPy {np.__version__}")
|
| 36 |
+
except ImportError as e:
|
| 37 |
+
print(f"✗ NumPy import failed: {e}")
|
| 38 |
+
return False
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
import pandas as pd
|
| 42 |
+
print(f"✓ Pandas {pd.__version__}")
|
| 43 |
+
except ImportError as e:
|
| 44 |
+
print(f"✗ Pandas import failed: {e}")
|
| 45 |
+
return False
|
| 46 |
+
|
| 47 |
+
return True
|
| 48 |
+
|
| 49 |
+
def test_app_creation():
|
| 50 |
+
"""Test if Flask app can be created"""
|
| 51 |
+
print("\nTesting Flask app creation...")
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
from app import app
|
| 55 |
+
print("✓ Flask app created successfully")
|
| 56 |
+
return True
|
| 57 |
+
except Exception as e:
|
| 58 |
+
print(f"✗ Flask app creation failed: {e}")
|
| 59 |
+
return False
|
| 60 |
+
|
| 61 |
+
def test_model_predictor():
|
| 62 |
+
"""Test model predictor initialization"""
|
| 63 |
+
print("\nTesting model predictor...")
|
| 64 |
+
|
| 65 |
+
try:
|
| 66 |
+
from model_predictor import EpitopePredictor
|
| 67 |
+
|
| 68 |
+
# Try to create predictor (may fail if no model files)
|
| 69 |
+
try:
|
| 70 |
+
predictor = EpitopePredictor()
|
| 71 |
+
print("✓ Model predictor created successfully")
|
| 72 |
+
|
| 73 |
+
# Test model info
|
| 74 |
+
info = predictor.get_model_info()
|
| 75 |
+
print(f"✓ Model info retrieved: {len(info)} parameters")
|
| 76 |
+
|
| 77 |
+
return True
|
| 78 |
+
except Exception as e:
|
| 79 |
+
print(f"⚠ Model predictor creation failed (expected if no model files): {e}")
|
| 80 |
+
return True # This is expected without model files
|
| 81 |
+
|
| 82 |
+
except Exception as e:
|
| 83 |
+
print(f"✗ Model predictor import failed: {e}")
|
| 84 |
+
return False
|
| 85 |
+
|
| 86 |
+
def test_fasta_parsing():
|
| 87 |
+
"""Test FASTA parsing functionality"""
|
| 88 |
+
print("\nTesting FASTA parsing...")
|
| 89 |
+
|
| 90 |
+
try:
|
| 91 |
+
from app import parse_fasta_text, validate_sequences
|
| 92 |
+
|
| 93 |
+
# Test valid FASTA
|
| 94 |
+
test_fasta = """>Test_Sequence_1
|
| 95 |
+
MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL
|
| 96 |
+
|
| 97 |
+
>Test_Sequence_2
|
| 98 |
+
CARFASLIYGKFVRQPQVWLRIQNYSVMDICDEHQGVMVPGVGVPQALQKYNPD"""
|
| 99 |
+
|
| 100 |
+
sequences = parse_fasta_text(test_fasta)
|
| 101 |
+
print(f"✓ Parsed {len(sequences)} sequences")
|
| 102 |
+
|
| 103 |
+
# Test validation
|
| 104 |
+
errors = validate_sequences(sequences)
|
| 105 |
+
if not errors:
|
| 106 |
+
print("✓ Sequence validation passed")
|
| 107 |
+
else:
|
| 108 |
+
print(f"⚠ Validation warnings: {errors}")
|
| 109 |
+
|
| 110 |
+
return True
|
| 111 |
+
|
| 112 |
+
except Exception as e:
|
| 113 |
+
print(f"✗ FASTA parsing test failed: {e}")
|
| 114 |
+
return False
|
| 115 |
+
|
| 116 |
+
def test_routes():
|
| 117 |
+
"""Test Flask routes"""
|
| 118 |
+
print("\nTesting Flask routes...")
|
| 119 |
+
|
| 120 |
+
try:
|
| 121 |
+
from app import app
|
| 122 |
+
|
| 123 |
+
with app.test_client() as client:
|
| 124 |
+
# Test main page
|
| 125 |
+
response = client.get('/')
|
| 126 |
+
if response.status_code == 200:
|
| 127 |
+
print("✓ Main page route works")
|
| 128 |
+
else:
|
| 129 |
+
print(f"✗ Main page returned status {response.status_code}")
|
| 130 |
+
return False
|
| 131 |
+
|
| 132 |
+
# Test instructions page
|
| 133 |
+
response = client.get('/instructions')
|
| 134 |
+
if response.status_code == 200:
|
| 135 |
+
print("✓ Instructions page route works")
|
| 136 |
+
else:
|
| 137 |
+
print(f"✗ Instructions page returned status {response.status_code}")
|
| 138 |
+
return False
|
| 139 |
+
|
| 140 |
+
# Test about page
|
| 141 |
+
response = client.get('/about')
|
| 142 |
+
if response.status_code == 200:
|
| 143 |
+
print("✓ About page route works")
|
| 144 |
+
else:
|
| 145 |
+
print(f"✗ About page returned status {response.status_code}")
|
| 146 |
+
return False
|
| 147 |
+
|
| 148 |
+
return True
|
| 149 |
+
|
| 150 |
+
except Exception as e:
|
| 151 |
+
print(f"✗ Route testing failed: {e}")
|
| 152 |
+
return False
|
| 153 |
+
|
| 154 |
+
def test_file_structure():
|
| 155 |
+
"""Test if all required files exist"""
|
| 156 |
+
print("\nTesting file structure...")
|
| 157 |
+
|
| 158 |
+
required_files = [
|
| 159 |
+
'app.py',
|
| 160 |
+
'model_predictor.py',
|
| 161 |
+
'run.py',
|
| 162 |
+
'requirements.txt',
|
| 163 |
+
'templates/base.html',
|
| 164 |
+
'templates/index.html',
|
| 165 |
+
'templates/results.html',
|
| 166 |
+
'templates/instructions.html',
|
| 167 |
+
'templates/about.html',
|
| 168 |
+
'static/css/style.css',
|
| 169 |
+
'static/js/main.js'
|
| 170 |
+
]
|
| 171 |
+
|
| 172 |
+
missing_files = []
|
| 173 |
+
for file_path in required_files:
|
| 174 |
+
if not os.path.exists(file_path):
|
| 175 |
+
missing_files.append(file_path)
|
| 176 |
+
|
| 177 |
+
if missing_files:
|
| 178 |
+
print(f"✗ Missing files: {', '.join(missing_files)}")
|
| 179 |
+
return False
|
| 180 |
+
else:
|
| 181 |
+
print(f"✓ All {len(required_files)} required files present")
|
| 182 |
+
return True
|
| 183 |
+
|
| 184 |
+
def main():
|
| 185 |
+
"""Run all tests"""
|
| 186 |
+
print("EpiPred Web Application Test Suite")
|
| 187 |
+
print("=" * 40)
|
| 188 |
+
|
| 189 |
+
tests = [
|
| 190 |
+
("File Structure", test_file_structure),
|
| 191 |
+
("Python Imports", test_imports),
|
| 192 |
+
("Flask App Creation", test_app_creation),
|
| 193 |
+
("Model Predictor", test_model_predictor),
|
| 194 |
+
("FASTA Parsing", test_fasta_parsing),
|
| 195 |
+
("Flask Routes", test_routes)
|
| 196 |
+
]
|
| 197 |
+
|
| 198 |
+
passed = 0
|
| 199 |
+
total = len(tests)
|
| 200 |
+
|
| 201 |
+
for test_name, test_func in tests:
|
| 202 |
+
print(f"\n{test_name}:")
|
| 203 |
+
print("-" * len(test_name))
|
| 204 |
+
|
| 205 |
+
try:
|
| 206 |
+
if test_func():
|
| 207 |
+
passed += 1
|
| 208 |
+
print(f"✓ {test_name} PASSED")
|
| 209 |
+
else:
|
| 210 |
+
print(f"✗ {test_name} FAILED")
|
| 211 |
+
except Exception as e:
|
| 212 |
+
print(f"✗ {test_name} ERROR: {e}")
|
| 213 |
+
|
| 214 |
+
print("\n" + "=" * 40)
|
| 215 |
+
print(f"Test Results: {passed}/{total} tests passed")
|
| 216 |
+
|
| 217 |
+
if passed == total:
|
| 218 |
+
print("🎉 All tests passed! The application should work correctly.")
|
| 219 |
+
print("\nTo start the application, run:")
|
| 220 |
+
print(" python run.py")
|
| 221 |
+
print("\nThen open http://localhost:5000 in your browser.")
|
| 222 |
+
else:
|
| 223 |
+
print("⚠ Some tests failed. Please check the errors above.")
|
| 224 |
+
if passed >= total - 1: # Allow model predictor to fail
|
| 225 |
+
print("\nThe application may still work if only model loading failed.")
|
| 226 |
+
print("Make sure model files are available for full functionality.")
|
| 227 |
+
|
| 228 |
+
return passed == total
|
| 229 |
+
|
| 230 |
+
if __name__ == '__main__':
|
| 231 |
+
success = main()
|
| 232 |
+
sys.exit(0 if success else 1)
|
test_deployment.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for deployment compatibility
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
def test_basic_imports():
|
| 10 |
+
"""Test basic Python imports"""
|
| 11 |
+
print("Testing basic imports...")
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
import flask
|
| 15 |
+
print(f"✓ Flask {flask.__version__}")
|
| 16 |
+
except ImportError as e:
|
| 17 |
+
print(f"✗ Flask: {e}")
|
| 18 |
+
return False
|
| 19 |
+
|
| 20 |
+
try:
|
| 21 |
+
import numpy as np
|
| 22 |
+
print(f"✓ NumPy {np.__version__}")
|
| 23 |
+
except ImportError as e:
|
| 24 |
+
print(f"✗ NumPy: {e}")
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
import pandas as pd
|
| 29 |
+
print(f"✓ Pandas {pd.__version__}")
|
| 30 |
+
except ImportError as e:
|
| 31 |
+
print(f"✗ Pandas: {e}")
|
| 32 |
+
return False
|
| 33 |
+
|
| 34 |
+
return True
|
| 35 |
+
|
| 36 |
+
def test_tensorflow():
|
| 37 |
+
"""Test TensorFlow import"""
|
| 38 |
+
print("\nTesting TensorFlow...")
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
import tensorflow as tf
|
| 42 |
+
print(f"✓ TensorFlow {tf.__version__}")
|
| 43 |
+
return True
|
| 44 |
+
except ImportError as e:
|
| 45 |
+
print(f"⚠ TensorFlow not available: {e}")
|
| 46 |
+
print(" App will run in demo mode")
|
| 47 |
+
return False
|
| 48 |
+
|
| 49 |
+
def test_model_predictor():
|
| 50 |
+
"""Test model predictor"""
|
| 51 |
+
print("\nTesting model predictor...")
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
from model_predictor import EpitopePredictor
|
| 55 |
+
predictor = EpitopePredictor()
|
| 56 |
+
|
| 57 |
+
if predictor.model is None:
|
| 58 |
+
print("⚠ Model not loaded - will use demo mode")
|
| 59 |
+
else:
|
| 60 |
+
print("✓ Model loaded successfully")
|
| 61 |
+
|
| 62 |
+
# Test prediction with demo sequence
|
| 63 |
+
test_seq = "MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL"
|
| 64 |
+
b_epitopes, t_epitopes = predictor.predict_epitopes(test_seq)
|
| 65 |
+
|
| 66 |
+
print(f"✓ Prediction test: {len(b_epitopes)} B-cell, {len(t_epitopes)} T-cell epitopes")
|
| 67 |
+
return True
|
| 68 |
+
|
| 69 |
+
except Exception as e:
|
| 70 |
+
print(f"✗ Model predictor failed: {e}")
|
| 71 |
+
return False
|
| 72 |
+
|
| 73 |
+
def test_flask_app():
|
| 74 |
+
"""Test Flask app creation"""
|
| 75 |
+
print("\nTesting Flask app...")
|
| 76 |
+
|
| 77 |
+
try:
|
| 78 |
+
from app import app
|
| 79 |
+
|
| 80 |
+
with app.test_client() as client:
|
| 81 |
+
# Test health endpoint
|
| 82 |
+
response = client.get('/health')
|
| 83 |
+
if response.status_code == 200:
|
| 84 |
+
print("✓ Health endpoint working")
|
| 85 |
+
else:
|
| 86 |
+
print(f"⚠ Health endpoint returned {response.status_code}")
|
| 87 |
+
|
| 88 |
+
# Test main page
|
| 89 |
+
response = client.get('/')
|
| 90 |
+
if response.status_code == 200:
|
| 91 |
+
print("✓ Main page working")
|
| 92 |
+
else:
|
| 93 |
+
print(f"⚠ Main page returned {response.status_code}")
|
| 94 |
+
|
| 95 |
+
return True
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
print(f"✗ Flask app test failed: {e}")
|
| 99 |
+
return False
|
| 100 |
+
|
| 101 |
+
def main():
|
| 102 |
+
"""Run all deployment tests"""
|
| 103 |
+
print("EpiPred Deployment Test")
|
| 104 |
+
print("=" * 30)
|
| 105 |
+
|
| 106 |
+
tests = [
|
| 107 |
+
("Basic Imports", test_basic_imports),
|
| 108 |
+
("TensorFlow", test_tensorflow),
|
| 109 |
+
("Model Predictor", test_model_predictor),
|
| 110 |
+
("Flask App", test_flask_app)
|
| 111 |
+
]
|
| 112 |
+
|
| 113 |
+
passed = 0
|
| 114 |
+
total = len(tests)
|
| 115 |
+
|
| 116 |
+
for test_name, test_func in tests:
|
| 117 |
+
try:
|
| 118 |
+
if test_func():
|
| 119 |
+
passed += 1
|
| 120 |
+
except Exception as e:
|
| 121 |
+
print(f"✗ {test_name} ERROR: {e}")
|
| 122 |
+
|
| 123 |
+
print("\n" + "=" * 30)
|
| 124 |
+
print(f"Results: {passed}/{total} tests passed")
|
| 125 |
+
|
| 126 |
+
if passed >= 3: # Allow TensorFlow to fail
|
| 127 |
+
print("🎉 Deployment should work!")
|
| 128 |
+
if passed < total:
|
| 129 |
+
print("⚠ Some features may be limited (demo mode)")
|
| 130 |
+
else:
|
| 131 |
+
print("❌ Deployment may have issues")
|
| 132 |
+
|
| 133 |
+
return passed >= 3
|
| 134 |
+
|
| 135 |
+
if __name__ == '__main__':
|
| 136 |
+
success = main()
|
| 137 |
+
sys.exit(0 if success else 1)
|
test_gradio.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for Gradio app
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
def test_imports():
|
| 10 |
+
"""Test if all required modules can be imported"""
|
| 11 |
+
print("Testing imports...")
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
import gradio as gr
|
| 15 |
+
print(f"✅ Gradio {gr.__version__}")
|
| 16 |
+
except ImportError as e:
|
| 17 |
+
print(f"❌ Gradio: {e}")
|
| 18 |
+
return False
|
| 19 |
+
|
| 20 |
+
try:
|
| 21 |
+
import pandas as pd
|
| 22 |
+
print(f"✅ Pandas {pd.__version__}")
|
| 23 |
+
except ImportError as e:
|
| 24 |
+
print(f"❌ Pandas: {e}")
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
import numpy as np
|
| 29 |
+
print(f"✅ NumPy {np.__version__}")
|
| 30 |
+
except ImportError as e:
|
| 31 |
+
print(f"❌ NumPy: {e}")
|
| 32 |
+
return False
|
| 33 |
+
|
| 34 |
+
return True
|
| 35 |
+
|
| 36 |
+
def test_config():
|
| 37 |
+
"""Test configuration import"""
|
| 38 |
+
print("\nTesting configuration...")
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
from config_hf import get_config, get_example_sequences, get_custom_css
|
| 42 |
+
config = get_config()
|
| 43 |
+
examples = get_example_sequences()
|
| 44 |
+
css = get_custom_css()
|
| 45 |
+
|
| 46 |
+
print("✅ Configuration loaded successfully")
|
| 47 |
+
print(f" - Max sequences: {config['MAX_SEQUENCES']}")
|
| 48 |
+
print(f" - Example sequences: {len(examples)} characters")
|
| 49 |
+
print(f" - CSS length: {len(css)} characters")
|
| 50 |
+
return True
|
| 51 |
+
|
| 52 |
+
except Exception as e:
|
| 53 |
+
print(f"❌ Configuration failed: {e}")
|
| 54 |
+
return False
|
| 55 |
+
|
| 56 |
+
def test_model_predictor():
|
| 57 |
+
"""Test model predictor"""
|
| 58 |
+
print("\nTesting model predictor...")
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
from model_predictor import EpitopePredictor
|
| 62 |
+
predictor = EpitopePredictor()
|
| 63 |
+
|
| 64 |
+
if predictor.model is None:
|
| 65 |
+
print("⚠️ Model not loaded - will use demo mode")
|
| 66 |
+
else:
|
| 67 |
+
print("✅ Model loaded successfully")
|
| 68 |
+
|
| 69 |
+
# Test prediction
|
| 70 |
+
test_seq = "MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL"
|
| 71 |
+
b_epitopes, t_epitopes = predictor.predict_epitopes(test_seq)
|
| 72 |
+
|
| 73 |
+
print(f"✅ Prediction test: {len(b_epitopes)} B-cell, {len(t_epitopes)} T-cell epitopes")
|
| 74 |
+
return True
|
| 75 |
+
|
| 76 |
+
except Exception as e:
|
| 77 |
+
print(f"❌ Model predictor failed: {e}")
|
| 78 |
+
return False
|
| 79 |
+
|
| 80 |
+
def test_gradio_app():
|
| 81 |
+
"""Test Gradio app creation"""
|
| 82 |
+
print("\nTesting Gradio app...")
|
| 83 |
+
|
| 84 |
+
try:
|
| 85 |
+
from app_gradio import create_interface
|
| 86 |
+
demo = create_interface()
|
| 87 |
+
|
| 88 |
+
print("✅ Gradio interface created successfully")
|
| 89 |
+
print(f" - Interface type: {type(demo)}")
|
| 90 |
+
|
| 91 |
+
# Test if we can get the config
|
| 92 |
+
if hasattr(demo, 'config'):
|
| 93 |
+
print("✅ Interface has config")
|
| 94 |
+
|
| 95 |
+
return True
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
print(f"❌ Gradio app creation failed: {e}")
|
| 99 |
+
return False
|
| 100 |
+
|
| 101 |
+
def test_sample_prediction():
|
| 102 |
+
"""Test a sample prediction through the interface"""
|
| 103 |
+
print("\nTesting sample prediction...")
|
| 104 |
+
|
| 105 |
+
try:
|
| 106 |
+
from app_gradio import predict_epitopes
|
| 107 |
+
|
| 108 |
+
# Test with sample sequence
|
| 109 |
+
sample_seq = """>Test_Protein
|
| 110 |
+
MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL"""
|
| 111 |
+
|
| 112 |
+
# Mock progress function
|
| 113 |
+
class MockProgress:
|
| 114 |
+
def __call__(self, value, desc=""):
|
| 115 |
+
print(f" Progress: {value:.1%} - {desc}")
|
| 116 |
+
|
| 117 |
+
result = predict_epitopes(sample_seq, None, 0.5, MockProgress())
|
| 118 |
+
|
| 119 |
+
if len(result) == 5: # Expected return format
|
| 120 |
+
summary, b_df, t_df, csv_file, json_file = result
|
| 121 |
+
print("✅ Prediction function works")
|
| 122 |
+
print(f" - Summary length: {len(summary)} characters")
|
| 123 |
+
print(f" - B-cell epitopes: {len(b_df)} rows")
|
| 124 |
+
print(f" - T-cell epitopes: {len(t_df)} rows")
|
| 125 |
+
return True
|
| 126 |
+
else:
|
| 127 |
+
print(f"❌ Unexpected return format: {len(result)} items")
|
| 128 |
+
return False
|
| 129 |
+
|
| 130 |
+
except Exception as e:
|
| 131 |
+
print(f"❌ Sample prediction failed: {e}")
|
| 132 |
+
return False
|
| 133 |
+
|
| 134 |
+
def main():
|
| 135 |
+
"""Run all tests"""
|
| 136 |
+
print("🧪 EpiPred Gradio App Test Suite")
|
| 137 |
+
print("=" * 40)
|
| 138 |
+
|
| 139 |
+
tests = [
|
| 140 |
+
("Basic Imports", test_imports),
|
| 141 |
+
("Configuration", test_config),
|
| 142 |
+
("Model Predictor", test_model_predictor),
|
| 143 |
+
("Gradio App", test_gradio_app),
|
| 144 |
+
("Sample Prediction", test_sample_prediction)
|
| 145 |
+
]
|
| 146 |
+
|
| 147 |
+
passed = 0
|
| 148 |
+
total = len(tests)
|
| 149 |
+
|
| 150 |
+
for test_name, test_func in tests:
|
| 151 |
+
try:
|
| 152 |
+
if test_func():
|
| 153 |
+
passed += 1
|
| 154 |
+
print(f"✅ {test_name} PASSED")
|
| 155 |
+
else:
|
| 156 |
+
print(f"❌ {test_name} FAILED")
|
| 157 |
+
except Exception as e:
|
| 158 |
+
print(f"❌ {test_name} ERROR: {e}")
|
| 159 |
+
print()
|
| 160 |
+
|
| 161 |
+
print("=" * 40)
|
| 162 |
+
print(f"Results: {passed}/{total} tests passed")
|
| 163 |
+
|
| 164 |
+
if passed >= 4: # Allow model predictor to fail
|
| 165 |
+
print("🎉 Gradio app is ready for deployment!")
|
| 166 |
+
if passed < total:
|
| 167 |
+
print("⚠️ Some features may be limited (demo mode)")
|
| 168 |
+
else:
|
| 169 |
+
print("❌ App has issues that need to be fixed")
|
| 170 |
+
|
| 171 |
+
return passed >= 4
|
| 172 |
+
|
| 173 |
+
if __name__ == '__main__':
|
| 174 |
+
success = main()
|
| 175 |
+
sys.exit(0 if success else 1)
|
test_model.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script to verify the epitope prediction model is working correctly
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
import logging
|
| 9 |
+
|
| 10 |
+
# Add the current directory to path
|
| 11 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 12 |
+
|
| 13 |
+
from model_predictor import EpitopePredictor
|
| 14 |
+
|
| 15 |
+
# Configure logging
|
| 16 |
+
logging.basicConfig(level=logging.INFO)
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
def test_model():
|
| 20 |
+
"""Test the epitope prediction model"""
|
| 21 |
+
logger.info("Starting model test...")
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
# Initialize predictor
|
| 25 |
+
logger.info("Initializing EpitopePredictor...")
|
| 26 |
+
predictor = EpitopePredictor()
|
| 27 |
+
logger.info("Model loaded successfully!")
|
| 28 |
+
|
| 29 |
+
# Get model info
|
| 30 |
+
model_info = predictor.get_model_info()
|
| 31 |
+
logger.info(f"Model info: {model_info}")
|
| 32 |
+
|
| 33 |
+
# Test sequences
|
| 34 |
+
test_sequences = {
|
| 35 |
+
"Test_Sequence_1": "MKLLILTCLVAVALARPKHPIKHQGLPQEVLNENLLRFFVAPFPEVFGKEKVNEL",
|
| 36 |
+
"Test_Sequence_2": "GIVEQCCTSICSLYQLENYCN", # Insulin A chain
|
| 37 |
+
"Test_Sequence_3": "FVNQHLCGSHLVEALYLVCGERGFFYTPKT" # Insulin B chain
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
for seq_name, sequence in test_sequences.items():
|
| 41 |
+
logger.info(f"\nTesting sequence: {seq_name} (length: {len(sequence)})")
|
| 42 |
+
logger.info(f"Sequence: {sequence}")
|
| 43 |
+
|
| 44 |
+
try:
|
| 45 |
+
# Predict epitopes
|
| 46 |
+
b_cell_epitopes, t_cell_epitopes = predictor.predict_epitopes(sequence)
|
| 47 |
+
|
| 48 |
+
logger.info(f"Results for {seq_name}:")
|
| 49 |
+
logger.info(f" B-cell epitopes: {len(b_cell_epitopes)}")
|
| 50 |
+
for i, (epitope, confidence, pos_range) in enumerate(b_cell_epitopes[:3]):
|
| 51 |
+
logger.info(f" {i+1}. {epitope} (confidence: {confidence:.3f}, position: {pos_range})")
|
| 52 |
+
|
| 53 |
+
logger.info(f" T-cell epitopes: {len(t_cell_epitopes)}")
|
| 54 |
+
for i, (epitope, confidence, pos_range) in enumerate(t_cell_epitopes[:3]):
|
| 55 |
+
logger.info(f" {i+1}. {epitope} (confidence: {confidence:.3f}, position: {pos_range})")
|
| 56 |
+
|
| 57 |
+
except Exception as e:
|
| 58 |
+
logger.error(f"Error predicting epitopes for {seq_name}: {e}")
|
| 59 |
+
|
| 60 |
+
logger.info("\nModel test completed successfully!")
|
| 61 |
+
return True
|
| 62 |
+
|
| 63 |
+
except Exception as e:
|
| 64 |
+
logger.error(f"Model test failed: {e}")
|
| 65 |
+
return False
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
success = test_model()
|
| 69 |
+
sys.exit(0 if success else 1)
|