-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
harryjmoss
committed
Jun 18, 2019
0 parents
commit 3e7dfa7
Showing
4 changed files
with
132 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
FROM python:3.6-slim-stretch | ||
|
||
RUN apt update | ||
RUN apt install -y python3-dev gcc | ||
|
||
# Install pytorch and fastai | ||
RUN pip install torch_nightly -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html | ||
RUN pip install fastai | ||
|
||
# Install starlette and uvicorn | ||
RUN pip install starlette uvicorn python-multipart aiohttp | ||
|
||
ADD imgapp.py imgapp.py | ||
ADD resnet50_EAP_version6.pkl resnet50_EAP_version6.pkl | ||
|
||
# Run it once to trigger resnet download | ||
RUN python imgapp.py | ||
|
||
EXPOSE 8008 | ||
|
||
# Start the server | ||
CMD ["python", "imgapp.py", "serve"] |
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,11 @@ | ||
# Image orientation detection | ||
*App inspired by the work of [Deepanshu Thakur](https://github.com/Deepanshu2017/AlligatorOrCrocodile) and [Simon Willison](https://github.com/simonw/cougar-or-not)* | ||
|
||
This webapp was created on behalf of the British Library [Endangered Archive Programme](https://eap.bl.uk/) to determine the orientation of images in their collection. The deep learning model is based on a 50-layer version of [resnet](https://arxiv.org/abs/1512.03385) and was trained on around 650 unique images from the EAP archives using the [fast.ai](https://docs.fast.ai/) library. Each image is duplicated into four representations, consisting of the application of 0, 90, 180 or 270 degrees of anti-clockwise rotation. | ||
|
||
The 99MB pre-trained model is included in this package and is found within `resnet50_EAP_version6.pkl` | ||
|
||
`imgapp.py` is a [Starlette](https://www.starlette.io/) API server which accepts image uploads or image URLs and runs them against the pre-calculated model. | ||
|
||
A `Dockerfile` is included for convenience. | ||
|
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,99 @@ | ||
from starlette.applications import Starlette | ||
from starlette.responses import JSONResponse, HTMLResponse, RedirectResponse | ||
from fastai import * | ||
from fastai.vision import * | ||
|
||
import torch | ||
from pathlib import Path | ||
from io import BytesIO | ||
import sys | ||
import uvicorn | ||
import aiohttp | ||
import asyncio | ||
|
||
|
||
async def get_bytes(url): | ||
async with aiohttp.ClientSession() as session: | ||
async with session.get(url) as response: | ||
return await response.read() | ||
|
||
|
||
app = Starlette() | ||
path = Path(".") | ||
preTrainedModel="resnet50_EAP_version6.pkl" | ||
classes = ["0","90","180","270"] | ||
|
||
|
||
|
||
|
||
|
||
data = ImageDataBunch.single_from_classes( | ||
path, | ||
classes, | ||
ds_tfms=get_transforms(), | ||
size=224, | ||
).normalize(imagenet_stats) | ||
|
||
learn=load_learner(path,preTrainedModel) | ||
|
||
@app.route("/upload", methods=["POST"]) | ||
async def upload(request): | ||
data = await request.form() | ||
bytes = await (data["file"].read()) | ||
return predict_image_from_bytes(bytes) | ||
|
||
|
||
@app.route("/classify-url", methods=["GET"]) | ||
async def classify_url(request): | ||
bytes = await get_bytes(request.query_params["url"]) | ||
return predict_image_from_bytes(bytes) | ||
|
||
|
||
def predict_image_from_bytes(bytes): | ||
""" | ||
Predict function called by either classify_url or by upload | ||
Returns True class as well as the scores | ||
Scores are *not* probabilities - use activation function (softmax, sigmoid, etc) | ||
to convert to probabilities! | ||
""" | ||
|
||
img = open_image(BytesIO(bytes)) | ||
class_, predictions, losses = learn.predict(img) | ||
return JSONResponse({ | ||
"class": class_, | ||
"scores": sorted( | ||
zip(learn.data.classes, map(float, losses)), | ||
key=lambda p: p[1], | ||
reverse=True | ||
) | ||
}) | ||
|
||
|
||
@app.route("/") | ||
def form(request): | ||
return HTMLResponse( | ||
""" | ||
<h3> Image orientation prediction app <h3> | ||
<h4> Trained on approximately 650 images, each with four representations<h4> | ||
<form action="/upload" method="post" enctype="multipart/form-data"> | ||
Select image to upload: | ||
<input type="file" name="file"> | ||
<input type="submit" value="Upload Image"> | ||
</form> | ||
Or submit a URL: | ||
<form action="/classify-url" method="get"> | ||
<input type="url" name="url"> | ||
<input type="submit" value="Fetch and analyze image"> | ||
</form> | ||
""") | ||
|
||
|
||
@app.route("/form") | ||
def redirect_to_homepage(request): | ||
return RedirectResponse("/") | ||
|
||
|
||
if __name__ == "__main__": | ||
if "serve" in sys.argv: | ||
uvicorn.run(app, host="0.0.0.0", port=8080) |
Binary file not shown.