-
Notifications
You must be signed in to change notification settings - Fork 30
Predict app #121
base: dev
Are you sure you want to change the base?
Predict app #121
Changes from 10 commits
0d6e168
a0bf719
7511cab
8e04609
75eeadd
fa5bd2b
191a6da
400c24d
74ed977
31201ac
251d239
4d0a3dd
2c7f764
8b6ecc1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import os | ||
|
||
from werkzeug import secure_filename | ||
|
||
|
||
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, upload_path=None): | ||
""" | ||
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 | ||
""" | ||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we make these methods top-level functions? I don't see much benefit in having them together in a class rather than just a module, it requires another line of code for callers, and it seems to me less idiomatic Python. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I made them top-level functions now. |
||
""" | ||
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)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import os | ||
from zipfile import ZipFile | ||
|
||
from .File import File | ||
from ..requests.Validator import ALLOWED_IMAGE_FILES | ||
from ..utils import allowed_file | ||
|
||
|
||
class ZipArchive: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need our own ZipArchive class -- the standard library has these capabilities https://docs.python.org/3/library/zipfile.html There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here the same as for the 'File' (now 'TemporaryFile'). It adds more functionality to the build in functions and can be reused. |
||
""" | ||
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, upload_folder=None): | ||
""" | ||
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 = File(file, upload_folder) | ||
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): | ||
""" | ||
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.setPath(os.path.join(path, member)) | ||
extractedFiles[member] = file | ||
return extractedFiles |
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.
We don't need our own file class -- Python has built-in ways to do these things.
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.
Especially for our use case, I wrote the File class, so it would remove the file upon destructing the object. This removes the need to manually handle this every time while handling a file.
I think maybe the naming here is the problem, so I renamed this class to be 'TemporaryFile'.
I would like to keep this class, since the functionality here is pretty awesome and makes the handling very simple. However it is your call, if you don't see the value.
What do you think?
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.
Could we use https://docs.python.org/3/library/tempfile.html?