Spaces:
Sleeping
Sleeping
Commit ·
5862ee1
1
Parent(s): 83a664b
Add auto-admin script and route DB/Media to persistent storage
Browse files- 0a055266cd32ddd1222176d9cd02fd44.jpg +0 -0
- Dockerfile +3 -1
- check_classes.py +13 -0
- images.jpg +0 -0
- mango1_538x.jpg +0 -0
- plant_core/diseases/management/commands/setup_admin.py +20 -0
- plant_core/plant_core/settings.py +11 -2
- update_prompt.py +52 -0
0a055266cd32ddd1222176d9cd02fd44.jpg
ADDED
|
Dockerfile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
|
| 2 |
|
| 3 |
# Install system dependencies for OpenCV/YOLO
|
| 4 |
RUN apt-get update && apt-get install -y \
|
|
@@ -46,5 +46,7 @@ CMD cd plant_core && \
|
|
| 46 |
python manage.py collectstatic --no-input && \
|
| 47 |
echo "Running migrations..." && \
|
| 48 |
python manage.py migrate && \
|
|
|
|
|
|
|
| 49 |
echo "Starting Gunicorn..." && \
|
| 50 |
gunicorn plant_core.wsgi:application --bind 0.0.0.0:7860 --timeout 300 --log-level info
|
|
|
|
| 1 |
+
FROM python:3.12-slim
|
| 2 |
|
| 3 |
# Install system dependencies for OpenCV/YOLO
|
| 4 |
RUN apt-get update && apt-get install -y \
|
|
|
|
| 46 |
python manage.py collectstatic --no-input && \
|
| 47 |
echo "Running migrations..." && \
|
| 48 |
python manage.py migrate && \
|
| 49 |
+
echo "Setting up admin user..." && \
|
| 50 |
+
python manage.py setup_admin && \
|
| 51 |
echo "Starting Gunicorn..." && \
|
| 52 |
gunicorn plant_core.wsgi:application --bind 0.0.0.0:7860 --timeout 300 --log-level info
|
check_classes.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import django
|
| 3 |
+
|
| 4 |
+
sys.path.append('d:/Plant_App/plant_core')
|
| 5 |
+
import os
|
| 6 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'plant_core.settings')
|
| 7 |
+
django.setup()
|
| 8 |
+
|
| 9 |
+
from diseases.ml_logic import CLASS_NAMES
|
| 10 |
+
print("Mango classes:")
|
| 11 |
+
for c in CLASS_NAMES:
|
| 12 |
+
if "mango" in c.lower():
|
| 13 |
+
print(c)
|
images.jpg
ADDED
|
mango1_538x.jpg
ADDED
|
plant_core/diseases/management/commands/setup_admin.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.core.management.base import BaseCommand
|
| 2 |
+
from django.contrib.auth import get_user_model
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
class Command(BaseCommand):
|
| 6 |
+
help = 'Automatically creates a superuser from ADMIN_USERNAME and ADMIN_PASSWORD environment variables'
|
| 7 |
+
|
| 8 |
+
def handle(self, *args, **options):
|
| 9 |
+
User = get_user_model()
|
| 10 |
+
|
| 11 |
+
username = os.environ.get('ADMIN_USERNAME', 'admin')
|
| 12 |
+
password = os.environ.get('ADMIN_PASSWORD', 'admin')
|
| 13 |
+
email = os.environ.get('ADMIN_EMAIL', 'admin@example.com')
|
| 14 |
+
|
| 15 |
+
if not User.objects.filter(username=username).exists():
|
| 16 |
+
self.stdout.write(f"Creating superuser '{username}'...")
|
| 17 |
+
User.objects.create_superuser(username=username, email=email, password=password)
|
| 18 |
+
self.stdout.write(self.style.SUCCESS(f"Successfully created superuser '{username}'"))
|
| 19 |
+
else:
|
| 20 |
+
self.stdout.write(f"Superuser '{username}' already exists. Skipping creation.")
|
plant_core/plant_core/settings.py
CHANGED
|
@@ -32,9 +32,15 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|
| 32 |
|
| 33 |
import dj_database_url
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
DATABASES = {
|
| 36 |
'default': dj_database_url.config(
|
| 37 |
-
default=f"sqlite:///{
|
| 38 |
conn_max_age=600,
|
| 39 |
conn_health_checks=True,
|
| 40 |
)
|
|
@@ -174,7 +180,10 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
| 174 |
|
| 175 |
|
| 176 |
MEDIA_URL = '/media/'
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
DATA_UPLOAD_MAX_MEMORY_SIZE = 10_485_760 # 10MB — allow phone camera uploads
|
| 180 |
|
|
|
|
| 32 |
|
| 33 |
import dj_database_url
|
| 34 |
|
| 35 |
+
# Check if running on Hugging Face Spaces with persistent storage
|
| 36 |
+
if os.path.exists('/data'):
|
| 37 |
+
DB_PATH = '/data/db.sqlite3'
|
| 38 |
+
else:
|
| 39 |
+
DB_PATH = BASE_DIR / 'db.sqlite3'
|
| 40 |
+
|
| 41 |
DATABASES = {
|
| 42 |
'default': dj_database_url.config(
|
| 43 |
+
default=f"sqlite:///{DB_PATH}",
|
| 44 |
conn_max_age=600,
|
| 45 |
conn_health_checks=True,
|
| 46 |
)
|
|
|
|
| 180 |
|
| 181 |
|
| 182 |
MEDIA_URL = '/media/'
|
| 183 |
+
if os.path.exists('/data'):
|
| 184 |
+
MEDIA_ROOT = '/data/media'
|
| 185 |
+
else:
|
| 186 |
+
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
| 187 |
|
| 188 |
DATA_UPLOAD_MAX_MEMORY_SIZE = 10_485_760 # 10MB — allow phone camera uploads
|
| 189 |
|
update_prompt.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
|
| 3 |
+
with open('plant_core/diseases/ml_logic.py', 'r') as f:
|
| 4 |
+
content = f.read()
|
| 5 |
+
|
| 6 |
+
replacement = '''
|
| 7 |
+
if species_hint:
|
| 8 |
+
prompt = (
|
| 9 |
+
"You are a plant pathologist analyzing a single leaf image.\\n\\n"
|
| 10 |
+
f"CONTEXT: An upstream object-detection model tagged this leaf as species '{species_hint}'. "
|
| 11 |
+
"That model is NOT trained on all crop types and frequently mislabels out-of-distribution leaves. "
|
| 12 |
+
"Treat the hint as a weak prior only — never as evidence.\\n\\n"
|
| 13 |
+
"ANALYSIS STEPS (do this reasoning internally, do not output it):\\n"
|
| 14 |
+
"1. Confirm this is actually a leaf. If it's not a leaf (or not identifiable as one), set is_plant_leaf to false and every other field to null.\\n"
|
| 15 |
+
f"2. Identify the species independently from leaf shape, margin, venation pattern, and texture. Only agree with '{species_hint}' if your own visual evidence supports it. If you're not confident in the species, say so via a lower confidence score rather than defaulting to the hint.\\n"
|
| 16 |
+
"3. Inspect the leaf surface for visible symptoms: discoloration, lesions, spots, mold, wilting, curling, chlorosis, necrosis, pest damage, etc.\\n"
|
| 17 |
+
"4. Decide health status:\\n"
|
| 18 |
+
" - No visible symptoms -> disease_name MUST be \\"healthy\\".\\n"
|
| 19 |
+
" - Visible symptoms -> name the disease based on the specific visual pattern you observe (e.g. \\"early blight\\", \\"powdery mildew\\", \\"leaf rust\\"), not a generic guess. If symptoms are visible but you can't confidently name a specific disease, use \\"unidentified leaf disease\\" rather than inventing a specific name.\\n"
|
| 20 |
+
"5. Only fill organic_cure, chemical_cure, and prevention_tips if disease_name is a real (non-null, non-\\"healthy\\") diagnosis you are reasonably confident in. If disease_name is \\"healthy\\" or \\"unidentified leaf disease\\", set these three fields to null instead of fabricating treatment advice.\\n\\n"
|
| 21 |
+
"CONFIDENCE CALIBRATION:\\n"
|
| 22 |
+
"- confidence reflects your certainty in the (species + disease) call jointly.\\n"
|
| 23 |
+
"- Use <0.5 when the image is blurry, ambiguous, or symptoms are non-specific.\\n"
|
| 24 |
+
"- Do not inflate confidence just because a specific hint was provided.\\n\\n"
|
| 25 |
+
"Respond with ONLY valid JSON, no markdown, no explanation, no reasoning text.\\n"
|
| 26 |
+
"Schema:\\n"
|
| 27 |
+
'{"is_plant_leaf": bool, "plant_species": str or null, "disease_name": str or null, "confidence": float 0-1, "severity": "mild"|"moderate"|"severe"|null, "organic_cure": str or null, "chemical_cure": str or null, "prevention_tips": str or null}'
|
| 28 |
+
)
|
| 29 |
+
else:
|
| 30 |
+
prompt = (
|
| 31 |
+
"You are a plant pathologist analyzing a single leaf image.\\n\\n"
|
| 32 |
+
"ANALYSIS STEPS (do this reasoning internally, do not output it):\\n"
|
| 33 |
+
"1. Confirm this is actually a leaf. If it's not a leaf (or not identifiable as one), set is_plant_leaf to false and every other field to null.\\n"
|
| 34 |
+
"2. Identify the species independently from leaf shape, margin, venation pattern, and texture. If you're not confident in the species, say so via a lower confidence score.\\n"
|
| 35 |
+
"3. Inspect the leaf surface for visible symptoms: discoloration, lesions, spots, mold, wilting, curling, chlorosis, necrosis, pest damage, etc.\\n"
|
| 36 |
+
"4. Decide health status:\\n"
|
| 37 |
+
" - No visible symptoms -> disease_name MUST be \\"healthy\\".\\n"
|
| 38 |
+
" - Visible symptoms -> name the disease based on the specific visual pattern you observe (e.g. \\"early blight\\", \\"powdery mildew\\", \\"leaf rust\\"), not a generic guess. If symptoms are visible but you can't confidently name a specific disease, use \\"unidentified leaf disease\\" rather than inventing a specific name.\\n"
|
| 39 |
+
"5. Only fill organic_cure, chemical_cure, and prevention_tips if disease_name is a real (non-null, non-\\"healthy\\") diagnosis you are reasonably confident in. If disease_name is \\"healthy\\" or \\"unidentified leaf disease\\", set these three fields to null instead of fabricating treatment advice.\\n\\n"
|
| 40 |
+
"CONFIDENCE CALIBRATION:\\n"
|
| 41 |
+
"- confidence reflects your certainty in the (species + disease) call jointly.\\n"
|
| 42 |
+
"- Use <0.5 when the image is blurry, ambiguous, or symptoms are non-specific.\\n\\n"
|
| 43 |
+
"Respond with ONLY valid JSON, no markdown, no explanation, no reasoning text.\\n"
|
| 44 |
+
"Schema:\\n"
|
| 45 |
+
'{"is_plant_leaf": bool, "plant_species": str or null, "disease_name": str or null, "confidence": float 0-1, "severity": "mild"|"moderate"|"severe"|null, "organic_cure": str or null, "chemical_cure": str or null, "prevention_tips": str or null}'
|
| 46 |
+
)'''
|
| 47 |
+
|
| 48 |
+
pattern = r'if species_hint:\s+prompt = \(.*?\)\s+else:\s+prompt = \(.*?\)'
|
| 49 |
+
new_content = re.sub(pattern, replacement.strip(), content, flags=re.DOTALL)
|
| 50 |
+
|
| 51 |
+
with open('plant_core/diseases/ml_logic.py', 'w') as f:
|
| 52 |
+
f.write(new_content)
|