This repository has been archived by the owner on Apr 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 30
Predict app #121
Open
DigitalPhilosopher
wants to merge
14
commits into
uptake:dev
Choose a base branch
from
DigitalPhilosopher:predict-app
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Predict app #121
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
0d6e168
Add Validator for app
a0bf719
Use Validator
7511cab
Add Validator for zip files
8e04609
Use PredictZipRequestValidator
75eeadd
Add File class
fa5bd2b
Add ZipArchive
191a6da
Add docstrings
400c24d
Fix imports
74ed977
Refactor for travis ci imports and docstrings
31201ac
Merge branch 'dev' into predict-app
251d239
Remove object oriented programming within validation
4d0a3dd
Remove object oriented programming from prediction
2c7f764
Move routes to folder
8b6ecc1
Black code formating
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import os | ||
|
||
from werkzeug import secure_filename | ||
|
||
|
||
UPLOAD_FOLDER = "/tmp/" | ||
|
||
|
||
class TemporaryFile: | ||
""" | ||
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, upload_path=UPLOAD_FOLDER): | ||
""" | ||
Constructor of File | ||
|
||
Save the file on the server if a file is given. | ||
|
||
Parameters: | ||
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) | ||
|
||
def __del__(self): | ||
""" | ||
Destructor of File | ||
|
||
Remove the file from the server. | ||
""" | ||
os.remove(self.path) | ||
|
||
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 = self.name | ||
if upload_path: | ||
self.path = os.path.join(upload_path, self.path) | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import os | ||
from zipfile import ZipFile | ||
|
||
from .TemporaryFile import TemporaryFile, UPLOAD_FOLDER | ||
from ..validation.validation import allowed_file, ALLOWED_IMAGE_FILES | ||
|
||
|
||
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 | ||
upload_folder: The folder to save the zip file | ||
""" | ||
self.file = TemporaryFile(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=UPLOAD_FOLDER, 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 = TemporaryFile() | ||
file.setPath(os.path.join(path, member)) | ||
extractedFiles[member] = file | ||
return extractedFiles |
Empty file.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
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)) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from flask import Blueprint, jsonify, request | ||
|
||
from ..filesystem.TemporaryFile import TemporaryFile | ||
from ..prediction.prediction import predict | ||
from ..validation.predict import validate_predict_request | ||
|
||
|
||
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 = TemporaryFile(request.files["file"]) | ||
|
||
# Return ziped probabilities | ||
return jsonify(predict(file)) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've never used
Blueprint
objects, but I'm intrigued. Can you point me to a resource that explains how they work?