Spaces:
Sleeping
Sleeping
Commit
·
1624b73
0
Parent(s):
Initial Docker configuration for Hugging Face
Browse files- .gitattributes +1 -0
- DEPLOYMENT_HF.md +60 -0
- Dockerfile +37 -0
- README.md +35 -0
- README_HF.md +35 -0
- api/__init__.py +0 -0
- api/__pycache__/__init__.cpython-312.pyc +0 -0
- api/__pycache__/admin.cpython-312.pyc +0 -0
- api/__pycache__/models.cpython-312.pyc +0 -0
- api/__pycache__/serializers.cpython-312.pyc +0 -0
- api/__pycache__/stats_views.cpython-312.pyc +0 -0
- api/__pycache__/urls.cpython-312.pyc +0 -0
- api/__pycache__/utils.cpython-312.pyc +0 -0
- api/__pycache__/views.cpython-312.pyc +0 -0
- api/admin.py +66 -0
- api/migrations/0001_initial.py +27 -0
- api/migrations/__init__.py +0 -0
- api/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
- api/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
- api/models.py +12 -0
- api/serializers.py +7 -0
- api/stats_views.py +22 -0
- api/urls.py +9 -0
- api/utils.py +60 -0
- api/views.py +49 -0
- best.pt +3 -0
- manage.py +19 -0
- requirements.txt +57 -0
- satcap_project/__pycache__/settings.cpython-312.pyc +0 -0
- satcap_project/__pycache__/urls.cpython-312.pyc +0 -0
- satcap_project/__pycache__/wsgi.cpython-312.pyc +0 -0
- satcap_project/settings.py +92 -0
- satcap_project/urls.py +9 -0
- satcap_project/wsgi.py +4 -0
.gitattributes
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
best.pt filter=lfs diff=lfs merge=lfs -text
|
DEPLOYMENT_HF.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Documentation du Déploiement Backend sur Hugging Face (Docker)
|
| 2 |
+
|
| 3 |
+
Cette documentation explique les modifications apportées au dossier `backend` pour permettre son déploiement sur Hugging Face Spaces en utilisant Docker.
|
| 4 |
+
|
| 5 |
+
## 1. Fichiers Ajoutés
|
| 6 |
+
|
| 7 |
+
### `Dockerfile`
|
| 8 |
+
Le fichier `Dockerfile` est le cœur de la configuration Docker. Il effectue les actions suivantes :
|
| 9 |
+
- Utilise une image de base Python 3.10.
|
| 10 |
+
- Installe les dépendances système nécessaires à OpenCV (`libgl1-mesa-glx`, `libglib2.0-0`).
|
| 11 |
+
- Installe les dépendances Python listées dans `requirements.txt`.
|
| 12 |
+
- Configure le port par défaut sur `7860` (obligatoire pour Hugging Face Spaces).
|
| 13 |
+
- Exécute les migrations de base de données au démarrage.
|
| 14 |
+
|
| 15 |
+
### `README_HF.md`
|
| 16 |
+
Ce fichier contient les métadonnées YAML nécessaires à Hugging Face pour identifier le SDK (Docker) et le port de l'application.
|
| 17 |
+
**Note :** Pour que Hugging Face reconnaisse ces métadonnées, ce contenu doit être présent dans le fichier `README.md` à la racine du dépôt sur Hugging Face.
|
| 18 |
+
|
| 19 |
+
## 2. Modifications de Configuration
|
| 20 |
+
|
| 21 |
+
### Port de l'Application
|
| 22 |
+
Hugging Face Spaces expose l'application sur le port **7860**. Le `Dockerfile` est configuré pour lancer le serveur Django sur ce port spécifique :
|
| 23 |
+
`python manage.py runserver 0.0.0.0:7860`
|
| 24 |
+
|
| 25 |
+
### Gestion des Fichiers Statiques et Media
|
| 26 |
+
Dans un environnement Docker éphémère comme Hugging Face Spaces :
|
| 27 |
+
- Les fichiers media (images uploadées) seront stockés localement dans le conteneur mais seront perdus au redémarrage du Space.
|
| 28 |
+
- Pour une utilisation en production réelle, un stockage externe (comme AWS S3 ou Google Cloud Storage) serait recommandé.
|
| 29 |
+
|
| 30 |
+
## 3. Procédure d'Upload
|
| 31 |
+
|
| 32 |
+
Pour uploader le backend sur votre Space `SATCAP-OCEANS_DOCKER` :
|
| 33 |
+
|
| 34 |
+
1. **Initialiser Git (si ce n'est pas déjà fait)** :
|
| 35 |
+
```bash
|
| 36 |
+
cd backend
|
| 37 |
+
git init
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
2. **Ajouter le remote Hugging Face** :
|
| 41 |
+
```bash
|
| 42 |
+
git remote add hf https://huggingface.co/spaces/CosmoLABHub/SATCAP-OCEANS_DOCKER
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
3. **Préparer le README** :
|
| 46 |
+
Copiez le contenu de `README_HF.md` dans un fichier nommé `README.md` (Hugging Face a besoin du nom exact `README.md`).
|
| 47 |
+
|
| 48 |
+
4. **Pousser vers Hugging Face** :
|
| 49 |
+
```bash
|
| 50 |
+
git add .
|
| 51 |
+
git commit -m "Configuration Docker pour Hugging Face"
|
| 52 |
+
git push hf main --force
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
## 4. Utilisation comme API
|
| 56 |
+
|
| 57 |
+
Une fois le Space "Running", l'URL de l'API sera :
|
| 58 |
+
`https://cosmolabhub-satcap-oceans-docker.hf.space/api/detect/`
|
| 59 |
+
|
| 60 |
+
Vous pouvez utiliser cette URL dans votre frontend pour envoyer des requêtes de détection.
|
Dockerfile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use an official Python runtime as a parent image
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Set environment variables
|
| 5 |
+
ENV PYTHONDONTWRITEBYTECODE 1
|
| 6 |
+
ENV PYTHONUNBUFFERED 1
|
| 7 |
+
ENV PORT 7860
|
| 8 |
+
|
| 9 |
+
# Set work directory
|
| 10 |
+
WORKDIR /app
|
| 11 |
+
|
| 12 |
+
# Install system dependencies
|
| 13 |
+
RUN apt-get update && apt-get install -y \
|
| 14 |
+
libgl1-mesa-glx \
|
| 15 |
+
libglib2.0-0 \
|
| 16 |
+
git \
|
| 17 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 18 |
+
|
| 19 |
+
# Install dependencies
|
| 20 |
+
COPY requirements.txt /app/
|
| 21 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 22 |
+
|
| 23 |
+
# Copy project
|
| 24 |
+
COPY . /app/
|
| 25 |
+
|
| 26 |
+
# Create media and static directories
|
| 27 |
+
RUN mkdir -p /app/media /app/static
|
| 28 |
+
|
| 29 |
+
# Run migrations (using SQLite for simplicity in Space)
|
| 30 |
+
RUN python manage.py migrate
|
| 31 |
+
|
| 32 |
+
# Expose the port Gradio/Hugging Face expects
|
| 33 |
+
EXPOSE 7860
|
| 34 |
+
|
| 35 |
+
# Start the application using Gunicorn or directly with manage.py
|
| 36 |
+
# Hugging Face Spaces for Docker expects the app to run on port 7860
|
| 37 |
+
CMD ["python", "manage.py", "runserver", "0.0.0.0:7860"]
|
README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: SATCAP-OCEANS Backend
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# SATCAP-OCEANS Backend (API)
|
| 13 |
+
|
| 14 |
+
Ce dépôt contient le backend Django du projet SATCAP-OCEANS, configuré pour être déployé sur Hugging Face Spaces via Docker.
|
| 15 |
+
|
| 16 |
+
## Configuration pour Hugging Face
|
| 17 |
+
|
| 18 |
+
Le backend a été modifié pour fonctionner dans un environnement Docker sur Hugging Face :
|
| 19 |
+
- **Port** : L'application écoute sur le port `7860` (requis par Hugging Face).
|
| 20 |
+
- **Base de données** : Utilise SQLite par défaut pour la portabilité.
|
| 21 |
+
- **Dockerfile** : Inclus pour l'installation automatique des dépendances système (OpenCV, etc.) et Python.
|
| 22 |
+
|
| 23 |
+
## Utilisation de l'API
|
| 24 |
+
|
| 25 |
+
Une fois déployé, vous pouvez accéder à l'API via l'URL de votre Space.
|
| 26 |
+
|
| 27 |
+
### Endpoints principaux :
|
| 28 |
+
- `POST /api/detect/` : Envoyer une image pour détection.
|
| 29 |
+
- `GET /api/history/` : Récupérer l'historique des détections.
|
| 30 |
+
|
| 31 |
+
## Déploiement
|
| 32 |
+
|
| 33 |
+
Pour mettre à jour ce Space :
|
| 34 |
+
1. Ajoutez ce dépôt comme remote : `git remote add hf_docker https://huggingface.co/spaces/CosmoLABHub/SATCAP-OCEANS_DOCKER`
|
| 35 |
+
2. Poussez les changements : `git push hf_docker main`
|
README_HF.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: SATCAP-OCEANS Backend
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# SATCAP-OCEANS Backend (API)
|
| 13 |
+
|
| 14 |
+
Ce dépôt contient le backend Django du projet SATCAP-OCEANS, configuré pour être déployé sur Hugging Face Spaces via Docker.
|
| 15 |
+
|
| 16 |
+
## Configuration pour Hugging Face
|
| 17 |
+
|
| 18 |
+
Le backend a été modifié pour fonctionner dans un environnement Docker sur Hugging Face :
|
| 19 |
+
- **Port** : L'application écoute sur le port `7860` (requis par Hugging Face).
|
| 20 |
+
- **Base de données** : Utilise SQLite par défaut pour la portabilité.
|
| 21 |
+
- **Dockerfile** : Inclus pour l'installation automatique des dépendances système (OpenCV, etc.) et Python.
|
| 22 |
+
|
| 23 |
+
## Utilisation de l'API
|
| 24 |
+
|
| 25 |
+
Une fois déployé, vous pouvez accéder à l'API via l'URL de votre Space.
|
| 26 |
+
|
| 27 |
+
### Endpoints principaux :
|
| 28 |
+
- `POST /api/detect/` : Envoyer une image pour détection.
|
| 29 |
+
- `GET /api/history/` : Récupérer l'historique des détections.
|
| 30 |
+
|
| 31 |
+
## Déploiement
|
| 32 |
+
|
| 33 |
+
Pour mettre à jour ce Space :
|
| 34 |
+
1. Ajoutez ce dépôt comme remote : `git remote add hf_docker https://huggingface.co/spaces/CosmoLABHub/SATCAP-OCEANS_DOCKER`
|
| 35 |
+
2. Poussez les changements : `git push hf_docker main`
|
api/__init__.py
ADDED
|
File without changes
|
api/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (198 Bytes). View file
|
|
|
api/__pycache__/admin.cpython-312.pyc
ADDED
|
Binary file (4.04 kB). View file
|
|
|
api/__pycache__/models.cpython-312.pyc
ADDED
|
Binary file (1.36 kB). View file
|
|
|
api/__pycache__/serializers.cpython-312.pyc
ADDED
|
Binary file (741 Bytes). View file
|
|
|
api/__pycache__/stats_views.cpython-312.pyc
ADDED
|
Binary file (1.38 kB). View file
|
|
|
api/__pycache__/urls.cpython-312.pyc
ADDED
|
Binary file (677 Bytes). View file
|
|
|
api/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (2.15 kB). View file
|
|
|
api/__pycache__/views.cpython-312.pyc
ADDED
|
Binary file (2.71 kB). View file
|
|
|
api/admin.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from django.utils.html import format_html
|
| 3 |
+
from .models import Detection
|
| 4 |
+
from .utils import run_detection
|
| 5 |
+
import json
|
| 6 |
+
|
| 7 |
+
@admin.register(Detection)
|
| 8 |
+
class DetectionAdmin(admin.ModelAdmin):
|
| 9 |
+
list_display = ('id', 'thumbnail', 'user', 'object_count', 'top_labels', 'created_at')
|
| 10 |
+
list_filter = ('created_at', 'user')
|
| 11 |
+
search_fields = ('results', 'user__username')
|
| 12 |
+
readonly_fields = ('thumbnail_large', 'formatted_results', 'created_at')
|
| 13 |
+
actions = ['re_analyze_detections']
|
| 14 |
+
|
| 15 |
+
def thumbnail(self, obj):
|
| 16 |
+
if obj.image:
|
| 17 |
+
return format_html('<img src="{}" style="width: 50px; height: auto; border-radius: 4px;" />', obj.image.url)
|
| 18 |
+
return "No Image"
|
| 19 |
+
thumbnail.short_description = 'Preview'
|
| 20 |
+
|
| 21 |
+
def thumbnail_large(self, obj):
|
| 22 |
+
if obj.image:
|
| 23 |
+
return format_html('<img src="{}" style="max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);" />', obj.image.url)
|
| 24 |
+
return "No Image"
|
| 25 |
+
thumbnail_large.short_description = 'Image View'
|
| 26 |
+
|
| 27 |
+
def object_count(self, obj):
|
| 28 |
+
return len(obj.results) if obj.results else 0
|
| 29 |
+
object_count.short_description = 'Objects Found'
|
| 30 |
+
|
| 31 |
+
def top_labels(self, obj):
|
| 32 |
+
if not obj.results:
|
| 33 |
+
return "-"
|
| 34 |
+
labels = [item['label'] for item in obj.results]
|
| 35 |
+
unique_labels = list(set(labels))
|
| 36 |
+
return ", ".join(unique_labels[:3]) + ("..." if len(unique_labels) > 3 else "")
|
| 37 |
+
top_labels.short_description = 'Main Detections'
|
| 38 |
+
|
| 39 |
+
def formatted_results(self, obj):
|
| 40 |
+
return format_html('<pre style="background: #f4f4f4; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto;">{}</pre>',
|
| 41 |
+
json.dumps(obj.results, indent=2))
|
| 42 |
+
formatted_results.short_description = 'Analysis Data (JSON)'
|
| 43 |
+
|
| 44 |
+
@admin.action(description='Re-analyze selected detections with AI')
|
| 45 |
+
def re_analyze_detections(self, request, queryset):
|
| 46 |
+
count = 0
|
| 47 |
+
for obj in queryset:
|
| 48 |
+
if obj.image:
|
| 49 |
+
detections = run_detection(obj.image.path)
|
| 50 |
+
obj.results = detections
|
| 51 |
+
obj.save()
|
| 52 |
+
count += 1
|
| 53 |
+
self.message_user(request, f"Successfully re-analyzed {count} images.")
|
| 54 |
+
|
| 55 |
+
fieldsets = (
|
| 56 |
+
('General Information', {
|
| 57 |
+
'fields': ('user', 'image', 'created_at')
|
| 58 |
+
}),
|
| 59 |
+
('Visual Analysis', {
|
| 60 |
+
'fields': ('thumbnail_large',)
|
| 61 |
+
}),
|
| 62 |
+
('Detailed Results', {
|
| 63 |
+
'fields': ('formatted_results',),
|
| 64 |
+
'classes': ('collapse',)
|
| 65 |
+
}),
|
| 66 |
+
)
|
api/migrations/0001_initial.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 6.0 on 2026-01-03 17:13
|
| 2 |
+
|
| 3 |
+
import django.db.models.deletion
|
| 4 |
+
from django.conf import settings
|
| 5 |
+
from django.db import migrations, models
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class Migration(migrations.Migration):
|
| 9 |
+
|
| 10 |
+
initial = True
|
| 11 |
+
|
| 12 |
+
dependencies = [
|
| 13 |
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
operations = [
|
| 17 |
+
migrations.CreateModel(
|
| 18 |
+
name='Detection',
|
| 19 |
+
fields=[
|
| 20 |
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
| 21 |
+
('image', models.ImageField(upload_to='detections/')),
|
| 22 |
+
('results', models.JSONField()),
|
| 23 |
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
| 24 |
+
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
| 25 |
+
],
|
| 26 |
+
),
|
| 27 |
+
]
|
api/migrations/__init__.py
ADDED
|
File without changes
|
api/migrations/__pycache__/0001_initial.cpython-312.pyc
ADDED
|
Binary file (1.61 kB). View file
|
|
|
api/migrations/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (209 Bytes). View file
|
|
|
api/models.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import models
|
| 2 |
+
from django.contrib.auth.models import User
|
| 3 |
+
|
| 4 |
+
class Detection(models.Model):
|
| 5 |
+
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
|
| 6 |
+
image = models.ImageField(upload_to='detections/')
|
| 7 |
+
results = models.JSONField() # Stores the list of detected objects
|
| 8 |
+
created_at = models.DateTimeField(auto_now_add=True)
|
| 9 |
+
|
| 10 |
+
def __str__(self):
|
| 11 |
+
count = len(self.results) if self.results else 0
|
| 12 |
+
return f"Detection {self.id} ({count} objects) - {self.created_at.strftime('%Y-%m-%d %H:%M')}"
|
api/serializers.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from rest_framework import serializers
|
| 2 |
+
from .models import Detection
|
| 3 |
+
|
| 4 |
+
class DetectionSerializer(serializers.ModelSerializer):
|
| 5 |
+
class Meta:
|
| 6 |
+
model = Detection
|
| 7 |
+
fields = '__all__'
|
api/stats_views.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from rest_framework.views import APIView
|
| 2 |
+
from rest_framework.response import Response
|
| 3 |
+
from .models import Detection
|
| 4 |
+
from django.db.models import Count
|
| 5 |
+
from collections import Counter
|
| 6 |
+
|
| 7 |
+
class StatsView(APIView):
|
| 8 |
+
def get(self, request):
|
| 9 |
+
detections = Detection.objects.all()
|
| 10 |
+
total_detections = detections.count()
|
| 11 |
+
|
| 12 |
+
# Count labels across all detections
|
| 13 |
+
label_counts = Counter()
|
| 14 |
+
for det in detections:
|
| 15 |
+
for item in det.results:
|
| 16 |
+
label_counts[item['label']] += 1
|
| 17 |
+
|
| 18 |
+
return Response({
|
| 19 |
+
"total_images_analyzed": total_detections,
|
| 20 |
+
"total_objects_detected": sum(label_counts.values()),
|
| 21 |
+
"label_distribution": dict(label_counts)
|
| 22 |
+
})
|
api/urls.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.urls import path
|
| 2 |
+
from .views import DetectView, DetectionHistoryView
|
| 3 |
+
from .stats_views import StatsView
|
| 4 |
+
|
| 5 |
+
urlpatterns = [
|
| 6 |
+
path('detect/', DetectView.as_view(), name='detect'),
|
| 7 |
+
path('history/', DetectionHistoryView.as_view(), name='history'),
|
| 8 |
+
path('stats/', StatsView.as_view(), name='stats'),
|
| 9 |
+
]
|
api/utils.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import numpy as np
|
| 3 |
+
import os
|
| 4 |
+
from django.conf import settings
|
| 5 |
+
from ultralytics import YOLO
|
| 6 |
+
|
| 7 |
+
# Global model variable
|
| 8 |
+
_model = None
|
| 9 |
+
|
| 10 |
+
def get_model():
|
| 11 |
+
global _model
|
| 12 |
+
if _model is None:
|
| 13 |
+
MODEL_PATH = os.path.join(settings.BASE_DIR, 'best.pt')
|
| 14 |
+
try:
|
| 15 |
+
_model = YOLO(MODEL_PATH)
|
| 16 |
+
except Exception as e:
|
| 17 |
+
print(f"Error loading model: {e}")
|
| 18 |
+
return _model
|
| 19 |
+
|
| 20 |
+
def run_detection(image_path):
|
| 21 |
+
"""
|
| 22 |
+
Runs YOLO detection on an image file path and returns a list of detections.
|
| 23 |
+
"""
|
| 24 |
+
model = get_model()
|
| 25 |
+
if model is None:
|
| 26 |
+
return []
|
| 27 |
+
|
| 28 |
+
img = cv2.imread(image_path)
|
| 29 |
+
if img is None:
|
| 30 |
+
return []
|
| 31 |
+
|
| 32 |
+
results = model(img)
|
| 33 |
+
detections = []
|
| 34 |
+
|
| 35 |
+
for r in results:
|
| 36 |
+
boxes = r.boxes
|
| 37 |
+
for box in boxes:
|
| 38 |
+
# Get coordinates in percentage for the frontend
|
| 39 |
+
x_center, y_center, w, h = box.xywhn[0].tolist()
|
| 40 |
+
|
| 41 |
+
x = (x_center - w/2) * 100
|
| 42 |
+
y = (y_center - h/2) * 100
|
| 43 |
+
width = w * 100
|
| 44 |
+
height = h * 100
|
| 45 |
+
|
| 46 |
+
conf = float(box.conf[0])
|
| 47 |
+
cls = int(box.cls[0])
|
| 48 |
+
label = model.names[cls]
|
| 49 |
+
|
| 50 |
+
detections.append({
|
| 51 |
+
"id": len(detections),
|
| 52 |
+
"x": x,
|
| 53 |
+
"y": y,
|
| 54 |
+
"width": width,
|
| 55 |
+
"height": height,
|
| 56 |
+
"label": label,
|
| 57 |
+
"confidence": conf * 100
|
| 58 |
+
})
|
| 59 |
+
|
| 60 |
+
return detections
|
api/views.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from rest_framework.views import APIView
|
| 2 |
+
from rest_framework.response import Response
|
| 3 |
+
from rest_framework import status
|
| 4 |
+
from .models import Detection
|
| 5 |
+
from .serializers import DetectionSerializer
|
| 6 |
+
from django.core.files.base import ContentFile
|
| 7 |
+
import cv2
|
| 8 |
+
import numpy as np
|
| 9 |
+
from ultralytics import YOLO
|
| 10 |
+
import os
|
| 11 |
+
from django.conf import settings
|
| 12 |
+
|
| 13 |
+
from .utils import run_detection
|
| 14 |
+
|
| 15 |
+
class DetectView(APIView):
|
| 16 |
+
def post(self, request):
|
| 17 |
+
if 'image' not in request.FILES:
|
| 18 |
+
return Response({"error": "No image provided"}, status=status.HTTP_400_BAD_REQUEST)
|
| 19 |
+
|
| 20 |
+
image_file = request.FILES['image']
|
| 21 |
+
|
| 22 |
+
# Save temporarily to run detection or use the file directly
|
| 23 |
+
# For simplicity and consistency with the utility, we save the object first with empty results
|
| 24 |
+
# then run detection on the saved file path.
|
| 25 |
+
|
| 26 |
+
detection_obj = Detection.objects.create(
|
| 27 |
+
image=image_file,
|
| 28 |
+
results=[],
|
| 29 |
+
user=request.user if request.user.is_authenticated else None
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
# Run detection on the saved file
|
| 33 |
+
detections = run_detection(detection_obj.image.path)
|
| 34 |
+
|
| 35 |
+
# Update results
|
| 36 |
+
detection_obj.results = detections
|
| 37 |
+
detection_obj.save()
|
| 38 |
+
|
| 39 |
+
return Response(DetectionSerializer(detection_obj).data, status=status.HTTP_201_CREATED)
|
| 40 |
+
|
| 41 |
+
class DetectionHistoryView(APIView):
|
| 42 |
+
def get(self, request):
|
| 43 |
+
if request.user.is_authenticated:
|
| 44 |
+
detections = Detection.objects.filter(user=request.user).order_by('-created_at')
|
| 45 |
+
else:
|
| 46 |
+
detections = Detection.objects.all().order_by('-created_at')[:10] # Last 10 for guests
|
| 47 |
+
|
| 48 |
+
serializer = DetectionSerializer(detections, many=True)
|
| 49 |
+
return Response(serializer.data)
|
best.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8895d0ddf2db84fcd858cd4d6b7a15f0402b8b2125b29a1e3214cb3785644d73
|
| 3 |
+
size 52039826
|
manage.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
def main():
|
| 6 |
+
"""Run administrative tasks."""
|
| 7 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'satcap_project.settings')
|
| 8 |
+
try:
|
| 9 |
+
from django.core.management import execute_from_command_line
|
| 10 |
+
except ImportError as exc:
|
| 11 |
+
raise ImportError(
|
| 12 |
+
"Couldn't import Django. Are you sure it's installed and "
|
| 13 |
+
"available on your PYTHONPATH environment variable? Did you "
|
| 14 |
+
"forget to activate a virtual environment?"
|
| 15 |
+
) from exc
|
| 16 |
+
execute_from_command_line(sys.argv)
|
| 17 |
+
|
| 18 |
+
if __name__ == '__main__':
|
| 19 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
asgiref==3.11.0
|
| 2 |
+
certifi==2025.11.12
|
| 3 |
+
charset-normalizer==3.4.4
|
| 4 |
+
contourpy==1.3.3
|
| 5 |
+
cycler==0.12.1
|
| 6 |
+
Django==6.0
|
| 7 |
+
django-cors-headers==4.9.0
|
| 8 |
+
djangorestframework==3.16.1
|
| 9 |
+
filelock==3.20.2
|
| 10 |
+
fonttools==4.61.1
|
| 11 |
+
fsspec==2025.12.0
|
| 12 |
+
idna==3.11
|
| 13 |
+
Jinja2==3.1.6
|
| 14 |
+
kiwisolver==1.4.9
|
| 15 |
+
MarkupSafe==3.0.3
|
| 16 |
+
matplotlib==3.10.8
|
| 17 |
+
mpmath==1.3.0
|
| 18 |
+
networkx==3.6.1
|
| 19 |
+
numpy==2.2.6
|
| 20 |
+
nvidia-cublas-cu12==12.8.4.1
|
| 21 |
+
nvidia-cuda-cupti-cu12==12.8.90
|
| 22 |
+
nvidia-cuda-nvrtc-cu12==12.8.93
|
| 23 |
+
nvidia-cuda-runtime-cu12==12.8.90
|
| 24 |
+
nvidia-cudnn-cu12==9.10.2.21
|
| 25 |
+
nvidia-cufft-cu12==11.3.3.83
|
| 26 |
+
nvidia-cufile-cu12==1.13.1.3
|
| 27 |
+
nvidia-curand-cu12==10.3.9.90
|
| 28 |
+
nvidia-cusolver-cu12==11.7.3.90
|
| 29 |
+
nvidia-cusparse-cu12==12.5.8.93
|
| 30 |
+
nvidia-cusparselt-cu12==0.7.1
|
| 31 |
+
nvidia-nccl-cu12==2.27.5
|
| 32 |
+
nvidia-nvjitlink-cu12==12.8.93
|
| 33 |
+
nvidia-nvshmem-cu12==3.3.20
|
| 34 |
+
nvidia-nvtx-cu12==12.8.90
|
| 35 |
+
opencv-python==4.12.0.88
|
| 36 |
+
opencv-python-headless==4.12.0.88
|
| 37 |
+
packaging==25.0
|
| 38 |
+
pillow==12.1.0
|
| 39 |
+
polars==1.36.1
|
| 40 |
+
polars-runtime-32==1.36.1
|
| 41 |
+
psutil==7.2.1
|
| 42 |
+
pyparsing==3.3.1
|
| 43 |
+
python-dateutil==2.9.0.post0
|
| 44 |
+
PyYAML==6.0.3
|
| 45 |
+
requests==2.32.5
|
| 46 |
+
scipy==1.16.3
|
| 47 |
+
setuptools==80.9.0
|
| 48 |
+
six==1.17.0
|
| 49 |
+
sqlparse==0.5.5
|
| 50 |
+
sympy==1.14.0
|
| 51 |
+
torch==2.9.1
|
| 52 |
+
torchvision==0.24.1
|
| 53 |
+
triton==3.5.1
|
| 54 |
+
typing_extensions==4.15.0
|
| 55 |
+
ultralytics==8.3.246
|
| 56 |
+
ultralytics-thop==2.0.18
|
| 57 |
+
urllib3==2.6.2
|
satcap_project/__pycache__/settings.cpython-312.pyc
ADDED
|
Binary file (2.5 kB). View file
|
|
|
satcap_project/__pycache__/urls.cpython-312.pyc
ADDED
|
Binary file (722 Bytes). View file
|
|
|
satcap_project/__pycache__/wsgi.cpython-312.pyc
ADDED
|
Binary file (467 Bytes). View file
|
|
|
satcap_project/settings.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
| 5 |
+
|
| 6 |
+
SECRET_KEY = 'django-insecure-satcap-oceans-secret-key'
|
| 7 |
+
|
| 8 |
+
DEBUG = True
|
| 9 |
+
|
| 10 |
+
ALLOWED_HOSTS = ['*']
|
| 11 |
+
|
| 12 |
+
INSTALLED_APPS = [
|
| 13 |
+
'django.contrib.admin',
|
| 14 |
+
'django.contrib.auth',
|
| 15 |
+
'django.contrib.contenttypes',
|
| 16 |
+
'django.contrib.sessions',
|
| 17 |
+
'django.contrib.messages',
|
| 18 |
+
'django.contrib.staticfiles',
|
| 19 |
+
'rest_framework',
|
| 20 |
+
'corsheaders',
|
| 21 |
+
'api',
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
MIDDLEWARE = [
|
| 25 |
+
'corsheaders.middleware.CorsMiddleware',
|
| 26 |
+
'django.middleware.security.SecurityMiddleware',
|
| 27 |
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
| 28 |
+
'django.middleware.common.CommonMiddleware',
|
| 29 |
+
'django.middleware.csrf.CsrfViewMiddleware',
|
| 30 |
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
| 31 |
+
'django.contrib.messages.middleware.MessageMiddleware',
|
| 32 |
+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
ROOT_URLCONF = 'satcap_project.urls'
|
| 36 |
+
|
| 37 |
+
TEMPLATES = [
|
| 38 |
+
{
|
| 39 |
+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
| 40 |
+
'DIRS': [],
|
| 41 |
+
'APP_DIRS': True,
|
| 42 |
+
'OPTIONS': {
|
| 43 |
+
'context_processors': [
|
| 44 |
+
'django.template.context_processors.debug',
|
| 45 |
+
'django.template.context_processors.request',
|
| 46 |
+
'django.contrib.auth.context_processors.auth',
|
| 47 |
+
'django.contrib.messages.context_processors.messages',
|
| 48 |
+
],
|
| 49 |
+
},
|
| 50 |
+
},
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
WSGI_APPLICATION = 'satcap_project.wsgi.application'
|
| 54 |
+
|
| 55 |
+
DATABASES = {
|
| 56 |
+
'default': {
|
| 57 |
+
'ENGINE': 'django.db.backends.sqlite3',
|
| 58 |
+
'NAME': BASE_DIR / 'db.sqlite3',
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
AUTH_PASSWORD_VALIDATORS = [
|
| 63 |
+
{
|
| 64 |
+
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
| 74 |
+
},
|
| 75 |
+
]
|
| 76 |
+
|
| 77 |
+
LANGUAGE_CODE = 'en-us'
|
| 78 |
+
|
| 79 |
+
TIME_ZONE = 'UTC'
|
| 80 |
+
|
| 81 |
+
USE_I18N = True
|
| 82 |
+
|
| 83 |
+
USE_TZ = True
|
| 84 |
+
|
| 85 |
+
STATIC_URL = 'static/'
|
| 86 |
+
|
| 87 |
+
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
| 88 |
+
|
| 89 |
+
CORS_ALLOW_ALL_ORIGINS = True # For development
|
| 90 |
+
|
| 91 |
+
MEDIA_URL = '/media/'
|
| 92 |
+
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
satcap_project/urls.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from django.urls import path, include
|
| 3 |
+
from django.conf import settings
|
| 4 |
+
from django.conf.urls.static import static
|
| 5 |
+
|
| 6 |
+
urlpatterns = [
|
| 7 |
+
path('admin/', admin.site.urls),
|
| 8 |
+
path('api/', include('api.urls')),
|
| 9 |
+
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
satcap_project/wsgi.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from django.core.wsgi import get_wsgi_application
|
| 3 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'satcap_project.settings')
|
| 4 |
+
application = get_wsgi_application()
|