danielle2035 commited on
Commit
7ce37d1
·
1 Parent(s): 1f70e72
Files changed (2) hide show
  1. app.py +60 -9
  2. index.html +24 -2
app.py CHANGED
@@ -1,6 +1,9 @@
 
1
  import io
 
2
  from pathlib import Path
3
  from typing import List
 
4
 
5
  import numpy as np
6
  import torch
@@ -125,6 +128,41 @@ def predict_tensorflow(image: Image.Image):
125
  return predicted_class, confidence, all_probs
126
 
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  def classify(image: Image.Image, model_choice: str):
129
  if model_choice == "pytorch":
130
  return predict_pytorch(image)
@@ -145,15 +183,28 @@ def health_check():
145
 
146
 
147
  @app.post("/predict")
148
- def predict(image: UploadFile = File(...), model_choice: str = Form("pytorch")):
149
- if image.content_type.split('/')[0] != 'image':
150
- raise HTTPException(status_code=400, detail="Le fichier doit être une image.")
151
-
152
- image_data = image.file.read()
153
- try:
154
- img = Image.open(io.BytesIO(image_data)).convert("RGB")
155
- except Exception as exc:
156
- raise HTTPException(status_code=400, detail=f"Impossible de lire l'image: {exc}")
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
  predicted_class, confidence, all_probs = classify(img, model_choice)
159
  return {
 
1
+ import base64
2
  import io
3
+ import urllib.request
4
  from pathlib import Path
5
  from typing import List
6
+ from urllib.parse import urlparse
7
 
8
  import numpy as np
9
  import torch
 
128
  return predicted_class, confidence, all_probs
129
 
130
 
131
+ def load_image_from_url(image_url: str):
132
+ if not image_url or not image_url.strip():
133
+ raise ValueError("URL vide ou invalide.")
134
+
135
+ if image_url.startswith("data:"):
136
+ try:
137
+ header, encoded = image_url.split(",", 1)
138
+ if "base64" in header:
139
+ image_data = base64.b64decode(encoded)
140
+ else:
141
+ image_data = urllib.request.unquote_to_bytes(encoded)
142
+ return Image.open(io.BytesIO(image_data)).convert("RGB")
143
+ except Exception as exc:
144
+ raise ValueError(f"Impossible de lire le data URL: {exc}")
145
+
146
+ parsed = urlparse(image_url)
147
+ if parsed.scheme not in ("http", "https"):
148
+ raise ValueError("L'URL doit commencer par http:// ou https://")
149
+
150
+ try:
151
+ request = urllib.request.Request(
152
+ image_url,
153
+ headers={"User-Agent": "GeoClassifier/1.0"},
154
+ )
155
+ with urllib.request.urlopen(request, timeout=15) as response:
156
+ image_data = response.read()
157
+ except Exception as exc:
158
+ raise ValueError(f"Impossible de récupérer l'image depuis l'URL: {exc}")
159
+
160
+ try:
161
+ return Image.open(io.BytesIO(image_data)).convert("RGB")
162
+ except Exception as exc:
163
+ raise ValueError(f"Impossible de lire l'image depuis l'URL: {exc}")
164
+
165
+
166
  def classify(image: Image.Image, model_choice: str):
167
  if model_choice == "pytorch":
168
  return predict_pytorch(image)
 
183
 
184
 
185
  @app.post("/predict")
186
+ def predict(
187
+ image: UploadFile = File(None),
188
+ image_url: str = Form(None),
189
+ model_choice: str = Form("pytorch")
190
+ ):
191
+ if image is None and not image_url:
192
+ raise HTTPException(status_code=400, detail="Le fichier ou l'URL est requis.")
193
+
194
+ if image is not None:
195
+ if image.content_type.split('/')[0] != 'image':
196
+ raise HTTPException(status_code=400, detail="Le fichier doit être une image.")
197
+
198
+ image_data = image.file.read()
199
+ try:
200
+ img = Image.open(io.BytesIO(image_data)).convert("RGB")
201
+ except Exception as exc:
202
+ raise HTTPException(status_code=400, detail=f"Impossible de lire l'image: {exc}")
203
+ else:
204
+ try:
205
+ img = load_image_from_url(image_url)
206
+ except ValueError as exc:
207
+ raise HTTPException(status_code=400, detail=str(exc))
208
 
209
  predicted_class, confidence, all_probs = classify(img, model_choice)
210
  return {
index.html CHANGED
@@ -618,7 +618,11 @@ html::-webkit-scrollbar {
618
  }
619
 
620
  function toggleUrlInput() {
621
- document.getElementById('url-input-wrap').classList.toggle('hidden');
 
 
 
 
622
  }
623
 
624
  function resetAll() {
@@ -650,8 +654,26 @@ html::-webkit-scrollbar {
650
 
651
  if (!file && !urlVal) { showError(t('selectImage')); return; }
652
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
  const formData = new FormData();
654
- if (file) formData.append('image', file);
 
 
 
 
655
  formData.append('model_choice', modelChoice);
656
 
657
  setProcessing(true);
 
618
  }
619
 
620
  function toggleUrlInput() {
621
+ const wrap = document.getElementById('url-input-wrap');
622
+ wrap.classList.toggle('hidden');
623
+ if (wrap.classList.contains('hidden')) {
624
+ urlInput.value = '';
625
+ }
626
  }
627
 
628
  function resetAll() {
 
654
 
655
  if (!file && !urlVal) { showError(t('selectImage')); return; }
656
 
657
+ // Validation basique de l'URL
658
+ if (!file && urlVal) {
659
+ try {
660
+ const url = new URL(urlVal);
661
+ if (!['http:', 'https:', 'data:'].includes(url.protocol)) {
662
+ showError('URL invalide. Utilisez http://, https:// ou data:');
663
+ return;
664
+ }
665
+ } catch (e) {
666
+ showError('URL invalide. Vérifiez le format.');
667
+ return;
668
+ }
669
+ }
670
+
671
  const formData = new FormData();
672
+ if (file) {
673
+ formData.append('image', file);
674
+ } else {
675
+ formData.append('image_url', urlVal);
676
+ }
677
  formData.append('model_choice', modelChoice);
678
 
679
  setProcessing(true);