Skip to content

Commit

Permalink
Merge pull request #25 from MLOps-essi-upc/api_tests
Browse files Browse the repository at this point in the history
Api tests
  • Loading branch information
xmoure authored Dec 6, 2023
2 parents 09baca0 + e3b10b2 commit 16561d7
Show file tree
Hide file tree
Showing 19 changed files with 199 additions and 92 deletions.
Binary file modified src/app/backend/__pycache__/api.cpython-39.pyc
Binary file not shown.
142 changes: 87 additions & 55 deletions src/app/backend/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,65 @@
from src import MODELS_DIR, API_DIR
from ultralytics import YOLO
import os
import asyncio
model_wrappers_list: List[dict] = []

# Define application
app = FastAPI(
title="Where is Wally",
description="lorem ipsum",
description="Upload an image and we will help you to find Wally",
version="0.1",
)


def construct_response(f):
"""Construct a JSON response for an endpoint's results."""

@wraps(f)
def wrap(request: Request, *args, **kwargs):
results = f(request, *args, **kwargs)

# Construct response
response = {
"message": results["message"],
"method": request.method,
"status-code": results["status-code"],
"timestamp": datetime.now().isoformat(),
"url": request.url._url,
}

# Add data
if "data" in results:
response["data"] = results["data"]

return response
async def wrap(request: Request, *args, **kwargs):
try:
if asyncio.iscoroutinefunction(f):
results = await f(request, *args, **kwargs)
else:
results = f(request, *args, **kwargs)

# Default status code
status_code = results.get("status-code", HTTPStatus.OK)

response = {
"message": results.get("message", status_code.phrase),
"method": request.method,
"status-code": status_code,
"timestamp": datetime.now().isoformat(),
"url": request.url._url,
"data": results.get("data", {}),
"found": results.get("found", None)
}

# Include additional keys if present
for key in ['boxes', 'conf', 'encoded_img']:
if key in results:
response[key] = results[key]

return response

except HTTPException as http_exc:
# Forward HTTP exceptions as they are
raise http_exc

except Exception as exc:
# Handle other exceptions
return {
"message": "An error occurred",
"method": request.method,
"status-code": HTTPStatus.INTERNAL_SERVER_ERROR,
"timestamp": datetime.now().isoformat(),
"url": request.url._url,
"detail": str(exc),
}

return wrap



@app.on_event("startup")
def _load_models():
"""Loads all pickled models found in `MODELS_DIR` and adds them to `models_list`"""
Expand All @@ -61,7 +85,6 @@ def _load_models():
with open(path, "rb") as file:
# model_wrapper = pickle.load(file)
# model_wrappers_list.append(model_wrapper)
print("file",str(file))
model_wrapper=dict()
model = YOLO(path)
model_wrapper["model"]=model
Expand All @@ -78,7 +101,7 @@ def _index(request: Request):
response = {
"message": HTTPStatus.OK.phrase,
"status-code": HTTPStatus.OK,
"data": {"message": "Hello world"},
"data": {"message": "Welcome to Where is Wally!"},
}
return response

Expand Down Expand Up @@ -109,42 +132,51 @@ def _get_models_list(request: Request, type: str = None):
}



@construct_response
@app.post("/predict/{type}")
async def _predict(type: str,file: UploadFile = File(...)):
model_wrapper = next((m for m in model_wrappers_list if m["type"] == type), None)

if model_wrapper:
model=model_wrapper['model']
contents = file.file.read()
# contents = await file.read()
nparr = np.fromstring(contents, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
results = model.predict(source=img, conf=0.25)
boxes=results[0].boxes.xyxy
conf=results[0].boxes.conf

for r in results:
annotator = Annotator(img)
boxes = r.boxes
for box in boxes:
b = box.xyxy[0] # get box coordinates in (top, left, bottom, right) format
c = box.cls
annotator.box_label(b, model.names[int(c)])

return_img = annotator.result()

# line that fixed it
_, encoded_img = cv2.imencode('.PNG', return_img)
encoded_img = base64.b64encode(encoded_img)

if not model_wrapper:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Model not found")

else:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Model not found"
)
return{
'boxes':boxes,
'conf':conf,
'encoded_img': encoded_img,
}
model = model_wrapper['model']
contents = await file.read()
nparr = np.frombuffer(contents, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
if img is None:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid image file")
else:
results = model.predict(source=img, conf=0.25)
boxes = results[0].boxes.xyxy
conf = results[0].boxes.conf

for r in results:
annotator = Annotator(img)
boxes = r.boxes
for box in boxes:
b = box.xyxy[0] # get box coordinates in (top, left, bottom, right) format
c = box.cls
color = (0, 0, 0)
annotator.box_label(b, model.names[int(c)], color=color)

return_img = annotator.result()

_, encoded_img = cv2.imencode('.PNG', return_img)
encoded_img = base64.b64encode(encoded_img)
is_empty = len(boxes) == 0
if(is_empty):
return {
'boxes': boxes,
'encoded_img': encoded_img.decode(),
'message': "Processing completed, but Wally was not found in the image.",
'found': False,
}

return {
'boxes': boxes,
'conf': conf,
'encoded_img': encoded_img.decode(),
'found': True
}
Binary file modified src/app/frontend/web/__pycache__/apiConstants.cpython-39.pyc
Binary file not shown.
Binary file modified src/app/frontend/web/pages/__pycache__/views.cpython-39.pyc
Binary file not shown.
34 changes: 14 additions & 20 deletions src/app/frontend/web/pages/templates/pages/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ <h1>Where Is Wally?</h1>
<h2>Select a Model</h2>
<ul id="model-selection-list">
<li class="model-option selected-model" data-keyword="all">Model that finds all characters</li>
<li class="model-option" data-keyword="wally">Model that only finds Waldo</li>
<li class="model-option" data-keyword="wally">Model that only finds Wally</li>
</ul>
</div>

Expand Down Expand Up @@ -241,16 +241,10 @@ <h2>Select a Model</h2>
const modelOptions = document.querySelectorAll('.model-option');
modelOptions.forEach(function(option) {
option.addEventListener('click', function() {
// Remove the selected class from all model options
modelOptions.forEach(function(opt) {
opt.classList.remove('selected-model');
});

// Add the selected class to the clicked option
option.classList.add('selected-model');

// You can also update the server or UI based on the selection here
console.log(option.textContent + ' selected');
});
});

Expand Down Expand Up @@ -289,7 +283,7 @@ <h2>Select a Model</h2>
const selectedModelKeyword = selectedModelElement ? selectedModelElement.dataset.keyword : null;

const imagePreview = document.getElementById('uploaded-image');
imagePreview.style.display = 'none'; // Hide the image preview
imagePreview.style.display = 'none';


// Show spinner
Expand All @@ -312,7 +306,11 @@ <h2>Select a Model</h2>
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
return response.json().then(errorData => {
const error = new Error(errorData.detail.detail || 'Network response was not ok');
error.isCustomError = true;
throw error;
});
}
return response.json();
}
Expand All @@ -321,17 +319,13 @@ <h2>Select a Model</h2>
const encodedImg = data.encoded_img;
const imagePreview = document.getElementById('uploaded-image');
const boxes = data.boxes;
const found = data.found

const dataIsEmpty = isDataEmpty(boxes);
if(dataIsEmpty){
document.getElementById('error-message').textContent = "We are very sorry but the model was not able to find Wally. Please try with the other model";
if(!found){
document.getElementById('error-message').textContent = data.message + "Please try the other model.";
document.getElementById('error-message').style.display = 'block';
}


console.log("boxes", boxes);

console.log('Is data empty:', dataIsEmpty);
imagePreview.src = `data:image/png;base64, ${encodedImg}`;
imagePreviewContainer.style.display = 'block';
imagePreview.style.display = 'block';
Expand All @@ -341,13 +335,13 @@ <h2>Select a Model</h2>

})
.catch(error => {
console.error('Error:', error);
const errorMessage = error.isCustomError ? error.message : "An unexpected error occurred.";

// Hide spinner in case of error as well
document.getElementById('spinner').style.display = 'none';

// Display error message
document.getElementById('error-message').textContent = error.message;
// Display error message
document.getElementById('error-message').textContent = errorMessage;
document.getElementById('error-message').style.display = 'block';

});
Expand Down
37 changes: 20 additions & 17 deletions src/app/frontend/web/pages/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,28 @@
@csrf_exempt
def upload_image(request):
if request.method == 'POST' and request.FILES.get('image'):
try:
model_request = request.GET.get('model', "all")
image = request.FILES['image'].read()
data = {
'file': image
}
url= api.PREDICT+model_request
response = requests.post(url,files=data)

if response.status_code != 200:
return JsonResponse({
'status': 'error',
'message': f'Error from server: {response.status_code}',
'detail': response.json()
}, status=response.status_code, )

response_data = response.json()
return JsonResponse(response_data)

model_request = request.GET.get('model', "all")
print("model_request", model_request)


image = request.FILES['image'].read()

# API call
# TODO: not working the endpoint call
data = {
'file': image
}
url= api.PREDICT+model_request
response = requests.post(url,files=data)

# #convert reponse data into json
response = response.json()

return JsonResponse(response)
except requests.exceptions.RequestException as e:
return JsonResponse({'status': 'error', 'message': str(e)})

return JsonResponse({'status': 'error'})

Expand Down
Binary file not shown.
Binary file added src/models/__pycache__/__init__.cpython-39.pyc
Binary file not shown.
Binary file added src/models/__pycache__/evaluate.cpython-39.pyc
Binary file not shown.
Binary file not shown.
3 changes: 3 additions & 0 deletions tests/api_test/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
filterwarnings =
ignore::DeprecationWarning
Loading

0 comments on commit 16561d7

Please sign in to comment.