From 0d6e16867317720589bcc4c9ce84644e21722807 Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Tue, 15 Oct 2019 13:20:25 +0200 Subject: [PATCH 01/13] Add Validator for app - Add the abstract class Validator - Add the class PredictRequestValidator for validating single predict requests - Add fastai to requirements --- .../app/requests/PredictRequestValidator.py | 16 ++++++++++++++++ autofocus/predict/app/requests/Validator.py | 13 +++++++++++++ autofocus/predict/app/requests/__init__.py | 5 +++++ autofocus/predict/requirements.txt | 1 + 4 files changed, 35 insertions(+) create mode 100644 autofocus/predict/app/requests/PredictRequestValidator.py create mode 100644 autofocus/predict/app/requests/Validator.py create mode 100644 autofocus/predict/app/requests/__init__.py diff --git a/autofocus/predict/app/requests/PredictRequestValidator.py b/autofocus/predict/app/requests/PredictRequestValidator.py new file mode 100644 index 0000000..9ee8dc0 --- /dev/null +++ b/autofocus/predict/app/requests/PredictRequestValidator.py @@ -0,0 +1,16 @@ +from . import Validator, ALLOWED_IMAGE_FILES +from ..utils import allowed_file + +class PredictRequestValidator(Validator): + def validate(self): + self.error = {} + + file = self.request.files.get('file', None) + if not file: + self.error['file'] = "No file given." + elif not allowed_file(file.filename, ALLOWED_IMAGE_FILES): + self.error['file'] = "File type not allowed. File must be of type {allowed}".format( + allowed=ALLOWED_IMAGE_FILES + ) + + return not (self.error) diff --git a/autofocus/predict/app/requests/Validator.py b/autofocus/predict/app/requests/Validator.py new file mode 100644 index 0000000..69478cb --- /dev/null +++ b/autofocus/predict/app/requests/Validator.py @@ -0,0 +1,13 @@ +from abc import ABC, abstractmethod + +class Validator(ABC): + def __init__(self, request): + self.request = request + self.error = {} + + @abstractmethod + def validate(self): + pass + + def getError(self): + return self.error diff --git a/autofocus/predict/app/requests/__init__.py b/autofocus/predict/app/requests/__init__.py new file mode 100644 index 0000000..2e199c6 --- /dev/null +++ b/autofocus/predict/app/requests/__init__.py @@ -0,0 +1,5 @@ +ALLOWED_IMAGE_FILES = set(["png", "jpg", "jpeg", "gif", "bmp"]) + +from .Validator import Validator + +from .PredictRequestValidator import PredictRequestValidator diff --git a/autofocus/predict/requirements.txt b/autofocus/predict/requirements.txt index d1b17c1..a4469ff 100644 --- a/autofocus/predict/requirements.txt +++ b/autofocus/predict/requirements.txt @@ -2,3 +2,4 @@ Flask gunicorn numpy pillow +fastai From a0bf7195f3ffe93d53ab59c034d7afbfecd072b8 Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Tue, 15 Oct 2019 14:05:41 +0200 Subject: [PATCH 02/13] Use Validator - Use Validator in predict single - Add abort function to Validator --- autofocus/predict/app/app.py | 42 ++++++++++----------- autofocus/predict/app/requests/Validator.py | 11 ++++++ 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/autofocus/predict/app/app.py b/autofocus/predict/app/app.py index 27e8cd4..5b39614 100644 --- a/autofocus/predict/app/app.py +++ b/autofocus/predict/app/app.py @@ -8,6 +8,8 @@ from .model import predict_multiple, predict_single from .utils import allowed_file, filter_image_files, list_zip_files +from .requests import PredictRequestValidator + # We are going to upload the files to the server as part of the request, so set tmp folder here. UPLOAD_FOLDER = "/tmp/" ALLOWED_EXTENSIONS = set(["png", "jpg", "jpeg", "gif", "bmp"]) @@ -17,37 +19,31 @@ app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER -@app.route("/predict", methods=["GET", "POST"]) +@app.route("/predict", methods=["POST"]) def classify_single(): """Classify a single image""" - if request.method == "POST": - file = request.files["file"] + validator = PredictRequestValidator(request) + if(not validator.validate()): + validator.abort() - if not file: - return "No file sent." - - filename = secure_filename(file.filename) + file = request.files["file"] + filename = secure_filename(file.filename) - if allowed_file(filename, ALLOWED_EXTENSIONS): - file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename) - # this isn't super-optimal since it's saving the file to the server - file.save(file_path) + file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename) + # this isn't super-optimal since it's saving the file to the server + file.save(file_path) - app.logger.info("Classifying image %s" % (file_path)) + app.logger.info("Classifying image %s" % (file_path)) - # Get the predictions (output of the softmax) for this image - t = time.time() - predictions = predict_single(file_path) - dt = time.time() - t - app.logger.info("Execution time: %0.2f" % (dt * 1000.0)) + # Get the predictions (output of the softmax) for this image + t = time.time() + predictions = predict_single(file_path) + dt = time.time() - t + app.logger.info("Execution time: %0.2f" % (dt * 1000.0)) - os.remove(file_path) + os.remove(file_path) - return jsonify(predictions) - else: - return "File type not allowed. File must be of type {allowed}".format( - allowed=ALLOWED_EXTENSIONS - ) + return jsonify(predictions) @app.route("/predict_zip", methods=["GET", "POST"]) diff --git a/autofocus/predict/app/requests/Validator.py b/autofocus/predict/app/requests/Validator.py index 69478cb..cfe7728 100644 --- a/autofocus/predict/app/requests/Validator.py +++ b/autofocus/predict/app/requests/Validator.py @@ -1,4 +1,6 @@ from abc import ABC, abstractmethod +from flask import jsonify, make_response, abort +from flask_api import status class Validator(ABC): def __init__(self, request): @@ -11,3 +13,12 @@ def validate(self): def getError(self): return self.error + + def abort(self): + abort(make_response( + jsonify( + status=status.HTTP_400_BAD_REQUEST, + error=self.getError() + ), + status.HTTP_400_BAD_REQUEST + )) From 7511cab258420a381025b1a4c4262370dd585b6d Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Tue, 15 Oct 2019 14:18:09 +0200 Subject: [PATCH 03/13] Add Validator for zip files - Add PredictZipRequestValidator to module requests --- .../app/requests/PredictZipRequestValidator.py | 16 ++++++++++++++++ autofocus/predict/app/requests/__init__.py | 2 ++ 2 files changed, 18 insertions(+) create mode 100644 autofocus/predict/app/requests/PredictZipRequestValidator.py diff --git a/autofocus/predict/app/requests/PredictZipRequestValidator.py b/autofocus/predict/app/requests/PredictZipRequestValidator.py new file mode 100644 index 0000000..482d1af --- /dev/null +++ b/autofocus/predict/app/requests/PredictZipRequestValidator.py @@ -0,0 +1,16 @@ +from . import Validator, ALLOWED_ZIP_FILES +from ..utils import allowed_file + +class PredictZipRequestValidator(Validator): + def validate(self): + self.error = {} + + file = self.request.files.get('file', None) + if not file: + self.error['file'] = "No file given." + elif not allowed_file(file.filename, ALLOWED_ZIP_FILES): + self.error['file'] = "File type not allowed. File must be of type {allowed}".format( + allowed=ALLOWED_ZIP_FILES + ) + + return not (self.error) diff --git a/autofocus/predict/app/requests/__init__.py b/autofocus/predict/app/requests/__init__.py index 2e199c6..98956df 100644 --- a/autofocus/predict/app/requests/__init__.py +++ b/autofocus/predict/app/requests/__init__.py @@ -1,5 +1,7 @@ ALLOWED_IMAGE_FILES = set(["png", "jpg", "jpeg", "gif", "bmp"]) +ALLOWED_ZIP_FILES = {"zip"} from .Validator import Validator from .PredictRequestValidator import PredictRequestValidator +from .PredictZipRequestValidator import PredictZipRequestValidator From 8e046098de03c6fe638942fe24405e912a34f66e Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Tue, 15 Oct 2019 14:38:52 +0200 Subject: [PATCH 04/13] Use PredictZipRequestValidator --- autofocus/predict/app/app.py | 76 +++++++++++++++++------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/autofocus/predict/app/app.py b/autofocus/predict/app/app.py index 5b39614..be8d29c 100644 --- a/autofocus/predict/app/app.py +++ b/autofocus/predict/app/app.py @@ -8,7 +8,7 @@ from .model import predict_multiple, predict_single from .utils import allowed_file, filter_image_files, list_zip_files -from .requests import PredictRequestValidator +from .requests import PredictRequestValidator, PredictZipRequestValidator # We are going to upload the files to the server as part of the request, so set tmp folder here. UPLOAD_FOLDER = "/tmp/" @@ -23,7 +23,7 @@ def classify_single(): """Classify a single image""" validator = PredictRequestValidator(request) - if(not validator.validate()): + if not validator.validate(): validator.abort() file = request.files["file"] @@ -46,57 +46,53 @@ def classify_single(): return jsonify(predictions) -@app.route("/predict_zip", methods=["GET", "POST"]) +@app.route("/predict_zip", methods=["POST"]) def classify_zip(): """Classify all images from a zip file""" - if request.method == "POST": - file = request.files["file"] - - if not file: - return "No file sent." - - if not file.filename.split(".")[-1] == "zip": - return ".zip is the only compression format currently supported" + validator = PredictZipRequestValidator(request) + if not validator.validate(): + validator.abort() - filename = secure_filename(file.filename) - zip_file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename) - file.save(zip_file_path) + file = request.files["file"] + filename = secure_filename(file.filename) + zip_file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename) + file.save(zip_file_path) - zip_file = ZipFile(zip_file_path) - zip_file_list = list_zip_files(zip_file_path) - all_images = filter_image_files(zip_file_list, ALLOWED_EXTENSIONS) + zip_file = ZipFile(zip_file_path) + zip_file_list = list_zip_files(zip_file_path) + all_images = filter_image_files(zip_file_list, ALLOWED_EXTENSIONS) - if len(all_images) == 0: - return "No image files detected in the zip file" + if len(all_images) == 0: + return "No image files detected in the zip file" - # loop through images - start = 0 - increment = 500 - all_images_len = len(all_images) + # loop through images + start = 0 + increment = 500 + all_images_len = len(all_images) - while start < all_images_len: - end = start + increment - if end > len(all_images): - end = len(all_images) + while start < all_images_len: + end = start + increment + if end > len(all_images): + end = len(all_images) - # extract filenames - curr_file_list = all_images[start:end] - for filename in curr_file_list: - zip_file.extract(filename, path=app.config["UPLOAD_FOLDER"]) + # extract filenames + curr_file_list = all_images[start:end] + for filename in curr_file_list: + zip_file.extract(filename, path=app.config["UPLOAD_FOLDER"]) - curr_file_list = [ - os.path.join(app.config["UPLOAD_FOLDER"], x) for x in curr_file_list - ] + curr_file_list = [ + os.path.join(app.config["UPLOAD_FOLDER"], x) for x in curr_file_list + ] - predictions = predict_multiple(curr_file_list) + predictions = predict_multiple(curr_file_list) - # remove files - for curr_file in curr_file_list: - os.remove(curr_file) + # remove files + for curr_file in curr_file_list: + os.remove(curr_file) - return make_response(jsonify(predictions)) + return make_response(jsonify(predictions)) - start = end + 1 + start = end + 1 @app.route("/hello") From 75eeaddc6081de420f7e29a69aab81ba6637c26b Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Tue, 15 Oct 2019 14:53:55 +0200 Subject: [PATCH 05/13] Add File class - Handle file through file class - File is removed in the destructor - Use in predict_single --- autofocus/predict/app/app.py | 15 ++++----------- autofocus/predict/app/models/File.py | 15 +++++++++++++++ autofocus/predict/app/models/__init__.py | 1 + 3 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 autofocus/predict/app/models/File.py create mode 100644 autofocus/predict/app/models/__init__.py diff --git a/autofocus/predict/app/app.py b/autofocus/predict/app/app.py index be8d29c..263ca3e 100644 --- a/autofocus/predict/app/app.py +++ b/autofocus/predict/app/app.py @@ -7,7 +7,7 @@ from .model import predict_multiple, predict_single from .utils import allowed_file, filter_image_files, list_zip_files - +from .models import File from .requests import PredictRequestValidator, PredictZipRequestValidator # We are going to upload the files to the server as part of the request, so set tmp folder here. @@ -26,23 +26,16 @@ def classify_single(): if not validator.validate(): validator.abort() - file = request.files["file"] - filename = secure_filename(file.filename) + file = File(request.files["file"]) - file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename) - # this isn't super-optimal since it's saving the file to the server - file.save(file_path) - - app.logger.info("Classifying image %s" % (file_path)) + app.logger.info("Classifying image %s" % (file.getPath())) # Get the predictions (output of the softmax) for this image t = time.time() - predictions = predict_single(file_path) + predictions = predict_single(file.getPath()) dt = time.time() - t app.logger.info("Execution time: %0.2f" % (dt * 1000.0)) - os.remove(file_path) - return jsonify(predictions) diff --git a/autofocus/predict/app/models/File.py b/autofocus/predict/app/models/File.py new file mode 100644 index 0000000..e0d7c9c --- /dev/null +++ b/autofocus/predict/app/models/File.py @@ -0,0 +1,15 @@ +import os +from werkzeug import secure_filename +from ..app import app + +class File: + def __init__(self, file): + self.name = secure_filename(file.filename) + self.path = os.path.join(app.config["UPLOAD_FOLDER"], self.name) + file.save(self.path) + + def __del__(self): + os.remove(self.path) + + def getPath(self): + return self.path diff --git a/autofocus/predict/app/models/__init__.py b/autofocus/predict/app/models/__init__.py new file mode 100644 index 0000000..3f4e907 --- /dev/null +++ b/autofocus/predict/app/models/__init__.py @@ -0,0 +1 @@ +from .File import File From fa5bd2ba15d23b531417e37bc964f207f3083541 Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Tue, 15 Oct 2019 19:35:26 +0200 Subject: [PATCH 06/13] Add ZipArchive - Add ZipArchive to handle zips - Add predict multiple method to Predictor - Remove unused utils - Change File to set file or pathname - Change predict_zip in app --- autofocus/predict/app/app.py | 69 ++++++---------------- autofocus/predict/app/model.py | 29 --------- autofocus/predict/app/models/File.py | 13 +++- autofocus/predict/app/models/Predictor.py | 28 +++++++++ autofocus/predict/app/models/ZipArchive.py | 29 +++++++++ autofocus/predict/app/models/__init__.py | 2 + autofocus/predict/app/utils.py | 31 ---------- 7 files changed, 88 insertions(+), 113 deletions(-) delete mode 100644 autofocus/predict/app/model.py create mode 100644 autofocus/predict/app/models/Predictor.py create mode 100644 autofocus/predict/app/models/ZipArchive.py diff --git a/autofocus/predict/app/app.py b/autofocus/predict/app/app.py index 263ca3e..8280746 100644 --- a/autofocus/predict/app/app.py +++ b/autofocus/predict/app/app.py @@ -1,18 +1,12 @@ -import os import time -from zipfile import ZipFile -from flask import Flask, jsonify, make_response, request -from werkzeug import secure_filename +from flask import Flask, jsonify, request -from .model import predict_multiple, predict_single -from .utils import allowed_file, filter_image_files, list_zip_files -from .models import File +from .models import File, Predictor, ZipArchive from .requests import PredictRequestValidator, PredictZipRequestValidator # We are going to upload the files to the server as part of the request, so set tmp folder here. UPLOAD_FOLDER = "/tmp/" -ALLOWED_EXTENSIONS = set(["png", "jpg", "jpeg", "gif", "bmp"]) app = Flask(__name__) app.config.from_object(__name__) @@ -22,70 +16,45 @@ @app.route("/predict", methods=["POST"]) def classify_single(): """Classify a single image""" + # Validate request validator = PredictRequestValidator(request) if not validator.validate(): validator.abort() + # Get File object file = File(request.files["file"]) + # Predict probabilities app.logger.info("Classifying image %s" % (file.getPath())) - - # Get the predictions (output of the softmax) for this image t = time.time() - predictions = predict_single(file.getPath()) + predictor = Predictor() + predictor.predict(file) dt = time.time() - t app.logger.info("Execution time: %0.2f" % (dt * 1000.0)) - return jsonify(predictions) + # Return ziped probabilities + return jsonify(predictor.getProbabilities()) @app.route("/predict_zip", methods=["POST"]) def classify_zip(): """Classify all images from a zip file""" + # Validate request validator = PredictZipRequestValidator(request) if not validator.validate(): validator.abort() - file = request.files["file"] - filename = secure_filename(file.filename) - zip_file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename) - file.save(zip_file_path) - - zip_file = ZipFile(zip_file_path) - zip_file_list = list_zip_files(zip_file_path) - all_images = filter_image_files(zip_file_list, ALLOWED_EXTENSIONS) - - if len(all_images) == 0: - return "No image files detected in the zip file" - - # loop through images - start = 0 - increment = 500 - all_images_len = len(all_images) - - while start < all_images_len: - end = start + increment - if end > len(all_images): - end = len(all_images) - - # extract filenames - curr_file_list = all_images[start:end] - for filename in curr_file_list: - zip_file.extract(filename, path=app.config["UPLOAD_FOLDER"]) - - curr_file_list = [ - os.path.join(app.config["UPLOAD_FOLDER"], x) for x in curr_file_list - ] - - predictions = predict_multiple(curr_file_list) - - # remove files - for curr_file in curr_file_list: - os.remove(curr_file) + file = ZipArchive(request.files["file"]) + if not file.hasImages(): + validator.error['file'] = "No image files detected in the zip file." + validator.abort() - return make_response(jsonify(predictions)) + # Extract files + files = file.extractAll(app.config["UPLOAD_FOLDER"], file.listAllImages()) - start = end + 1 + # Make prediction + predictor = Predictor() + return jsonify(predictor.predict_multiple(files)) @app.route("/hello") diff --git a/autofocus/predict/app/model.py b/autofocus/predict/app/model.py deleted file mode 100644 index 1dbf831..0000000 --- a/autofocus/predict/app/model.py +++ /dev/null @@ -1,29 +0,0 @@ -from pathlib import Path - -from fastai.vision import load_learner, open_image - - -MODEL_DIR = Path(__file__).resolve().parents[1] / "models" -MODEL_NAME = "multilabel_model_20190407.pkl" -model = load_learner(MODEL_DIR, MODEL_NAME) -CLASSES = model.data.classes - - -def predict_single(path): - image = open_image(path) - pred_classes, preds, probs = model.predict(image) - probs = [prob.item() for prob in probs] - return dict(zip(CLASSES, probs)) - - -def predict_multiple(path_list): - predictions = {} - for path in path_list: - path_without_tmp = Path(*Path(path).parts[2:]) - predictions[str(path_without_tmp)] = predict_single(path) - return predictions - - -if __name__ == "__main__": - test_image_path = Path(__file__).parent / "test/flower.jpeg" - prediction = predict_single(test_image_path) diff --git a/autofocus/predict/app/models/File.py b/autofocus/predict/app/models/File.py index e0d7c9c..79e6ff5 100644 --- a/autofocus/predict/app/models/File.py +++ b/autofocus/predict/app/models/File.py @@ -3,13 +3,20 @@ from ..app import app class File: - def __init__(self, file): + def __init__(self, file=None): + if file: + self.setFromUploadedFile(file) + + def __del__(self): + os.remove(self.path) + + def setFromUploadedFile(self, file): self.name = secure_filename(file.filename) self.path = os.path.join(app.config["UPLOAD_FOLDER"], self.name) file.save(self.path) - def __del__(self): - os.remove(self.path) + def setPath(self, path): + self.path = path def getPath(self): return self.path diff --git a/autofocus/predict/app/models/Predictor.py b/autofocus/predict/app/models/Predictor.py new file mode 100644 index 0000000..f3ba5f2 --- /dev/null +++ b/autofocus/predict/app/models/Predictor.py @@ -0,0 +1,28 @@ +import time +from pathlib import Path +from fastai.vision import load_learner, open_image + +from ..model import predict_single + +MODEL_DIR = Path(__file__).resolve().parents[2] / "models" +MODEL_NAME = "multilabel_model_20190407.pkl" +model = load_learner(MODEL_DIR, MODEL_NAME) +CLASSES = model.data.classes + +class Predictor: + def predict(self, file): + image = open_image(file.getPath()) + # Get the predictions (output of the softmax) for this image + pred_classes, preds, probs = model.predict(image) + self.probabilities = [prob.item() for prob in probs] + + def predict_multiple(self, files): + predictions = {} + for key in files: + self.predict(files[key]) + predictions[key] = self.getProbabilities() + return predictions + + + def getProbabilities(self): + return dict(zip(CLASSES, self.probabilities)) diff --git a/autofocus/predict/app/models/ZipArchive.py b/autofocus/predict/app/models/ZipArchive.py new file mode 100644 index 0000000..2055770 --- /dev/null +++ b/autofocus/predict/app/models/ZipArchive.py @@ -0,0 +1,29 @@ +from zipfile import ZipFile +import os + +from . import File +from ..requests import ALLOWED_IMAGE_FILES +from ..utils import allowed_file + +class ZipArchive: + def __init__(self, file): + self.file = File.File(file) + self.zip = ZipFile(self.file.getPath()) + + def listFiles(self): + return [file.filename for file in self.zip.infolist()] + + def listAllImages(self, extensions=ALLOWED_IMAGE_FILES): + return [file for file in self.listFiles() if allowed_file(file, extensions)] + + def hasImages(self, extensions=ALLOWED_IMAGE_FILES): + return len(self.listAllImages(extensions)) > 0 + + def extractAll(self, path=None, members=None, pwd=None): + self.zip.extractall(path, members, pwd) + extractedFiles = {} + for member in members: + file = File.File() + file.setPath(os.path.join(path, member)) + extractedFiles[member] = file + return extractedFiles \ No newline at end of file diff --git a/autofocus/predict/app/models/__init__.py b/autofocus/predict/app/models/__init__.py index 3f4e907..7480d49 100644 --- a/autofocus/predict/app/models/__init__.py +++ b/autofocus/predict/app/models/__init__.py @@ -1 +1,3 @@ from .File import File +from .ZipArchive import ZipArchive +from .Predictor import Predictor diff --git a/autofocus/predict/app/utils.py b/autofocus/predict/app/utils.py index ce201cc..a1e9fcb 100644 --- a/autofocus/predict/app/utils.py +++ b/autofocus/predict/app/utils.py @@ -13,34 +13,3 @@ def allowed_file(filename, allowed_extensions): """ return Path(filename).suffix.lower().replace(".", "") in allowed_extensions - - -def list_zip_files(path): - """ - List the files in a zip archive. - Args: - path(str): path to the zip file - - Returns: - list of files - - """ - - file = ZipFile(path) - all_files = file.infolist() - - return [x.filename for x in all_files] - - -def filter_image_files(file_list, img_extensions=["jpeg", "jpg", "png", "bmp", "gif"]): - """ - Filter the image files out of a list - - Args: - file_list(list): list of file strings - img_extensions(list): file extensions for image files - - Returns: - - """ - return [x for x in file_list if allowed_file(x, img_extensions)] From 191a6da634599f55ef1ac24c21016f9c45dda13c Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Wed, 16 Oct 2019 11:09:16 +0200 Subject: [PATCH 07/13] Add docstrings - Add docstrings for new classes - Remove unused imports --- autofocus/predict/app/models/File.py | 40 +++++++++++ autofocus/predict/app/models/Predictor.py | 32 ++++++++- autofocus/predict/app/models/ZipArchive.py | 67 +++++++++++++++++-- autofocus/predict/app/models/__init__.py | 2 +- .../app/requests/PredictRequestValidator.py | 15 ++++- .../requests/PredictZipRequestValidator.py | 15 ++++- autofocus/predict/app/requests/Validator.py | 36 +++++++++- autofocus/predict/app/requests/__init__.py | 9 ++- autofocus/predict/app/utils.py | 1 - 9 files changed, 200 insertions(+), 17 deletions(-) diff --git a/autofocus/predict/app/models/File.py b/autofocus/predict/app/models/File.py index 79e6ff5..70873b0 100644 --- a/autofocus/predict/app/models/File.py +++ b/autofocus/predict/app/models/File.py @@ -2,21 +2,61 @@ from werkzeug import secure_filename from ..app import app + class File: + """ + Store a file and remove it upon destruction + + Parameters: + path: The path to the file + name: Secured filename (Can be empty) + """ + def __init__(self, file=None): + """ + Constructor of File + + Save the file on the server if a file is given. + + Parameters: + file: Uploaded file object from flask + """ if file: self.setFromUploadedFile(file) def __del__(self): + """ + Destructor of File + + Remove the file from the server. + """ os.remove(self.path) def setFromUploadedFile(self, file): + """ + Save file from uploaded file + + Parameters: + file: Uploaded file object from flask + """ self.name = secure_filename(file.filename) self.path = os.path.join(app.config["UPLOAD_FOLDER"], self.name) file.save(self.path) def setPath(self, path): + """ + Set the path to a saved file + + Parameters: + path: Path to the file + """ self.path = path def getPath(self): + """ + Return the saved path + + Returns: + string: Path to the file + """ return self.path diff --git a/autofocus/predict/app/models/Predictor.py b/autofocus/predict/app/models/Predictor.py index f3ba5f2..e5d4a42 100644 --- a/autofocus/predict/app/models/Predictor.py +++ b/autofocus/predict/app/models/Predictor.py @@ -1,22 +1,44 @@ -import time from pathlib import Path + from fastai.vision import load_learner, open_image -from ..model import predict_single MODEL_DIR = Path(__file__).resolve().parents[2] / "models" MODEL_NAME = "multilabel_model_20190407.pkl" model = load_learner(MODEL_DIR, MODEL_NAME) CLASSES = model.data.classes + class Predictor: + """ + Predicts probabilities with the model based on given files + + Parameters: + probabilities: Array of probabilities calculated in predict + """ + def predict(self, file): + """ + Predict probabilities of single file + + Parameters: + file: File object of image file + """ image = open_image(file.getPath()) # Get the predictions (output of the softmax) for this image pred_classes, preds, probs = model.predict(image) self.probabilities = [prob.item() for prob in probs] def predict_multiple(self, files): + """ + Predict probabilities of multiple files + + Parameters: + files: Dict with File objects of image file + + Returns: + dict: Dictionary of probabilities for each file in files + """ predictions = {} for key in files: self.predict(files[key]) @@ -25,4 +47,10 @@ def predict_multiple(self, files): def getProbabilities(self): + """ + Return formated Probabilities + + Returns: + dict: A dictionary of classes to probabilities + """ return dict(zip(CLASSES, self.probabilities)) diff --git a/autofocus/predict/app/models/ZipArchive.py b/autofocus/predict/app/models/ZipArchive.py index 2055770..cb8abe2 100644 --- a/autofocus/predict/app/models/ZipArchive.py +++ b/autofocus/predict/app/models/ZipArchive.py @@ -1,29 +1,86 @@ -from zipfile import ZipFile import os - +from zipfile import ZipFile from . import File from ..requests import ALLOWED_IMAGE_FILES from ..utils import allowed_file + class ZipArchive: + """ + Archive of a zip file + + This class is to store and access a zip file. + + Parameters: + file: The storage of the zip file (gets removed from the os upon destructor call) + zip: Opened zip file + """ + def __init__(self, file): + """ + Constructor of ZipFile + + Store the given file and open the zip file. + + Parameters: + file: Uploaded file from flask + """ self.file = File.File(file) self.zip = ZipFile(self.file.getPath()) def listFiles(self): + """ + List all files in the zip + + Returns: + array: Array of filenames + """ return [file.filename for file in self.zip.infolist()] def listAllImages(self, extensions=ALLOWED_IMAGE_FILES): + """ + List all image files + + Lists all image files within the zip archive based on the given extensions + + Parameters: + extensions: Array of allowed image extensions + + Returns: + array: Array of filenames matching the extension + """ return [file for file in self.listFiles() if allowed_file(file, extensions)] def hasImages(self, extensions=ALLOWED_IMAGE_FILES): + """ + Check for images in the zip file + + Parameters: + extensions: Array of allowed image extensions + + Returns: + boolean: True if zip has images + """ return len(self.listAllImages(extensions)) > 0 - def extractAll(self, path=None, members=None, pwd=None): - self.zip.extractall(path, members, pwd) + def extractAll(self, path=None, members=None): + """ + Extract all the given files + + Extractes all the given files and stores them as File objects. + Upon destruction of the array, files are getting removed from os. + + Parameters: + path: Path to store files + members: Files to extract + + Returns: + array: Array of extracted File objects + """ + self.zip.extractall(path, members) extractedFiles = {} for member in members: file = File.File() file.setPath(os.path.join(path, member)) extractedFiles[member] = file - return extractedFiles \ No newline at end of file + return extractedFiles diff --git a/autofocus/predict/app/models/__init__.py b/autofocus/predict/app/models/__init__.py index 7480d49..538554f 100644 --- a/autofocus/predict/app/models/__init__.py +++ b/autofocus/predict/app/models/__init__.py @@ -1,3 +1,3 @@ from .File import File -from .ZipArchive import ZipArchive from .Predictor import Predictor +from .ZipArchive import ZipArchive diff --git a/autofocus/predict/app/requests/PredictRequestValidator.py b/autofocus/predict/app/requests/PredictRequestValidator.py index 9ee8dc0..6b040f8 100644 --- a/autofocus/predict/app/requests/PredictRequestValidator.py +++ b/autofocus/predict/app/requests/PredictRequestValidator.py @@ -1,8 +1,21 @@ -from . import Validator, ALLOWED_IMAGE_FILES +from . import ALLOWED_IMAGE_FILES, Validator from ..utils import allowed_file + class PredictRequestValidator(Validator): + """ + Validate request for endpoint predict + """ + def validate(self): + """ + Validate the given request + + Check if the request has a file and the extension is an allowed image extension. + + Returns: + boolean: True if the request is valid + """ self.error = {} file = self.request.files.get('file', None) diff --git a/autofocus/predict/app/requests/PredictZipRequestValidator.py b/autofocus/predict/app/requests/PredictZipRequestValidator.py index 482d1af..816059e 100644 --- a/autofocus/predict/app/requests/PredictZipRequestValidator.py +++ b/autofocus/predict/app/requests/PredictZipRequestValidator.py @@ -1,8 +1,21 @@ -from . import Validator, ALLOWED_ZIP_FILES +from . import ALLOWED_ZIP_FILES, Validator from ..utils import allowed_file + class PredictZipRequestValidator(Validator): + """ + Validate request for endpoint predict_zip + """ + def validate(self): + """ + Validate the given request + + Check if the request has a file and the extension is ".zip". + + Returns: + boolean: True if the request is valid + """ self.error = {} file = self.request.files.get('file', None) diff --git a/autofocus/predict/app/requests/Validator.py b/autofocus/predict/app/requests/Validator.py index cfe7728..3c72c88 100644 --- a/autofocus/predict/app/requests/Validator.py +++ b/autofocus/predict/app/requests/Validator.py @@ -1,20 +1,54 @@ from abc import ABC, abstractmethod -from flask import jsonify, make_response, abort +from flask import abort, jsonify, make_response from flask_api import status + class Validator(ABC): + """ + Validate given request + + Validator validates a given request based upon the abstract method validate. + + Parameters: + request: Given request to validate + error: Dict of errors + """ + def __init__(self, request): + """ + Constructor of Validator + + Store the request and create an empty error Dict. + + Parameters: + request: Request to validate + """ self.request = request self.error = {} @abstractmethod def validate(self): + """ + Validate the given request + + Returns: + boolean: True if request is valid + """ pass def getError(self): + """ + Return the error dictionary + + Returns: + dict: The errors found during validation + """ return self.error def abort(self): + """ + Abort with errors + """ abort(make_response( jsonify( status=status.HTTP_400_BAD_REQUEST, diff --git a/autofocus/predict/app/requests/__init__.py b/autofocus/predict/app/requests/__init__.py index 98956df..3ff8975 100644 --- a/autofocus/predict/app/requests/__init__.py +++ b/autofocus/predict/app/requests/__init__.py @@ -1,7 +1,6 @@ -ALLOWED_IMAGE_FILES = set(["png", "jpg", "jpeg", "gif", "bmp"]) -ALLOWED_ZIP_FILES = {"zip"} - -from .Validator import Validator - from .PredictRequestValidator import PredictRequestValidator +from .Validator import Validator from .PredictZipRequestValidator import PredictZipRequestValidator + +ALLOWED_IMAGE_FILES = set(["png", "jpg", "jpeg", "gif", "bmp"]) +ALLOWED_ZIP_FILES = {"zip"} diff --git a/autofocus/predict/app/utils.py b/autofocus/predict/app/utils.py index a1e9fcb..3d21c53 100644 --- a/autofocus/predict/app/utils.py +++ b/autofocus/predict/app/utils.py @@ -1,5 +1,4 @@ from pathlib import Path -from zipfile import ZipFile def allowed_file(filename, allowed_extensions): From 400c24d8ed130fa05a1bb66399777f63a8c5c700 Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Wed, 16 Oct 2019 11:28:32 +0200 Subject: [PATCH 08/13] Fix imports - Remove imports from __init__ - Fix circle import from File and app --- autofocus/predict/app/app.py | 11 +++++++---- autofocus/predict/app/models/File.py | 13 ++++++++----- autofocus/predict/app/models/ZipArchive.py | 11 ++++++----- autofocus/predict/app/models/__init__.py | 3 --- .../predict/app/requests/PredictRequestValidator.py | 2 +- .../app/requests/PredictZipRequestValidator.py | 2 +- autofocus/predict/app/requests/Validator.py | 4 ++++ autofocus/predict/app/requests/__init__.py | 6 ------ 8 files changed, 27 insertions(+), 25 deletions(-) diff --git a/autofocus/predict/app/app.py b/autofocus/predict/app/app.py index 8280746..1415f58 100644 --- a/autofocus/predict/app/app.py +++ b/autofocus/predict/app/app.py @@ -2,8 +2,11 @@ from flask import Flask, jsonify, request -from .models import File, Predictor, ZipArchive -from .requests import PredictRequestValidator, PredictZipRequestValidator +from .models.File import File +from .models.Predictor import Predictor +from .models.ZipArchive import ZipArchive +from .requests.PredictRequestValidator import PredictRequestValidator +from .requests.PredictZipRequestValidator import PredictZipRequestValidator # We are going to upload the files to the server as part of the request, so set tmp folder here. UPLOAD_FOLDER = "/tmp/" @@ -22,7 +25,7 @@ def classify_single(): validator.abort() # Get File object - file = File(request.files["file"]) + file = File(request.files["file"], app.config["UPLOAD_FOLDER"]) # Predict probabilities app.logger.info("Classifying image %s" % (file.getPath())) @@ -44,7 +47,7 @@ def classify_zip(): if not validator.validate(): validator.abort() - file = ZipArchive(request.files["file"]) + file = ZipArchive(request.files["file"], app.config["UPLOAD_FOLDER"]) if not file.hasImages(): validator.error['file'] = "No image files detected in the zip file." validator.abort() diff --git a/autofocus/predict/app/models/File.py b/autofocus/predict/app/models/File.py index 70873b0..490c15c 100644 --- a/autofocus/predict/app/models/File.py +++ b/autofocus/predict/app/models/File.py @@ -1,6 +1,5 @@ import os from werkzeug import secure_filename -from ..app import app class File: @@ -12,7 +11,7 @@ class File: name: Secured filename (Can be empty) """ - def __init__(self, file=None): + def __init__(self, file=None, upload_path=None): """ Constructor of File @@ -20,9 +19,10 @@ def __init__(self, file=None): Parameters: file: Uploaded file object from flask + upload_path: The path to upload the file """ if file: - self.setFromUploadedFile(file) + self.setFromUploadedFile(file, upload_path) def __del__(self): """ @@ -32,15 +32,18 @@ def __del__(self): """ os.remove(self.path) - def setFromUploadedFile(self, file): + def setFromUploadedFile(self, file, upload_path=None): """ Save file from uploaded file Parameters: file: Uploaded file object from flask + upload_path: The path to upload the file """ self.name = secure_filename(file.filename) - self.path = os.path.join(app.config["UPLOAD_FOLDER"], self.name) + self.path = self.name + if upload_path: + self.path = os.path.join(upload_path, self.path) file.save(self.path) def setPath(self, path): diff --git a/autofocus/predict/app/models/ZipArchive.py b/autofocus/predict/app/models/ZipArchive.py index cb8abe2..89db1cb 100644 --- a/autofocus/predict/app/models/ZipArchive.py +++ b/autofocus/predict/app/models/ZipArchive.py @@ -1,7 +1,7 @@ import os from zipfile import ZipFile -from . import File -from ..requests import ALLOWED_IMAGE_FILES +from .File import File +from ..requests.Validator import ALLOWED_IMAGE_FILES from ..utils import allowed_file @@ -16,7 +16,7 @@ class ZipArchive: zip: Opened zip file """ - def __init__(self, file): + def __init__(self, file, upload_folder=None): """ Constructor of ZipFile @@ -24,8 +24,9 @@ def __init__(self, file): Parameters: file: Uploaded file from flask + upload_folder: The folder to save the zip file """ - self.file = File.File(file) + self.file = File(file, upload_folder) self.zip = ZipFile(self.file.getPath()) def listFiles(self): @@ -80,7 +81,7 @@ def extractAll(self, path=None, members=None): self.zip.extractall(path, members) extractedFiles = {} for member in members: - file = File.File() + file = File() file.setPath(os.path.join(path, member)) extractedFiles[member] = file return extractedFiles diff --git a/autofocus/predict/app/models/__init__.py b/autofocus/predict/app/models/__init__.py index 538554f..e69de29 100644 --- a/autofocus/predict/app/models/__init__.py +++ b/autofocus/predict/app/models/__init__.py @@ -1,3 +0,0 @@ -from .File import File -from .Predictor import Predictor -from .ZipArchive import ZipArchive diff --git a/autofocus/predict/app/requests/PredictRequestValidator.py b/autofocus/predict/app/requests/PredictRequestValidator.py index 6b040f8..7dd4af8 100644 --- a/autofocus/predict/app/requests/PredictRequestValidator.py +++ b/autofocus/predict/app/requests/PredictRequestValidator.py @@ -1,4 +1,4 @@ -from . import ALLOWED_IMAGE_FILES, Validator +from .Validator import ALLOWED_IMAGE_FILES, Validator from ..utils import allowed_file diff --git a/autofocus/predict/app/requests/PredictZipRequestValidator.py b/autofocus/predict/app/requests/PredictZipRequestValidator.py index 816059e..34d600f 100644 --- a/autofocus/predict/app/requests/PredictZipRequestValidator.py +++ b/autofocus/predict/app/requests/PredictZipRequestValidator.py @@ -1,4 +1,4 @@ -from . import ALLOWED_ZIP_FILES, Validator +from .Validator import ALLOWED_ZIP_FILES, Validator from ..utils import allowed_file diff --git a/autofocus/predict/app/requests/Validator.py b/autofocus/predict/app/requests/Validator.py index 3c72c88..4695d0d 100644 --- a/autofocus/predict/app/requests/Validator.py +++ b/autofocus/predict/app/requests/Validator.py @@ -3,6 +3,10 @@ from flask_api import status +ALLOWED_IMAGE_FILES = set(["png", "jpg", "jpeg", "gif", "bmp"]) +ALLOWED_ZIP_FILES = {"zip"} + + class Validator(ABC): """ Validate given request diff --git a/autofocus/predict/app/requests/__init__.py b/autofocus/predict/app/requests/__init__.py index 3ff8975..e69de29 100644 --- a/autofocus/predict/app/requests/__init__.py +++ b/autofocus/predict/app/requests/__init__.py @@ -1,6 +0,0 @@ -from .PredictRequestValidator import PredictRequestValidator -from .Validator import Validator -from .PredictZipRequestValidator import PredictZipRequestValidator - -ALLOWED_IMAGE_FILES = set(["png", "jpg", "jpeg", "gif", "bmp"]) -ALLOWED_ZIP_FILES = {"zip"} From 74ed97753cd99458b4a3279c7092338922d59844 Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Wed, 16 Oct 2019 11:41:54 +0200 Subject: [PATCH 09/13] Refactor for travis ci imports and docstrings --- autofocus/predict/app/app.py | 2 +- autofocus/predict/app/models/File.py | 1 + autofocus/predict/app/models/Predictor.py | 3 +-- autofocus/predict/app/models/ZipArchive.py | 9 +++++---- .../app/requests/PredictRequestValidator.py | 12 +++++------ .../requests/PredictZipRequestValidator.py | 12 +++++------ autofocus/predict/app/requests/Validator.py | 20 +++++++++---------- 7 files changed, 29 insertions(+), 30 deletions(-) diff --git a/autofocus/predict/app/app.py b/autofocus/predict/app/app.py index 1415f58..d888cb3 100644 --- a/autofocus/predict/app/app.py +++ b/autofocus/predict/app/app.py @@ -49,7 +49,7 @@ def classify_zip(): file = ZipArchive(request.files["file"], app.config["UPLOAD_FOLDER"]) if not file.hasImages(): - validator.error['file'] = "No image files detected in the zip file." + validator.error["file"] = "No image files detected in the zip file." validator.abort() # Extract files diff --git a/autofocus/predict/app/models/File.py b/autofocus/predict/app/models/File.py index 490c15c..62f6446 100644 --- a/autofocus/predict/app/models/File.py +++ b/autofocus/predict/app/models/File.py @@ -1,4 +1,5 @@ import os + from werkzeug import secure_filename diff --git a/autofocus/predict/app/models/Predictor.py b/autofocus/predict/app/models/Predictor.py index e5d4a42..99a0eef 100644 --- a/autofocus/predict/app/models/Predictor.py +++ b/autofocus/predict/app/models/Predictor.py @@ -35,7 +35,7 @@ def predict_multiple(self, files): Parameters: files: Dict with File objects of image file - + Returns: dict: Dictionary of probabilities for each file in files """ @@ -45,7 +45,6 @@ def predict_multiple(self, files): predictions[key] = self.getProbabilities() return predictions - def getProbabilities(self): """ Return formated Probabilities diff --git a/autofocus/predict/app/models/ZipArchive.py b/autofocus/predict/app/models/ZipArchive.py index 89db1cb..9e85be1 100644 --- a/autofocus/predict/app/models/ZipArchive.py +++ b/autofocus/predict/app/models/ZipArchive.py @@ -1,5 +1,6 @@ import os from zipfile import ZipFile + from .File import File from ..requests.Validator import ALLOWED_IMAGE_FILES from ..utils import allowed_file @@ -46,7 +47,7 @@ def listAllImages(self, extensions=ALLOWED_IMAGE_FILES): Parameters: extensions: Array of allowed image extensions - + Returns: array: Array of filenames matching the extension """ @@ -58,7 +59,7 @@ def hasImages(self, extensions=ALLOWED_IMAGE_FILES): Parameters: extensions: Array of allowed image extensions - + Returns: boolean: True if zip has images """ @@ -74,9 +75,9 @@ def extractAll(self, path=None, members=None): Parameters: path: Path to store files members: Files to extract - + Returns: - array: Array of extracted File objects + array: Array of extracted File objects """ self.zip.extractall(path, members) extractedFiles = {} diff --git a/autofocus/predict/app/requests/PredictRequestValidator.py b/autofocus/predict/app/requests/PredictRequestValidator.py index 7dd4af8..9fdfd97 100644 --- a/autofocus/predict/app/requests/PredictRequestValidator.py +++ b/autofocus/predict/app/requests/PredictRequestValidator.py @@ -3,9 +3,7 @@ class PredictRequestValidator(Validator): - """ - Validate request for endpoint predict - """ + """Validate request for endpoint predict""" def validate(self): """ @@ -18,11 +16,13 @@ def validate(self): """ self.error = {} - file = self.request.files.get('file', None) + file = self.request.files.get("file", None) if not file: - self.error['file'] = "No file given." + self.error["file"] = "No file given." elif not allowed_file(file.filename, ALLOWED_IMAGE_FILES): - self.error['file'] = "File type not allowed. File must be of type {allowed}".format( + self.error[ + "file" + ] = "File type not allowed. File must be of type {allowed}".format( allowed=ALLOWED_IMAGE_FILES ) diff --git a/autofocus/predict/app/requests/PredictZipRequestValidator.py b/autofocus/predict/app/requests/PredictZipRequestValidator.py index 34d600f..a913f8e 100644 --- a/autofocus/predict/app/requests/PredictZipRequestValidator.py +++ b/autofocus/predict/app/requests/PredictZipRequestValidator.py @@ -3,9 +3,7 @@ class PredictZipRequestValidator(Validator): - """ - Validate request for endpoint predict_zip - """ + """Validate request for endpoint predict_zip""" def validate(self): """ @@ -18,11 +16,13 @@ def validate(self): """ self.error = {} - file = self.request.files.get('file', None) + file = self.request.files.get("file", None) if not file: - self.error['file'] = "No file given." + self.error["file"] = "No file given." elif not allowed_file(file.filename, ALLOWED_ZIP_FILES): - self.error['file'] = "File type not allowed. File must be of type {allowed}".format( + self.error[ + "file" + ] = "File type not allowed. File must be of type {allowed}".format( allowed=ALLOWED_ZIP_FILES ) diff --git a/autofocus/predict/app/requests/Validator.py b/autofocus/predict/app/requests/Validator.py index 4695d0d..0381246 100644 --- a/autofocus/predict/app/requests/Validator.py +++ b/autofocus/predict/app/requests/Validator.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod + from flask import abort, jsonify, make_response from flask_api import status @@ -48,15 +49,12 @@ def getError(self): dict: The errors found during validation """ return self.error - + def abort(self): - """ - Abort with errors - """ - abort(make_response( - jsonify( - status=status.HTTP_400_BAD_REQUEST, - error=self.getError() - ), - status.HTTP_400_BAD_REQUEST - )) + """Abort with errors""" + abort( + make_response( + jsonify(status=status.HTTP_400_BAD_REQUEST, error=self.getError()), + status.HTTP_400_BAD_REQUEST, + ) + ) From 251d239fecf44d59ae695d16ecbc0ce71b5c4ebe Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Tue, 5 Nov 2019 13:03:59 +0100 Subject: [PATCH 10/13] Remove object oriented programming within validation --- autofocus/predict/app/app.py | 20 +++--- autofocus/predict/app/models/ZipArchive.py | 3 +- .../app/requests/PredictRequestValidator.py | 29 --------- .../requests/PredictZipRequestValidator.py | 29 --------- autofocus/predict/app/requests/Validator.py | 63 ------------------- autofocus/predict/app/utils.py | 14 ----- .../app/{requests => validation}/__init__.py | 0 autofocus/predict/app/validation/predict.py | 23 +++++++ .../predict/app/validation/predict_zip.py | 25 ++++++++ .../predict/app/validation/validation.py | 33 ++++++++++ 10 files changed, 92 insertions(+), 147 deletions(-) delete mode 100644 autofocus/predict/app/requests/PredictRequestValidator.py delete mode 100644 autofocus/predict/app/requests/PredictZipRequestValidator.py delete mode 100644 autofocus/predict/app/requests/Validator.py delete mode 100644 autofocus/predict/app/utils.py rename autofocus/predict/app/{requests => validation}/__init__.py (100%) create mode 100644 autofocus/predict/app/validation/predict.py create mode 100644 autofocus/predict/app/validation/predict_zip.py create mode 100644 autofocus/predict/app/validation/validation.py diff --git a/autofocus/predict/app/app.py b/autofocus/predict/app/app.py index d888cb3..be27d3e 100644 --- a/autofocus/predict/app/app.py +++ b/autofocus/predict/app/app.py @@ -5,8 +5,10 @@ from .models.File import File from .models.Predictor import Predictor from .models.ZipArchive import ZipArchive -from .requests.PredictRequestValidator import PredictRequestValidator -from .requests.PredictZipRequestValidator import PredictZipRequestValidator +from .validation.predict import validate_predict_request +from .validation.predict_zip import validate_predict_zip_request +from .validation.validation import abort_with_errors + # We are going to upload the files to the server as part of the request, so set tmp folder here. UPLOAD_FOLDER = "/tmp/" @@ -20,9 +22,7 @@ def classify_single(): """Classify a single image""" # Validate request - validator = PredictRequestValidator(request) - if not validator.validate(): - validator.abort() + validate_predict_request(request) # Get File object file = File(request.files["file"], app.config["UPLOAD_FOLDER"]) @@ -43,14 +43,14 @@ def classify_single(): def classify_zip(): """Classify all images from a zip file""" # Validate request - validator = PredictZipRequestValidator(request) - if not validator.validate(): - validator.abort() + validate_predict_zip_request(request) file = ZipArchive(request.files["file"], app.config["UPLOAD_FOLDER"]) if not file.hasImages(): - validator.error["file"] = "No image files detected in the zip file." - validator.abort() + error = { + "file": "No image files detected in the zip file." + } + abort_with_errors(error) # Extract files files = file.extractAll(app.config["UPLOAD_FOLDER"], file.listAllImages()) diff --git a/autofocus/predict/app/models/ZipArchive.py b/autofocus/predict/app/models/ZipArchive.py index 9e85be1..bb84649 100644 --- a/autofocus/predict/app/models/ZipArchive.py +++ b/autofocus/predict/app/models/ZipArchive.py @@ -2,8 +2,7 @@ from zipfile import ZipFile from .File import File -from ..requests.Validator import ALLOWED_IMAGE_FILES -from ..utils import allowed_file +from ..validation.validation import allowed_file, ALLOWED_IMAGE_FILES class ZipArchive: diff --git a/autofocus/predict/app/requests/PredictRequestValidator.py b/autofocus/predict/app/requests/PredictRequestValidator.py deleted file mode 100644 index 9fdfd97..0000000 --- a/autofocus/predict/app/requests/PredictRequestValidator.py +++ /dev/null @@ -1,29 +0,0 @@ -from .Validator import ALLOWED_IMAGE_FILES, Validator -from ..utils import allowed_file - - -class PredictRequestValidator(Validator): - """Validate request for endpoint predict""" - - def validate(self): - """ - Validate the given request - - Check if the request has a file and the extension is an allowed image extension. - - Returns: - boolean: True if the request is valid - """ - self.error = {} - - file = self.request.files.get("file", None) - if not file: - self.error["file"] = "No file given." - elif not allowed_file(file.filename, ALLOWED_IMAGE_FILES): - self.error[ - "file" - ] = "File type not allowed. File must be of type {allowed}".format( - allowed=ALLOWED_IMAGE_FILES - ) - - return not (self.error) diff --git a/autofocus/predict/app/requests/PredictZipRequestValidator.py b/autofocus/predict/app/requests/PredictZipRequestValidator.py deleted file mode 100644 index a913f8e..0000000 --- a/autofocus/predict/app/requests/PredictZipRequestValidator.py +++ /dev/null @@ -1,29 +0,0 @@ -from .Validator import ALLOWED_ZIP_FILES, Validator -from ..utils import allowed_file - - -class PredictZipRequestValidator(Validator): - """Validate request for endpoint predict_zip""" - - def validate(self): - """ - Validate the given request - - Check if the request has a file and the extension is ".zip". - - Returns: - boolean: True if the request is valid - """ - self.error = {} - - file = self.request.files.get("file", None) - if not file: - self.error["file"] = "No file given." - elif not allowed_file(file.filename, ALLOWED_ZIP_FILES): - self.error[ - "file" - ] = "File type not allowed. File must be of type {allowed}".format( - allowed=ALLOWED_ZIP_FILES - ) - - return not (self.error) diff --git a/autofocus/predict/app/requests/Validator.py b/autofocus/predict/app/requests/Validator.py deleted file mode 100644 index 6155900..0000000 --- a/autofocus/predict/app/requests/Validator.py +++ /dev/null @@ -1,63 +0,0 @@ -from abc import ABC, abstractmethod -import mimetypes - -from flask import abort, jsonify, make_response -from flask_api import status - - -ALLOWED_IMAGE_FILES = set( - k for k, v in mimetypes.types_map.items() if v.startswith("image/") -) -ALLOWED_ZIP_FILES = {".zip"} - - -class Validator(ABC): - """ - Validate given request - - Validator validates a given request based upon the abstract method validate. - - Parameters: - request: Given request to validate - error: Dict of errors - """ - - def __init__(self, request): - """ - Constructor of Validator - - Store the request and create an empty error Dict. - - Parameters: - request: Request to validate - """ - self.request = request - self.error = {} - - @abstractmethod - def validate(self): - """ - Validate the given request - - Returns: - boolean: True if request is valid - """ - pass - - def getError(self): - """ - Return the error dictionary - - Returns: - dict: The errors found during validation - """ - return self.error - - def abort(self): - """Abort with errors""" - abort( - make_response( - jsonify(status=status.HTTP_400_BAD_REQUEST, error=self.getError()), - status.HTTP_400_BAD_REQUEST, - ) - ) diff --git a/autofocus/predict/app/utils.py b/autofocus/predict/app/utils.py deleted file mode 100644 index 14d4b48..0000000 --- a/autofocus/predict/app/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -from pathlib import Path - - -def allowed_file(filename, allowed_extensions): - """ - Check for whether a filename is in the ALLOWED_EXTENSIONS - Args: - filename (str): filename to check - - Returns: - bool: whether the filename is in allowed extensions - - """ - return Path(filename).suffix.lower() in allowed_extensions diff --git a/autofocus/predict/app/requests/__init__.py b/autofocus/predict/app/validation/__init__.py similarity index 100% rename from autofocus/predict/app/requests/__init__.py rename to autofocus/predict/app/validation/__init__.py diff --git a/autofocus/predict/app/validation/predict.py b/autofocus/predict/app/validation/predict.py new file mode 100644 index 0000000..956c592 --- /dev/null +++ b/autofocus/predict/app/validation/predict.py @@ -0,0 +1,23 @@ +from .validation import abort_with_errors, ALLOWED_IMAGE_FILES, allowed_file + + +def validate_predict_request(request): + """ + Validate the given request + + Check if the request has a file and the extension is an allowed image extension. + """ + error = {} + + file = request.files.get("file", None) + if not file: + error["file"] = "No file given." + elif not allowed_file(file.filename, ALLOWED_IMAGE_FILES): + error[ + "file" + ] = "File type not allowed. File must be of type {allowed}".format( + allowed=ALLOWED_IMAGE_FILES + ) + + if (error): + abort_with_errors(error) diff --git a/autofocus/predict/app/validation/predict_zip.py b/autofocus/predict/app/validation/predict_zip.py new file mode 100644 index 0000000..a8521b0 --- /dev/null +++ b/autofocus/predict/app/validation/predict_zip.py @@ -0,0 +1,25 @@ +from .validation import abort_with_errors, ALLOWED_ZIP_FILES, allowed_file + +def validate_predict_zip_request(request): + """ + Validate the given request + + Check if the request has a file and the extension is ".zip". + + Returns: + error: Set of errors + """ + error = {} + + file = request.files.get("file", None) + if not file: + error["file"] = "No file given." + elif not allowed_file(file.filename, ALLOWED_ZIP_FILES): + error[ + "file" + ] = "File type not allowed. File must be of type {allowed}".format( + allowed=ALLOWED_ZIP_FILES + ) + + if error: + abort_with_errors(error) diff --git a/autofocus/predict/app/validation/validation.py b/autofocus/predict/app/validation/validation.py new file mode 100644 index 0000000..d4a0464 --- /dev/null +++ b/autofocus/predict/app/validation/validation.py @@ -0,0 +1,33 @@ +import mimetypes +from pathlib import Path + +from flask import abort, jsonify, make_response +from flask_api import status + + +ALLOWED_IMAGE_FILES = set( + k for k, v in mimetypes.types_map.items() if v.startswith("image/") +) +ALLOWED_ZIP_FILES = {".zip"} + + +def abort_with_errors(error): + """Abort with errors""" + abort( + make_response( + jsonify(status=status.HTTP_400_BAD_REQUEST, error=error), + status.HTTP_400_BAD_REQUEST, + ) + ) + +def allowed_file(filename, allowed_extensions): + """ + Check for whether a filename is in the ALLOWED_EXTENSIONS + Args: + filename (str): filename to check + + Returns: + bool: whether the filename is in allowed extensions + + """ + return Path(filename).suffix.lower() in allowed_extensions From 4d0a3dde6bc1e5000b6ea39827203ea31342394c Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Tue, 5 Nov 2019 13:21:19 +0100 Subject: [PATCH 11/13] Remove object oriented programming from prediction --- autofocus/predict/app/app.py | 15 +---- autofocus/predict/app/models/Predictor.py | 55 ------------------- .../predict/app/prediction/prediction.py | 46 ++++++++++++++++ 3 files changed, 49 insertions(+), 67 deletions(-) delete mode 100644 autofocus/predict/app/models/Predictor.py create mode 100644 autofocus/predict/app/prediction/prediction.py diff --git a/autofocus/predict/app/app.py b/autofocus/predict/app/app.py index be27d3e..643f298 100644 --- a/autofocus/predict/app/app.py +++ b/autofocus/predict/app/app.py @@ -3,11 +3,11 @@ from flask import Flask, jsonify, request from .models.File import File -from .models.Predictor import Predictor from .models.ZipArchive import ZipArchive from .validation.predict import validate_predict_request from .validation.predict_zip import validate_predict_zip_request from .validation.validation import abort_with_errors +from .prediction.prediction import predict, predict_multiple # We are going to upload the files to the server as part of the request, so set tmp folder here. @@ -27,16 +27,8 @@ def classify_single(): # Get File object file = File(request.files["file"], app.config["UPLOAD_FOLDER"]) - # Predict probabilities - app.logger.info("Classifying image %s" % (file.getPath())) - t = time.time() - predictor = Predictor() - predictor.predict(file) - dt = time.time() - t - app.logger.info("Execution time: %0.2f" % (dt * 1000.0)) - # Return ziped probabilities - return jsonify(predictor.getProbabilities()) + return jsonify(predict(file)) @app.route("/predict_zip", methods=["POST"]) @@ -56,8 +48,7 @@ def classify_zip(): files = file.extractAll(app.config["UPLOAD_FOLDER"], file.listAllImages()) # Make prediction - predictor = Predictor() - return jsonify(predictor.predict_multiple(files)) + return jsonify(predict_multiple(files)) @app.route("/hello") diff --git a/autofocus/predict/app/models/Predictor.py b/autofocus/predict/app/models/Predictor.py deleted file mode 100644 index 99a0eef..0000000 --- a/autofocus/predict/app/models/Predictor.py +++ /dev/null @@ -1,55 +0,0 @@ -from pathlib import Path - -from fastai.vision import load_learner, open_image - - -MODEL_DIR = Path(__file__).resolve().parents[2] / "models" -MODEL_NAME = "multilabel_model_20190407.pkl" -model = load_learner(MODEL_DIR, MODEL_NAME) -CLASSES = model.data.classes - - -class Predictor: - """ - Predicts probabilities with the model based on given files - - Parameters: - probabilities: Array of probabilities calculated in predict - """ - - def predict(self, file): - """ - Predict probabilities of single file - - Parameters: - file: File object of image file - """ - image = open_image(file.getPath()) - # Get the predictions (output of the softmax) for this image - pred_classes, preds, probs = model.predict(image) - self.probabilities = [prob.item() for prob in probs] - - def predict_multiple(self, files): - """ - Predict probabilities of multiple files - - Parameters: - files: Dict with File objects of image file - - Returns: - dict: Dictionary of probabilities for each file in files - """ - predictions = {} - for key in files: - self.predict(files[key]) - predictions[key] = self.getProbabilities() - return predictions - - def getProbabilities(self): - """ - Return formated Probabilities - - Returns: - dict: A dictionary of classes to probabilities - """ - return dict(zip(CLASSES, self.probabilities)) diff --git a/autofocus/predict/app/prediction/prediction.py b/autofocus/predict/app/prediction/prediction.py new file mode 100644 index 0000000..e9db3fc --- /dev/null +++ b/autofocus/predict/app/prediction/prediction.py @@ -0,0 +1,46 @@ +from pathlib import Path + +from fastai.vision import load_learner, open_image + + +MODEL_DIR = Path(__file__).resolve().parents[2] / "models" +MODEL_NAME = "multilabel_model_20190407.pkl" +model = load_learner(MODEL_DIR, MODEL_NAME) +CLASSES = model.data.classes + + +def predict_multiple(files): + """ + Predict probabilities of multiple files + + Parameters: + files: Dict with File objects of image file + + Returns: + dict: Dictionary of probabilities for each file in files + """ + predictions = {} + for key in files: + predictions[key] = predict(files[key]) + return predictions + +def predict(file): + """ + Predict probabilities of single file + + Parameters: + file: File object of image file + """ + image = open_image(file.getPath()) + # Get the predictions (output of the softmax) for this image + pred_classes, preds, probs = model.predict(image) + return getProbabilities([prob.item() for prob in probs]) + +def getProbabilities(probabilities): + """ + Return formated Probabilities + + Returns: + dict: A dictionary of classes to probabilities + """ + return dict(zip(CLASSES, probabilities)) From 2c7f764e72e8cbdb662df71cd3e31d106e214045 Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Tue, 5 Nov 2019 13:44:00 +0100 Subject: [PATCH 12/13] Move routes to folder --- autofocus/predict/app/app.py | 56 ++------------------- autofocus/predict/app/models/File.py | 5 +- autofocus/predict/app/models/ZipArchive.py | 8 +-- autofocus/predict/app/routes/__init__.py | 0 autofocus/predict/app/routes/predict.py | 25 +++++++++ autofocus/predict/app/routes/predict_zip.py | 32 ++++++++++++ 6 files changed, 70 insertions(+), 56 deletions(-) create mode 100644 autofocus/predict/app/routes/__init__.py create mode 100644 autofocus/predict/app/routes/predict.py create mode 100644 autofocus/predict/app/routes/predict_zip.py diff --git a/autofocus/predict/app/app.py b/autofocus/predict/app/app.py index 643f298..108aa8d 100644 --- a/autofocus/predict/app/app.py +++ b/autofocus/predict/app/app.py @@ -1,60 +1,14 @@ -import time +from flask import Flask -from flask import Flask, jsonify, request +from .routes.predict import predict_route +from .routes.predict_zip import predict_zip_route -from .models.File import File -from .models.ZipArchive import ZipArchive -from .validation.predict import validate_predict_request -from .validation.predict_zip import validate_predict_zip_request -from .validation.validation import abort_with_errors -from .prediction.prediction import predict, predict_multiple - - -# We are going to upload the files to the server as part of the request, so set tmp folder here. -UPLOAD_FOLDER = "/tmp/" app = Flask(__name__) app.config.from_object(__name__) -app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER - - -@app.route("/predict", methods=["POST"]) -def classify_single(): - """Classify a single image""" - # Validate request - validate_predict_request(request) - - # Get File object - file = File(request.files["file"], app.config["UPLOAD_FOLDER"]) - - # Return ziped probabilities - return jsonify(predict(file)) - - -@app.route("/predict_zip", methods=["POST"]) -def classify_zip(): - """Classify all images from a zip file""" - # Validate request - validate_predict_zip_request(request) - - file = ZipArchive(request.files["file"], app.config["UPLOAD_FOLDER"]) - if not file.hasImages(): - error = { - "file": "No image files detected in the zip file." - } - abort_with_errors(error) - - # Extract files - files = file.extractAll(app.config["UPLOAD_FOLDER"], file.listAllImages()) - - # Make prediction - return jsonify(predict_multiple(files)) - -@app.route("/hello") -def hello(): - """Just a test endpoint to make sure server is running""" - return "Hey there!\n" +app.register_blueprint(predict_route) +app.register_blueprint(predict_zip_route) if __name__ == "__main__": diff --git a/autofocus/predict/app/models/File.py b/autofocus/predict/app/models/File.py index 62f6446..4590d7d 100644 --- a/autofocus/predict/app/models/File.py +++ b/autofocus/predict/app/models/File.py @@ -3,6 +3,8 @@ from werkzeug import secure_filename +UPLOAD_FOLDER = "/tmp/" + class File: """ Store a file and remove it upon destruction @@ -12,7 +14,7 @@ class File: name: Secured filename (Can be empty) """ - def __init__(self, file=None, upload_path=None): + def __init__(self, file=None, upload_path=UPLOAD_FOLDER): """ Constructor of File @@ -22,6 +24,7 @@ def __init__(self, file=None, upload_path=None): file: Uploaded file object from flask upload_path: The path to upload the file """ + self.upload_folder = upload_path if file: self.setFromUploadedFile(file, upload_path) diff --git a/autofocus/predict/app/models/ZipArchive.py b/autofocus/predict/app/models/ZipArchive.py index bb84649..6fc6ae8 100644 --- a/autofocus/predict/app/models/ZipArchive.py +++ b/autofocus/predict/app/models/ZipArchive.py @@ -1,7 +1,7 @@ import os from zipfile import ZipFile -from .File import File +from .File import File, UPLOAD_FOLDER from ..validation.validation import allowed_file, ALLOWED_IMAGE_FILES @@ -16,7 +16,7 @@ class ZipArchive: zip: Opened zip file """ - def __init__(self, file, upload_folder=None): + def __init__(self, file): """ Constructor of ZipFile @@ -26,7 +26,7 @@ def __init__(self, file, upload_folder=None): file: Uploaded file from flask upload_folder: The folder to save the zip file """ - self.file = File(file, upload_folder) + self.file = File(file) self.zip = ZipFile(self.file.getPath()) def listFiles(self): @@ -64,7 +64,7 @@ def hasImages(self, extensions=ALLOWED_IMAGE_FILES): """ return len(self.listAllImages(extensions)) > 0 - def extractAll(self, path=None, members=None): + def extractAll(self, path=UPLOAD_FOLDER, members=None): """ Extract all the given files diff --git a/autofocus/predict/app/routes/__init__.py b/autofocus/predict/app/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/autofocus/predict/app/routes/predict.py b/autofocus/predict/app/routes/predict.py new file mode 100644 index 0000000..26268e1 --- /dev/null +++ b/autofocus/predict/app/routes/predict.py @@ -0,0 +1,25 @@ +import time + +from flask import Blueprint, jsonify, request + +from ..models.File import File +from ..models.ZipArchive import ZipArchive +from ..validation.predict import validate_predict_request +from ..validation.predict_zip import validate_predict_zip_request +from ..validation.validation import abort_with_errors +from ..prediction.prediction import predict, predict_multiple + + +predict_route = Blueprint("predict", __name__) + +@predict_route.route("/predict", methods=["POST"]) +def classify_single(): + """Classify a single image""" + # Validate request + validate_predict_request(request) + + # Get File object + file = File(request.files["file"]) + + # Return ziped probabilities + return jsonify(predict(file)) diff --git a/autofocus/predict/app/routes/predict_zip.py b/autofocus/predict/app/routes/predict_zip.py new file mode 100644 index 0000000..71cfe3c --- /dev/null +++ b/autofocus/predict/app/routes/predict_zip.py @@ -0,0 +1,32 @@ +import time + +from flask import Blueprint, jsonify, request + +from ..models.File import File +from ..models.ZipArchive import ZipArchive +from ..validation.predict import validate_predict_request +from ..validation.predict_zip import validate_predict_zip_request +from ..validation.validation import abort_with_errors +from ..prediction.prediction import predict, predict_multiple + + +predict_zip_route = Blueprint("predict_zip", __name__) + +@predict_zip_route.route("/predict_zip", methods=["POST"]) +def classify_zip(): + """Classify all images from a zip file""" + # Validate request + validate_predict_zip_request(request) + + file = ZipArchive(request.files["file"]) + if not file.hasImages(): + error = { + "file": "No image files detected in the zip file." + } + abort_with_errors(error) + + # Extract files + files = file.extractAll(members=file.listAllImages()) + + # Make prediction + return jsonify(predict_multiple(files)) From 8b6ecc1925541ba824c50107846d2699ac2e7f13 Mon Sep 17 00:00:00 2001 From: Patrick Kuehn Date: Tue, 5 Nov 2019 13:50:47 +0100 Subject: [PATCH 13/13] Black code formating - Rename File to temporary file --- .../{models/File.py => filesystem/TemporaryFile.py} | 3 ++- .../app/{models => filesystem}/ZipArchive.py | 6 +++--- .../predict/app/{models => filesystem}/__init__.py | 0 autofocus/predict/app/prediction/prediction.py | 2 ++ autofocus/predict/app/routes/predict.py | 12 ++++-------- autofocus/predict/app/routes/predict_zip.py | 13 ++++--------- autofocus/predict/app/validation/predict.py | 8 +++----- autofocus/predict/app/validation/predict_zip.py | 7 +++---- autofocus/predict/app/validation/validation.py | 1 + 9 files changed, 22 insertions(+), 30 deletions(-) rename autofocus/predict/app/{models/File.py => filesystem/TemporaryFile.py} (98%) rename autofocus/predict/app/{models => filesystem}/ZipArchive.py (94%) rename autofocus/predict/app/{models => filesystem}/__init__.py (100%) diff --git a/autofocus/predict/app/models/File.py b/autofocus/predict/app/filesystem/TemporaryFile.py similarity index 98% rename from autofocus/predict/app/models/File.py rename to autofocus/predict/app/filesystem/TemporaryFile.py index 4590d7d..08a8bf6 100644 --- a/autofocus/predict/app/models/File.py +++ b/autofocus/predict/app/filesystem/TemporaryFile.py @@ -5,7 +5,8 @@ UPLOAD_FOLDER = "/tmp/" -class File: + +class TemporaryFile: """ Store a file and remove it upon destruction diff --git a/autofocus/predict/app/models/ZipArchive.py b/autofocus/predict/app/filesystem/ZipArchive.py similarity index 94% rename from autofocus/predict/app/models/ZipArchive.py rename to autofocus/predict/app/filesystem/ZipArchive.py index 6fc6ae8..1d735b7 100644 --- a/autofocus/predict/app/models/ZipArchive.py +++ b/autofocus/predict/app/filesystem/ZipArchive.py @@ -1,7 +1,7 @@ import os from zipfile import ZipFile -from .File import File, UPLOAD_FOLDER +from .TemporaryFile import TemporaryFile, UPLOAD_FOLDER from ..validation.validation import allowed_file, ALLOWED_IMAGE_FILES @@ -26,7 +26,7 @@ def __init__(self, file): file: Uploaded file from flask upload_folder: The folder to save the zip file """ - self.file = File(file) + self.file = TemporaryFile(file) self.zip = ZipFile(self.file.getPath()) def listFiles(self): @@ -81,7 +81,7 @@ def extractAll(self, path=UPLOAD_FOLDER, members=None): self.zip.extractall(path, members) extractedFiles = {} for member in members: - file = File() + file = TemporaryFile() file.setPath(os.path.join(path, member)) extractedFiles[member] = file return extractedFiles diff --git a/autofocus/predict/app/models/__init__.py b/autofocus/predict/app/filesystem/__init__.py similarity index 100% rename from autofocus/predict/app/models/__init__.py rename to autofocus/predict/app/filesystem/__init__.py diff --git a/autofocus/predict/app/prediction/prediction.py b/autofocus/predict/app/prediction/prediction.py index e9db3fc..ae5bb47 100644 --- a/autofocus/predict/app/prediction/prediction.py +++ b/autofocus/predict/app/prediction/prediction.py @@ -24,6 +24,7 @@ def predict_multiple(files): predictions[key] = predict(files[key]) return predictions + def predict(file): """ Predict probabilities of single file @@ -36,6 +37,7 @@ def predict(file): pred_classes, preds, probs = model.predict(image) return getProbabilities([prob.item() for prob in probs]) + def getProbabilities(probabilities): """ Return formated Probabilities diff --git a/autofocus/predict/app/routes/predict.py b/autofocus/predict/app/routes/predict.py index 26268e1..af9faff 100644 --- a/autofocus/predict/app/routes/predict.py +++ b/autofocus/predict/app/routes/predict.py @@ -1,17 +1,13 @@ -import time - from flask import Blueprint, jsonify, request -from ..models.File import File -from ..models.ZipArchive import ZipArchive +from ..filesystem.TemporaryFile import TemporaryFile +from ..prediction.prediction import predict from ..validation.predict import validate_predict_request -from ..validation.predict_zip import validate_predict_zip_request -from ..validation.validation import abort_with_errors -from ..prediction.prediction import predict, predict_multiple predict_route = Blueprint("predict", __name__) + @predict_route.route("/predict", methods=["POST"]) def classify_single(): """Classify a single image""" @@ -19,7 +15,7 @@ def classify_single(): validate_predict_request(request) # Get File object - file = File(request.files["file"]) + file = TemporaryFile(request.files["file"]) # Return ziped probabilities return jsonify(predict(file)) diff --git a/autofocus/predict/app/routes/predict_zip.py b/autofocus/predict/app/routes/predict_zip.py index 71cfe3c..2619f59 100644 --- a/autofocus/predict/app/routes/predict_zip.py +++ b/autofocus/predict/app/routes/predict_zip.py @@ -1,17 +1,14 @@ -import time - from flask import Blueprint, jsonify, request -from ..models.File import File -from ..models.ZipArchive import ZipArchive -from ..validation.predict import validate_predict_request +from ..filesystem.ZipArchive import ZipArchive +from ..prediction.prediction import predict_multiple from ..validation.predict_zip import validate_predict_zip_request from ..validation.validation import abort_with_errors -from ..prediction.prediction import predict, predict_multiple predict_zip_route = Blueprint("predict_zip", __name__) + @predict_zip_route.route("/predict_zip", methods=["POST"]) def classify_zip(): """Classify all images from a zip file""" @@ -20,9 +17,7 @@ def classify_zip(): file = ZipArchive(request.files["file"]) if not file.hasImages(): - error = { - "file": "No image files detected in the zip file." - } + error = {"file": "No image files detected in the zip file."} abort_with_errors(error) # Extract files diff --git a/autofocus/predict/app/validation/predict.py b/autofocus/predict/app/validation/predict.py index 956c592..7ae63f3 100644 --- a/autofocus/predict/app/validation/predict.py +++ b/autofocus/predict/app/validation/predict.py @@ -1,4 +1,4 @@ -from .validation import abort_with_errors, ALLOWED_IMAGE_FILES, allowed_file +from .validation import abort_with_errors, allowed_file, ALLOWED_IMAGE_FILES def validate_predict_request(request): @@ -13,11 +13,9 @@ def validate_predict_request(request): if not file: error["file"] = "No file given." elif not allowed_file(file.filename, ALLOWED_IMAGE_FILES): - error[ - "file" - ] = "File type not allowed. File must be of type {allowed}".format( + error["file"] = "File type not allowed. File must be of type {allowed}".format( allowed=ALLOWED_IMAGE_FILES ) - if (error): + if error: abort_with_errors(error) diff --git a/autofocus/predict/app/validation/predict_zip.py b/autofocus/predict/app/validation/predict_zip.py index a8521b0..2ab89a3 100644 --- a/autofocus/predict/app/validation/predict_zip.py +++ b/autofocus/predict/app/validation/predict_zip.py @@ -1,4 +1,5 @@ -from .validation import abort_with_errors, ALLOWED_ZIP_FILES, allowed_file +from .validation import abort_with_errors, allowed_file, ALLOWED_ZIP_FILES + def validate_predict_zip_request(request): """ @@ -15,9 +16,7 @@ def validate_predict_zip_request(request): if not file: error["file"] = "No file given." elif not allowed_file(file.filename, ALLOWED_ZIP_FILES): - error[ - "file" - ] = "File type not allowed. File must be of type {allowed}".format( + error["file"] = "File type not allowed. File must be of type {allowed}".format( allowed=ALLOWED_ZIP_FILES ) diff --git a/autofocus/predict/app/validation/validation.py b/autofocus/predict/app/validation/validation.py index d4a0464..d97e4e2 100644 --- a/autofocus/predict/app/validation/validation.py +++ b/autofocus/predict/app/validation/validation.py @@ -20,6 +20,7 @@ def abort_with_errors(error): ) ) + def allowed_file(filename, allowed_extensions): """ Check for whether a filename is in the ALLOWED_EXTENSIONS