joytheslothh commited on
Commit
0cc2bd2
·
1 Parent(s): 3480927

Fix deployment: add startup script, env config, routing fixes, and reorganize classifier service

Browse files
DEPLOYMENT.md ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🦠 BacSense v2 - Hugging Face Deployment Guide
2
+
3
+ ## Current Status
4
+ - **Frontend**: Deployed on Vercel ✅
5
+ - **Backend**: Deployed on Hugging Face Spaces (Docker) ⚠️
6
+
7
+ ## Issue Diagnosis
8
+
9
+ The "Not Found" error on Hugging Face typically means:
10
+ 1. The server isn't starting properly
11
+ 2. The port configuration is incorrect
12
+ 3. Model files aren't being found during initialization
13
+
14
+ ## Solution Implemented
15
+
16
+ ### 1. Updated Dockerfile
17
+ - Added better debugging with startup script
18
+ - Configured proper environment variables for Hugging Face
19
+ - Added curl for health checks
20
+
21
+ ### 2. Added Startup Script (`start.sh`)
22
+ - Shows directory contents on startup
23
+ - Verifies model files are present
24
+ - Starts uvicorn server with proper configuration
25
+
26
+ ### 3. API Configuration
27
+ - Root endpoint (`/`) now shows a welcome page
28
+ - Health check available at `/health`
29
+ - Debug endpoint at `/debug_model` to diagnose issues
30
+ - API documentation at `/docs`
31
+
32
+ ## How to Deploy
33
+
34
+ ### Step 1: Push to Hugging Face
35
+ ```bash
36
+ cd "e:\1-Antigravity Python\Bacsense 2.0\BacSense-API"
37
+ git add .
38
+ git commit -m "Fix deployment: add startup script and debugging"
39
+ git push origin main
40
+ ```
41
+
42
+ ### Step 2: Check Deployment Logs
43
+ 1. Go to your Hugging Face Space: https://huggingface.co/spaces/joytheslothh/BacSense-API
44
+ 2. Click on "Logs" tab
45
+ 3. Watch the startup logs to see if:
46
+ - All dependencies install successfully
47
+ - Model files are found
48
+ - Server starts on port 7860
49
+
50
+ ### Step 3: Test Endpoints
51
+ Once deployed, test these endpoints:
52
+
53
+ **Health Check:**
54
+ ```
55
+ https://joytheslothh-bacsense-api.hf.space/health
56
+ ```
57
+
58
+ **API Documentation:**
59
+ ```
60
+ https://joytheslothh-bacsense-api.hf.space/docs
61
+ ```
62
+
63
+ **Debug Model:**
64
+ ```
65
+ https://joytheslothh-bacsense-api.hf.space/debug_model
66
+ ```
67
+
68
+ ## Frontend Configuration (Vercel)
69
+
70
+ Update your frontend API URL in Vercel to point to:
71
+ ```
72
+ https://joytheslothh-bacsense-api.hf.space
73
+ ```
74
+
75
+ Or in your frontend code, update the API base URL:
76
+ ```typescript
77
+ // In your React code
78
+ const API_URL = "https://joytheslothh-bacsense-api.hf.space";
79
+ ```
80
+
81
+ ## Troubleshooting
82
+
83
+ ### If you still see "Not Found":
84
+
85
+ 1. **Check the logs** - Look for import errors or missing files
86
+ 2. **Verify model loading** - Visit `/debug_model` endpoint
87
+ 3. **Test locally** - Run `docker build -t bacsense .` and `docker run -p 7860:7860 bacsense`
88
+
89
+ ### Common Issues:
90
+
91
+ **TensorFlow Import Error:**
92
+ - Ensure all requirements are installed
93
+ - Check if TensorFlow CPU version is compatible
94
+
95
+ **Model Files Not Found:**
96
+ - Verify `bacsense_v2_package` folder exists in the repo
97
+ - Check that `.keras` and `.pkl` files are tracked by Git LFS
98
+
99
+ **Port Binding Issue:**
100
+ - Hugging Face expects port 7860
101
+ - Server must bind to `0.0.0.0` not `localhost`
102
+
103
+ ## Alternative: Streamlit-Only Deployment
104
+
105
+ If FastAPI continues to have issues, you can deploy as a pure Streamlit app:
106
+
107
+ 1. Rename `app.py` to `streamlit_app.py`
108
+ 2. Update Dockerfile CMD to: `CMD ["streamlit", "run", "streamlit_app.py"]`
109
+
110
+ This gives you the dashboard without the separate API backend.
Dockerfile CHANGED
@@ -7,6 +7,7 @@ RUN apt-get update && apt-get install -y \
7
  libsm6 \
8
  libxrender1 \
9
  libxext6 \
 
10
  && rm -rf /var/lib/apt/lists/*
11
 
12
  # Set up user
@@ -23,8 +24,15 @@ RUN pip install --no-cache-dir -r requirements.txt
23
  # Copy source code
24
  COPY --chown=user . .
25
 
26
- # Hugging Face usually expects 7860
27
  EXPOSE 7860
28
 
29
- # Run FastAPI
30
- CMD ["uvicorn", "classifier_service.api:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
7
  libsm6 \
8
  libxrender1 \
9
  libxext6 \
10
+ curl \
11
  && rm -rf /var/lib/apt/lists/*
12
 
13
  # Set up user
 
24
  # Copy source code
25
  COPY --chown=user . .
26
 
27
+ # Hugging Face Spaces typically expects port 7860
28
  EXPOSE 7860
29
 
30
+ # Environment variable for Hugging Face
31
+ ENV GRADIO_SERVER_NAME="0.0.0.0"
32
+ ENV GRADIO_SERVER_PORT="7860"
33
+
34
+ # Make startup script executable
35
+ RUN chmod +x start.sh
36
+
37
+ # Run the startup script for better debugging
38
+ CMD ["./start.sh"]
README.md CHANGED
@@ -1,90 +1,11 @@
1
- ---
2
- title: BacSense API
3
  emoji: 🦠
4
  colorFrom: blue
5
- colorTo: green
6
- sdk: streamlit
7
- sdk_version: "1.32.0"
8
- app_file: app.py
9
  pinned: false
10
- ---
11
-
12
- # 🦠 Bacsense 2.0
13
-
14
- ![Bacsense Banner](https://img.shields.io/badge/Bacsense-2.0-13a4ec?style=for-the-badge)
15
-
16
- **Bacsense 2.0** is an open-access visual platform for clinical microbiology research. Our mission is to accelerate pathogen identification through advanced hybrid neural networks and machine learning.
17
-
18
- This project integrates a robust **VGG16 + SVM** hybrid classification architecture with a modern, high-performance web interface to quickly and accurately identify microscopic bacterial species from uploaded culture images.
19
-
20
- ## ✨ Key Features
21
-
22
- - **🔬 High-Accuracy Classification:** Leverages a pre-trained VGG16 backbone for deep feature extraction, paired with a Support Vector Machine (SVM) classifier for pinpoint taxa identification.
23
- - **⚡ Real-time API:** Fast and lightweight inference backend powered by FastAPI.
24
- - **🌌 Premium Scientific UI:** A stunning, fully responsive dark-theme design featuring highly interactive GSAP spring cursors, meteor shower effects, and beautifully animated petri-dish data components.
25
- - **📊 Detailed Analysis Metrics:** Get immediate clinical insights on morphological traits, probability distribution thresholds, and gram stains for tested pathogens natively in the browser.
26
-
27
- ## 🛠️ Tech Stack
28
-
29
- ### Frontend
30
- - **Framework:** React + Vite (TypeScript)
31
- - **Styling:** Tailwind CSS
32
- - **Animations:** GSAP (GreenSock) & Framer Motion
33
- - **UI Architecture:** MagicUI
34
-
35
- ### Backend / ML Engine
36
- - **REST API Runtime:** FastAPI & Uvicorn
37
- - **Machine Learning Pipelines:** TensorFlow / Keras (VGG16), Scikit-Learn (SVM, PCA)
38
- - **Image Processing Computation:** Pillow (PIL), NumPy, SciPy
39
-
40
- ---
41
-
42
- ## 🚀 Getting Started
43
-
44
- ### Prerequisites
45
- - [Node.js](https://nodejs.org/) (v16+)
46
- - [Python](https://python.org/) (3.9+)
47
-
48
- ### 1. Boot the ML Backend
49
-
50
- Open a terminal in the project root and navigate to the backend service to spin up the prediction API:
51
-
52
- ```bash
53
- cd bacterial-classifier
54
- python -m venv venv
55
-
56
- # Windows Activation
57
- venv\Scripts\activate
58
- # Mac/Linux Activation
59
- # source venv/bin/activate
60
-
61
- pip install -r requirements.txt
62
- pip install fastapi uvicorn python-multipart
63
-
64
- # Start the FastAPI uvicorn server
65
- uvicorn api:app --host 0.0.0.0 --port 5000 --reload
66
- ```
67
- The ML API will successfully bind to `http://localhost:5000`.
68
-
69
- ### 2. Start the React Frontend
70
-
71
- Open a new terminal tab, navigate to the frontend folder, install dependencies, and launch the Vite dev server:
72
-
73
- ```bash
74
- cd frontend
75
- npm install
76
- npm run dev
77
- ```
78
-
79
- The user interface will be live at `http://localhost:5173`. 🥳 Drag and drop a microscopic image into the Upload Zone to test the prediction model!
80
-
81
- ## 🔬 Supported Species
82
- The engine spans multiple common pathogenic datasets and correctly identifies critical bacteria including:
83
- - *Escherichia coli* (Gram-negative)
84
- - *Staphylococcus aureus* (Gram-positive)
85
- - *Clostridium perfringens* (Anaerobic)
86
- - *Bacillus cereus* (Spore-forming)
87
- - *Listeria monocytogenes*
88
-
89
- ---
90
- *© 2026 Bacsense Scientific Systems. Built for Next-Gen Bioinformatics.*
 
1
+ title: BacSense v2 API
 
2
  emoji: 🦠
3
  colorFrom: blue
4
+ colorTo: purple
5
+ sdk: docker
 
 
6
  pinned: false
7
+ license: mit
8
+ tags:
9
+ - water-quality
10
+ - bacteria-classification
11
+ - machine-learning
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bacterial-classifier/api.py DELETED
@@ -1,80 +0,0 @@
1
- import io
2
- import os
3
- import sys
4
- import tempfile
5
- from typing import List
6
- from fastapi import FastAPI, UploadFile, File, HTTPException
7
- from fastapi.middleware.cors import CORSMiddleware
8
-
9
- # Add the parent directory to sys.path to import bacsense_v2_package
10
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
11
- from bacsense_v2_package.inference import BacSense
12
-
13
- app = FastAPI(title="Bacsense 2.0 API")
14
-
15
- # Setup CORS to allow requests from the React frontend
16
- app.add_middleware(
17
- CORSMiddleware,
18
- allow_origins=["*"], # Adjust this in production, e.g., ["http://localhost:5173"]
19
- allow_credentials=True,
20
- allow_methods=["*"],
21
- allow_headers=["*"],
22
- )
23
-
24
- # Load the model upon startup
25
- # The model files are located in the bacsense_v2_package directory
26
- model_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'bacsense_v2_package'))
27
- classifier = BacSense(model_dir=model_dir)
28
- classifier.warmup()
29
-
30
- @app.post("/predict_batch")
31
- async def predict_batch(files: List[UploadFile] = File(...)):
32
- if not files or len(files) == 0:
33
- raise HTTPException(status_code=400, detail="No files uploaded")
34
-
35
- results = []
36
-
37
- for file in files:
38
- temp_path = None
39
- try:
40
- # Read the uploaded file into an IO stream
41
- contents = await file.read()
42
-
43
- # BacSense uses cv2.imread and PIL.Image.open with a file path, so we save it to disk temporarily
44
- fd, temp_path = tempfile.mkstemp(suffix=".png")
45
- with os.fdopen(fd, 'wb') as f:
46
- f.write(contents)
47
-
48
- # Process the image
49
- result = classifier.predict(temp_path)
50
-
51
- # Format probabilities for the frontend
52
- # UI expects 0-100 for confidence
53
- confidence_pct = result["confidence"] * 100 if result["confidence"] <= 1.0 else result["confidence"]
54
-
55
- results.append({
56
- "filename": file.filename,
57
- "success": True,
58
- "prediction": result['prediction'],
59
- "confidence": confidence_pct,
60
- "probabilities": [
61
- {"name": result['prediction'], "probability": confidence_pct}
62
- ],
63
- "details": {
64
- "gram_stain": result.get("gram", "Unknown"),
65
- "shape": result.get("shape", "Unknown"),
66
- "pathogenicity": result.get("risk", "Unknown")
67
- }
68
- })
69
- except Exception as e:
70
- results.append({
71
- "filename": file.filename,
72
- "success": False,
73
- "error": str(e)
74
- })
75
- finally:
76
- # Clean up the temporary file
77
- if temp_path and os.path.exists(temp_path):
78
- os.remove(temp_path)
79
-
80
- return {"results": results}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
{bacterial-classifier → classifier_service}/.gitignore RENAMED
File without changes
{bacterial-classifier → classifier_service}/README.txt RENAMED
File without changes
classifier_service/api.py CHANGED
@@ -49,12 +49,14 @@ async def root():
49
  </html>
50
  """
51
 
52
- # Catch-all route to resolve any routing issues on Hugging Face
53
  @app.get("/{path_name:path}", response_class=HTMLResponse)
54
  async def catch_all(path_name: str):
55
- # If the user is trying to reach docs or health, let those routes handle it
56
- if path_name in ["docs", "redoc", "openapi.json", "health", "debug_model", "predict_batch"]:
57
- raise HTTPException(status_code=404)
 
 
58
  return await root()
59
 
60
  # Global classifier instance
 
49
  </html>
50
  """
51
 
52
+ # Catch-all route for SPA support (but exclude API endpoints)
53
  @app.get("/{path_name:path}", response_class=HTMLResponse)
54
  async def catch_all(path_name: str):
55
+ # Don't catch API endpoints - they should return 404 if not found
56
+ api_endpoints = ["docs", "redoc", "openapi.json", "health", "debug_model", "predict_batch", "api"]
57
+ if any(path_name.startswith(endpoint) for endpoint in api_endpoints):
58
+ raise HTTPException(status_code=404)
59
+ # For everything else, return the root page (useful for SPA routing)
60
  return await root()
61
 
62
  # Global classifier instance
{bacterial-classifier → classifier_service}/app.py RENAMED
File without changes
{bacterial-classifier → classifier_service}/requirements.txt RENAMED
File without changes
{bacterial-classifier → classifier_service}/run_backend.ps1 RENAMED
File without changes
push2.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Uploading LFS objects: 100% (7/7), 61 MB | 0 B/s, done.
2
+ remote: -------------------------------------------------------------------------
3
+ remote: Your push was rejected because it contains files larger than 10 MiB.
4
+ remote: Please use https://git-lfs.github.com/ to store large files.
5
+ remote: See also: https://hf.co/docs/hub/repositories-getting-started#terminal
6
+ remote:
7
+ remote: Offending files:
8
+ remote: - bacsense_v2_package/vgg16_feature_extractor.keras (ref: refs/heads/main)
9
+ remote: -------------------------------------------------------------------------
10
+ To https://huggingface.co/spaces/joytheslothh/BacSense-API
11
+ ! [remote rejected] main -> main (pre-receive hook declined)
12
+ error: failed to push some refs to 'https://huggingface.co/spaces/joytheslothh/BacSense-API'
push_final.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Pushing to https://huggingface.co/spaces/joytheslothh/BacSense-API
2
+ Uploading LFS objects: 100% (7/7), 61 MB | 0 B/s, done.
3
+ POST git-receive-pack (60176516 bytes)
4
+ remote: -------------------------------------------------------------------------
5
+ remote: Your push was rejected because it contains files larger than 10 MiB.
6
+ remote: Please use https://git-lfs.github.com/ to store large files.
7
+ remote: See also: https://hf.co/docs/hub/repositories-getting-started#terminal
8
+ remote:
9
+ remote: Offending files:
10
+ remote: - bacsense_v2_package/vgg16_feature_extractor.keras (ref: refs/heads/main)
11
+ remote: -------------------------------------------------------------------------
12
+ To https://huggingface.co/spaces/joytheslothh/BacSense-API
13
+ ! [remote rejected] main -> main (pre-receive hook declined)
14
+ error: failed to push some refs to 'https://huggingface.co/spaces/joytheslothh/BacSense-API'
push_log.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Uploading LFS objects: 100% (7/7), 61 MB | 0 B/s, done.
2
+ remote: -------------------------------------------------------------------------
3
+ remote: Your push was rejected because it contains files larger than 10 MiB.
4
+ remote: Please use https://git-lfs.github.com/ to store large files.
5
+ remote: See also: https://hf.co/docs/hub/repositories-getting-started#terminal
6
+ remote:
7
+ remote: Offending files:
8
+ remote: - bacsense_v2_package/vgg16_feature_extractor.keras (ref: refs/heads/main)
9
+ remote: -------------------------------------------------------------------------
10
+ To https://huggingface.co/spaces/joytheslothh/BacSense-API
11
+ ! [remote rejected] main -> main (pre-receive hook declined)
12
+ error: failed to push some refs to 'https://huggingface.co/spaces/joytheslothh/BacSense-API'
requirements.txt CHANGED
@@ -1,11 +1,11 @@
1
- tensorflow>=2.12.0
2
  scikit-learn>=1.3.0
3
  scikit-image>=0.21.0
4
  opencv-python-headless>=4.8.0
5
- numpy>=1.24.0
6
- Pillow>=9.5.0
7
  scipy>=1.11.0
8
- streamlit>=1.32.0
9
  pandas>=2.0.0
10
  fastapi
11
  uvicorn
 
1
+ tensorflow-cpu>=2.15.0,<2.16.0
2
  scikit-learn>=1.3.0
3
  scikit-image>=0.21.0
4
  opencv-python-headless>=4.8.0
5
+ numpy>=1.24.0,<2.0.0
6
+ Pillow>=10.0.0
7
  scipy>=1.11.0
8
+ streamlit>=1.41.0
9
  pandas>=2.0.0
10
  fastapi
11
  uvicorn
start.sh ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Startup script for Hugging Face Spaces
3
+
4
+ echo "🦠 Starting BacSense v2 API..."
5
+ echo "Current directory: $(pwd)"
6
+ echo "Contents:"
7
+ ls -la
8
+
9
+ echo ""
10
+ echo "Checking bacsense_v2_package:"
11
+ ls -la bacsense_v2_package/ || echo "Directory not found!"
12
+
13
+ echo ""
14
+ echo "Starting uvicorn server..."
15
+ exec uvicorn classifier_service.api:app --host 0.0.0.0 --port 7860
upload_to_hf.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from huggingface_hub import HfApi
2
+ import os
3
+
4
+ TOKEN = "REDACTED"
5
+ REPO_ID = "joytheslothh/BacSense-API"
6
+ REPO_TYPE = "space"
7
+
8
+ # Files/dirs to skip (frontend, docs, git artifacts)
9
+ SKIP_DIRS = {"frontend", ".git", "__pycache__", ".gitignore"}
10
+ SKIP_FILES = {"push_log.txt", "push2.txt", "push_final.txt", "upload_to_hf.py",
11
+ "render.yaml", "BacSense_v2_Technical_Documentation.docx",
12
+ "BacSense_v2_Technical_Documentation.docx-1.pdf",
13
+ "BacSense_v2_Technical_Documentation.docx.txt"}
14
+ SKIP_EXTS = {".ps1"}
15
+
16
+ api = HfApi(token=TOKEN)
17
+ base = os.path.abspath(os.path.dirname(__file__))
18
+
19
+ files_to_upload = []
20
+ for root, dirs, files in os.walk(base):
21
+ # Prune skip dirs in-place
22
+ dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith('.')]
23
+ for fname in files:
24
+ if fname in SKIP_FILES:
25
+ continue
26
+ if any(fname.endswith(ext) for ext in SKIP_EXTS):
27
+ continue
28
+ full_path = os.path.join(root, fname)
29
+ rel_path = os.path.relpath(full_path, base).replace("\\", "/")
30
+ files_to_upload.append((full_path, rel_path))
31
+
32
+ print(f"Uploading {len(files_to_upload)} files to {REPO_ID}...\n")
33
+ for i, (local, remote) in enumerate(files_to_upload, 1):
34
+ size_mb = os.path.getsize(local) / (1024*1024)
35
+ print(f"[{i}/{len(files_to_upload)}] {remote} ({size_mb:.2f} MB)")
36
+ api.upload_file(
37
+ path_or_fileobj=local,
38
+ path_in_repo=remote,
39
+ repo_id=REPO_ID,
40
+ repo_type=REPO_TYPE,
41
+ )
42
+
43
+ print("\n✅ All files uploaded successfully!")