Spaces:
Sleeping
Sleeping
Emaglp commited on
Commit ·
c41e59f
1
Parent(s): 36d1800
Step 7 Complete: Docker image with Flask API and Gradio UI working locally
Browse files- .ipynb_checkpoints/Dockerfile-checkpoint +30 -0
- .ipynb_checkpoints/README-checkpoint.md +6 -0
- .ipynb_checkpoints/app_gradio-checkpoint.py +39 -0
- .ipynb_checkpoints/movieposter_api-checkpoint.py +74 -0
- .ipynb_checkpoints/requirements-api-checkpoint.txt +7 -0
- .ipynb_checkpoints/test_api-checkpoint.ipynb +106 -0
- Dockerfile +30 -0
- Dockerfile-api +0 -20
- README.md +4 -1
- app_gradio.py +3 -2
- requirements-api.txt +3 -1
- test_api.ipynb +0 -0
.ipynb_checkpoints/Dockerfile-checkpoint
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 1. Utiliser Python 3.10 (stable pour PyTorch et Gradio)
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# 2. Définir le dossier de travail dans le conteneur
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# 3. Installer les dépendances système nécessaires (OpenCV, Pillow, etc.)
|
| 8 |
+
RUN apt-get update && apt-get install -y \
|
| 9 |
+
libgl1 \
|
| 10 |
+
libglib2.0-0 \
|
| 11 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
# 4. Copier et installer les bibliothèques Python
|
| 14 |
+
COPY requirements-api.txt .
|
| 15 |
+
RUN pip install --no-cache-dir -r requirements-api.txt
|
| 16 |
+
# On force l'installation de Gradio et Requests au cas où
|
| 17 |
+
RUN pip install --no-cache-dir gradio requests
|
| 18 |
+
|
| 19 |
+
# 5. Copier tout ton code (le modèle .pth doit être dans un dossier 'weights/')
|
| 20 |
+
COPY . .
|
| 21 |
+
|
| 22 |
+
# 6. Exposer les ports pour l'extérieur
|
| 23 |
+
# 5075 = Flask (API) | 7860 = Gradio (Interface)
|
| 24 |
+
EXPOSE 5075 7860
|
| 25 |
+
|
| 26 |
+
# 7. La commande de lancement
|
| 27 |
+
# On utilise 'sh -c' pour lancer deux processus en même temps :
|
| 28 |
+
# 'python movieposter_api.py &' lance l'API en tâche de fond.
|
| 29 |
+
# 'python app_gradio.py' lance l'interface au premier plan.
|
| 30 |
+
CMD sh -c "python movieposter_api.py --model_path weights/movieposter_net.pth & python app_gradio.py"
|
.ipynb_checkpoints/README-checkpoint.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# projet_AIF
|
| 2 |
+
projet avancé jusqu' au point 3. de la partie 1. Il faut faire le 4. Build Gradio interface
|
| 3 |
+
Docker commun
|
| 4 |
+
#commandes dans terminal :
|
| 5 |
+
docker build -t movie-poster-app .
|
| 6 |
+
docker run -p 5075:5075 -p 7860:7860 movie-poster-app
|
.ipynb_checkpoints/app_gradio-checkpoint.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import requests
|
| 3 |
+
import io
|
| 4 |
+
from PIL import Image
|
| 5 |
+
|
| 6 |
+
def predict_movie_genre(image):
|
| 7 |
+
# 1. Configuration (basée sur le test de ta collègue)
|
| 8 |
+
API_URL = "http://127.0.0.1:5075/predict"
|
| 9 |
+
|
| 10 |
+
# 2. Conversion de l'image Gradio (PIL) en bytes pour l'API
|
| 11 |
+
img_byte_arr = io.BytesIO()
|
| 12 |
+
image.save(img_byte_arr, format='JPEG')
|
| 13 |
+
img_data = img_byte_arr.getvalue()
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
# 3. Envoi de la requête (format data brut comme dans son test)
|
| 17 |
+
response = requests.post(API_URL, data=img_data)
|
| 18 |
+
|
| 19 |
+
if response.status_code == 200:
|
| 20 |
+
prediction = response.json().get('label', 'Genre inconnu')
|
| 21 |
+
return f"🎬 Genre prédit : {prediction}"
|
| 22 |
+
else:
|
| 23 |
+
return f"⚠️ Erreur API : Code {response.status_code}"
|
| 24 |
+
|
| 25 |
+
except Exception as e:
|
| 26 |
+
return f"Impossible de contacter l'API. Est-elle lancée sur le port 5075 ? ({e})"
|
| 27 |
+
|
| 28 |
+
# 4. Création de l'interface visuelle
|
| 29 |
+
demo = gr.Interface(
|
| 30 |
+
fn=predict_movie_genre,
|
| 31 |
+
inputs=gr.Image(type="pil", label="Déposez un poster ici"),
|
| 32 |
+
outputs=gr.Text(label="Résultat de l'analyse"),
|
| 33 |
+
title="Analyseur de Posters de Films",
|
| 34 |
+
description="Cette interface utilise une API Flask et un modèle Deep Learning pour prédire le genre d'un film."
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
if __name__ == "__main__":
|
| 38 |
+
#CRUCIAL pour Docker : server_name="0.0.0.0" permet l'accès extérieur
|
| 39 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
.ipynb_checkpoints/movieposter_api-checkpoint.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import torch
|
| 3 |
+
import torchvision.transforms as transforms
|
| 4 |
+
from flask import Flask, jsonify, request
|
| 5 |
+
from PIL import Image
|
| 6 |
+
import io
|
| 7 |
+
from model import MovieposterNet
|
| 8 |
+
|
| 9 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 10 |
+
|
| 11 |
+
app = Flask(__name__)
|
| 12 |
+
|
| 13 |
+
# Liste des classes pour le mapping
|
| 14 |
+
CLASSES = ['action', 'animation', 'comedy', 'documentary', 'drama', 'fantasy', 'horror', 'romance', 'science Fiction', 'thriller']
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
parser = argparse.ArgumentParser()
|
| 19 |
+
parser.add_argument('--model_path', type=str, default = 'weights/movieposter_net.pth', help='model path')
|
| 20 |
+
args = parser.parse_args()
|
| 21 |
+
model_path = args.model_path
|
| 22 |
+
|
| 23 |
+
model = MovieposterNet().to(device)
|
| 24 |
+
model.load_state_dict(torch.load(model_path, map_location=device))
|
| 25 |
+
model.eval()
|
| 26 |
+
|
| 27 |
+
# Resizing des images
|
| 28 |
+
transform = transforms.Compose([
|
| 29 |
+
transforms.Resize((224, 224)),
|
| 30 |
+
transforms.ToTensor(),
|
| 31 |
+
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
|
| 32 |
+
])
|
| 33 |
+
|
| 34 |
+
@app.route('/predict', methods=['POST'])
|
| 35 |
+
def predict():
|
| 36 |
+
img_binary = request.data
|
| 37 |
+
img_pil = Image.open(io.BytesIO(img_binary))
|
| 38 |
+
|
| 39 |
+
# Transform the PIL image
|
| 40 |
+
tensor = transform(img_pil).to(device)
|
| 41 |
+
tensor = tensor.unsqueeze(0) # Add batch dimension
|
| 42 |
+
|
| 43 |
+
# Make prediction
|
| 44 |
+
with torch.no_grad():
|
| 45 |
+
outputs = model(tensor)
|
| 46 |
+
_, predicted = outputs.max(1)
|
| 47 |
+
|
| 48 |
+
return jsonify({"prediction": int(predicted[0]), "label": CLASSES[int(predicted[0])]})
|
| 49 |
+
|
| 50 |
+
@app.route('/batch_predict', methods=['POST'])
|
| 51 |
+
def batch_predict():
|
| 52 |
+
# Get the image data from the request
|
| 53 |
+
images_binary = request.files.getlist("images[]")
|
| 54 |
+
|
| 55 |
+
tensors = []
|
| 56 |
+
|
| 57 |
+
for img_binary in images_binary:
|
| 58 |
+
img_pil = Image.open(img_binary.stream)
|
| 59 |
+
tensor = transform(img_pil)
|
| 60 |
+
tensors.append(tensor)
|
| 61 |
+
|
| 62 |
+
# Stack tensors to form a batch tensor
|
| 63 |
+
batch_tensor = torch.stack(tensors, dim=0).to(device)
|
| 64 |
+
|
| 65 |
+
# Make prediction
|
| 66 |
+
with torch.no_grad():
|
| 67 |
+
outputs = model(batch_tensor)
|
| 68 |
+
_, predictions = outputs.max(1)
|
| 69 |
+
|
| 70 |
+
res = [CLASSES[idx] for idx in predictions.tolist()]
|
| 71 |
+
return jsonify({"predictions": res})
|
| 72 |
+
|
| 73 |
+
if __name__ == "__main__":
|
| 74 |
+
app.run(host='0.0.0.0', port=5075, debug=True)
|
.ipynb_checkpoints/requirements-api-checkpoint.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.0.1
|
| 2 |
+
torchvision==0.15.2
|
| 3 |
+
flask==2.3.2
|
| 4 |
+
pillow==10.0.0
|
| 5 |
+
numpy==1.24.4
|
| 6 |
+
gradio
|
| 7 |
+
requests
|
.ipynb_checkpoints/test_api-checkpoint.ipynb
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"id": "cc94cf7f",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [
|
| 9 |
+
{
|
| 10 |
+
"ename": "ValueError",
|
| 11 |
+
"evalue": "Sample larger than population or is negative",
|
| 12 |
+
"output_type": "error",
|
| 13 |
+
"traceback": [
|
| 14 |
+
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
|
| 15 |
+
"\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)",
|
| 16 |
+
"Cell \u001b[1;32mIn[1], line 23\u001b[0m\n\u001b[0;32m 20\u001b[0m all_images\u001b[38;5;241m.\u001b[39mappend((full_path, genre_reel))\n\u001b[0;32m 22\u001b[0m \u001b[38;5;66;03m# 3. Sélection de 10 posters au hasard\u001b[39;00m\n\u001b[1;32m---> 23\u001b[0m test_samples \u001b[38;5;241m=\u001b[39m random\u001b[38;5;241m.\u001b[39msample(all_images, \u001b[38;5;241m10\u001b[39m)\n\u001b[0;32m 25\u001b[0m \u001b[38;5;66;03m# 4. Affichage des résultats\u001b[39;00m\n\u001b[0;32m 26\u001b[0m plt\u001b[38;5;241m.\u001b[39mfigure(figsize\u001b[38;5;241m=\u001b[39m(\u001b[38;5;241m20\u001b[39m, \u001b[38;5;241m10\u001b[39m))\n",
|
| 17 |
+
"File \u001b[1;32m~\\anaconda3\\Lib\\random.py:430\u001b[0m, in \u001b[0;36mRandom.sample\u001b[1;34m(self, population, k, counts)\u001b[0m\n\u001b[0;32m 428\u001b[0m randbelow \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_randbelow\n\u001b[0;32m 429\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;241m0\u001b[39m \u001b[38;5;241m<\u001b[39m\u001b[38;5;241m=\u001b[39m k \u001b[38;5;241m<\u001b[39m\u001b[38;5;241m=\u001b[39m n:\n\u001b[1;32m--> 430\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mSample larger than population or is negative\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 431\u001b[0m result \u001b[38;5;241m=\u001b[39m [\u001b[38;5;28;01mNone\u001b[39;00m] \u001b[38;5;241m*\u001b[39m k\n\u001b[0;32m 432\u001b[0m setsize \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m21\u001b[39m \u001b[38;5;66;03m# size of a small set minus size of an empty list\u001b[39;00m\n",
|
| 18 |
+
"\u001b[1;31mValueError\u001b[0m: Sample larger than population or is negative"
|
| 19 |
+
]
|
| 20 |
+
}
|
| 21 |
+
],
|
| 22 |
+
"source": [
|
| 23 |
+
"import requests\n",
|
| 24 |
+
"import os\n",
|
| 25 |
+
"import random\n",
|
| 26 |
+
"import matplotlib.pyplot as plt\n",
|
| 27 |
+
"from PIL import Image\n",
|
| 28 |
+
"import io\n",
|
| 29 |
+
"\n",
|
| 30 |
+
"# 1. Configuration\n",
|
| 31 |
+
"API_URL = \"http://localhost:5075/predict\"\n",
|
| 32 |
+
"DATASET_PATH = \"../sorted_movie_posters_paligema\"\n",
|
| 33 |
+
"\n",
|
| 34 |
+
"# 2. Récupération de tous les chemins d'images\n",
|
| 35 |
+
"all_images = []\n",
|
| 36 |
+
"for root, dirs, files in os.walk(DATASET_PATH):\n",
|
| 37 |
+
" for file in files:\n",
|
| 38 |
+
" if file.lower().endswith(('.png', '.jpg', '.jpeg')):\n",
|
| 39 |
+
" full_path = os.path.join(root, file)\n",
|
| 40 |
+
" # On extrait le genre à partir du nom du dossier parent\n",
|
| 41 |
+
" genre_reel = os.path.basename(root)\n",
|
| 42 |
+
" all_images.append((full_path, genre_reel))\n",
|
| 43 |
+
"\n",
|
| 44 |
+
"# 3. Sélection de 10 posters au hasard\n",
|
| 45 |
+
"test_samples = random.sample(all_images, 10)\n",
|
| 46 |
+
"\n",
|
| 47 |
+
"# 4. Affichage des résultats\n",
|
| 48 |
+
"plt.figure(figsize=(20, 10))\n",
|
| 49 |
+
"\n",
|
| 50 |
+
"for i, (img_path, ground_truth) in enumerate(test_samples):\n",
|
| 51 |
+
" # Lecture et envoi de l'image à l'API\n",
|
| 52 |
+
" with open(img_path, \"rb\") as f:\n",
|
| 53 |
+
" img_data = f.read()\n",
|
| 54 |
+
" \n",
|
| 55 |
+
" try:\n",
|
| 56 |
+
" response = requests.post(API_URL, data=img_data)\n",
|
| 57 |
+
" prediction = response.json().get('label', 'Erreur')\n",
|
| 58 |
+
" except Exception as e:\n",
|
| 59 |
+
" prediction = \"API Down\"\n",
|
| 60 |
+
"\n",
|
| 61 |
+
" # Affichage\n",
|
| 62 |
+
" img = Image.open(img_path)\n",
|
| 63 |
+
" plt.subplot(2, 5, i + 1)\n",
|
| 64 |
+
" plt.imshow(img)\n",
|
| 65 |
+
" \n",
|
| 66 |
+
" # Couleur du titre : vert si correct, rouge si erreur\n",
|
| 67 |
+
" color = 'green' if prediction == ground_truth else 'red'\n",
|
| 68 |
+
" \n",
|
| 69 |
+
" plt.title(f\"Réel: {ground_truth}\\nPred: {prediction}\", color=color, fontsize=10)\n",
|
| 70 |
+
" plt.axis('off')\n",
|
| 71 |
+
"\n",
|
| 72 |
+
"plt.tight_layout()\n",
|
| 73 |
+
"plt.show()"
|
| 74 |
+
]
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"cell_type": "code",
|
| 78 |
+
"execution_count": null,
|
| 79 |
+
"id": "7ab72703",
|
| 80 |
+
"metadata": {},
|
| 81 |
+
"outputs": [],
|
| 82 |
+
"source": []
|
| 83 |
+
}
|
| 84 |
+
],
|
| 85 |
+
"metadata": {
|
| 86 |
+
"kernelspec": {
|
| 87 |
+
"display_name": "Python 3 (ipykernel)",
|
| 88 |
+
"language": "python",
|
| 89 |
+
"name": "python3"
|
| 90 |
+
},
|
| 91 |
+
"language_info": {
|
| 92 |
+
"codemirror_mode": {
|
| 93 |
+
"name": "ipython",
|
| 94 |
+
"version": 3
|
| 95 |
+
},
|
| 96 |
+
"file_extension": ".py",
|
| 97 |
+
"mimetype": "text/x-python",
|
| 98 |
+
"name": "python",
|
| 99 |
+
"nbconvert_exporter": "python",
|
| 100 |
+
"pygments_lexer": "ipython3",
|
| 101 |
+
"version": "3.12.3"
|
| 102 |
+
}
|
| 103 |
+
},
|
| 104 |
+
"nbformat": 4,
|
| 105 |
+
"nbformat_minor": 5
|
| 106 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 1. Utiliser Python 3.10 (stable pour PyTorch et Gradio)
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# 2. Définir le dossier de travail dans le conteneur
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# 3. Installer les dépendances système nécessaires (OpenCV, Pillow, etc.)
|
| 8 |
+
RUN apt-get update && apt-get install -y \
|
| 9 |
+
libgl1 \
|
| 10 |
+
libglib2.0-0 \
|
| 11 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
# 4. Copier et installer les bibliothèques Python
|
| 14 |
+
COPY requirements-api.txt .
|
| 15 |
+
RUN pip install --no-cache-dir -r requirements-api.txt
|
| 16 |
+
# On force l'installation de Gradio et Requests au cas où
|
| 17 |
+
RUN pip install --no-cache-dir gradio requests
|
| 18 |
+
|
| 19 |
+
# 5. Copier tout ton code (le modèle .pth doit être dans un dossier 'weights/')
|
| 20 |
+
COPY . .
|
| 21 |
+
|
| 22 |
+
# 6. Exposer les ports pour l'extérieur
|
| 23 |
+
# 5075 = Flask (API) | 7860 = Gradio (Interface)
|
| 24 |
+
EXPOSE 5075 7860
|
| 25 |
+
|
| 26 |
+
# 7. La commande de lancement
|
| 27 |
+
# On utilise 'sh -c' pour lancer deux processus en même temps :
|
| 28 |
+
# 'python movieposter_api.py &' lance l'API en tâche de fond.
|
| 29 |
+
# 'python app_gradio.py' lance l'interface au premier plan.
|
| 30 |
+
CMD sh -c "python movieposter_api.py --model_path weights/movieposter_net.pth & python app_gradio.py"
|
Dockerfile-api
DELETED
|
@@ -1,20 +0,0 @@
|
|
| 1 |
-
# Use an official Python runtime as the parent image
|
| 2 |
-
FROM python:3.10-slim
|
| 3 |
-
|
| 4 |
-
# Set the working directory in the container to /app
|
| 5 |
-
WORKDIR /app
|
| 6 |
-
|
| 7 |
-
# Copy the current directory contents into the container at /app
|
| 8 |
-
COPY . /app
|
| 9 |
-
|
| 10 |
-
# Install any needed packages specified in requirements.txt
|
| 11 |
-
RUN pip install --trusted-host pypi.python.org -r requirements-api.txt
|
| 12 |
-
|
| 13 |
-
# Make port 5075 available to the world outside this container
|
| 14 |
-
EXPOSE 5075
|
| 15 |
-
|
| 16 |
-
# Define environment variable for Flask to run in production mode
|
| 17 |
-
ENV FLASK_ENV=production
|
| 18 |
-
|
| 19 |
-
# Run mnist_api.py when the container launches
|
| 20 |
-
CMD ["python", "movieposter_api.py", "--model_path", "weights/movieposter_net.pth"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,3 +1,6 @@
|
|
| 1 |
# projet_AIF
|
| 2 |
projet avancé jusqu' au point 3. de la partie 1. Il faut faire le 4. Build Gradio interface
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# projet_AIF
|
| 2 |
projet avancé jusqu' au point 3. de la partie 1. Il faut faire le 4. Build Gradio interface
|
| 3 |
+
Docker commun
|
| 4 |
+
#commandes dans terminal :
|
| 5 |
+
docker build -t movie-poster-app .
|
| 6 |
+
docker run -p 5075:5075 -p 7860:7860 movie-poster-app
|
app_gradio.py
CHANGED
|
@@ -5,7 +5,7 @@ from PIL import Image
|
|
| 5 |
|
| 6 |
def predict_movie_genre(image):
|
| 7 |
# 1. Configuration (basée sur le test de ta collègue)
|
| 8 |
-
API_URL = "http://
|
| 9 |
|
| 10 |
# 2. Conversion de l'image Gradio (PIL) en bytes pour l'API
|
| 11 |
img_byte_arr = io.BytesIO()
|
|
@@ -35,4 +35,5 @@ demo = gr.Interface(
|
|
| 35 |
)
|
| 36 |
|
| 37 |
if __name__ == "__main__":
|
| 38 |
-
|
|
|
|
|
|
| 5 |
|
| 6 |
def predict_movie_genre(image):
|
| 7 |
# 1. Configuration (basée sur le test de ta collègue)
|
| 8 |
+
API_URL = "http://127.0.0.1:5075/predict"
|
| 9 |
|
| 10 |
# 2. Conversion de l'image Gradio (PIL) en bytes pour l'API
|
| 11 |
img_byte_arr = io.BytesIO()
|
|
|
|
| 35 |
)
|
| 36 |
|
| 37 |
if __name__ == "__main__":
|
| 38 |
+
#CRUCIAL pour Docker : server_name="0.0.0.0" permet l'accès extérieur
|
| 39 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
requirements-api.txt
CHANGED
|
@@ -2,4 +2,6 @@ torch==2.0.1
|
|
| 2 |
torchvision==0.15.2
|
| 3 |
flask==2.3.2
|
| 4 |
pillow==10.0.0
|
| 5 |
-
numpy==1.24.4
|
|
|
|
|
|
|
|
|
| 2 |
torchvision==0.15.2
|
| 3 |
flask==2.3.2
|
| 4 |
pillow==10.0.0
|
| 5 |
+
numpy==1.24.4
|
| 6 |
+
gradio
|
| 7 |
+
requests
|
test_api.ipynb
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|