Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Implement post upload endpoint #23

Open
wants to merge 23 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .DS_Store
Binary file not shown.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
.buildlog/
.history
.svn/
*.json


# IntelliJ related
*.iml
Expand Down Expand Up @@ -74,4 +76,5 @@

# Backend-related
# Don't check in virtual environment
/backend/env
/backend/env
env/
Empty file added backend/.gitignore
Empty file.
30 changes: 16 additions & 14 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import json
from flask import Request, make_response
from posts.posts import get_posts
from posts.posts import get_posts, add_post, upload
from posts.models import AddPostMetadata
from posts.exceptions import CreatePostException, GetPostException, UnknownPostIdException
from posts.intelligence import _get_imagga_data
from requests import Response

REQUEST_ARG_USER_ID = 'userId'
REQUEST_ARG_POST_ID = 'postId'
Expand Down Expand Up @@ -34,32 +36,32 @@ def posts(request: Request) -> Response:
'Content-Length': len(posts_json)
}
response = make_response((posts_json, status, headers))
except GetPostQueryException:
error_body = {
'errorCode': 'unknown',
'message': 'An unknown server error occured. Try again later.'
}
response = make_response((error_body, 500))
except UnknownPostIdException:
error_body = {
'errorCode': 'unknownPostId',
'message': 'The provided post does not exist.'
}
response = make_response((error_body, 404))
except GetPostException:
error_body = {
'errorCode': 'unknown',
'message': 'An unknown server error occured. Try again later.'
}
response = make_response((error_body, 500))
except:
response = _generate_server_error()
elif request.method === 'POST':
elif request.method == 'POST' and 'photo' in request.files:
try:
imageUrl = request.args.get(REQUEST_ARG_IMAGE_URL)
if imageUrl is None or imageUrl == ''
add_post_metadata = AddPostMetadata()
# TODO: Handle uploads
image = request.files['photo']
imgUrl = upload(image)
json_response = _get_imagga_data(imgUrl)
return json_response
except CreatePostException:
response = _generate_server_error()
else:
error_body = {
'errorCode': 'methodNotSupported',
'message': 'This HTTP method is support supported by this server.'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this. 😆

'message': 'This HTTP method is not supported by this server.'
}
response = make_response((error_body, 405))
return response
Expand All @@ -80,7 +82,7 @@ def auth(request: Request) -> Response:

"""
# TODO: Handle request
return Response('OK', status=200)
return make_response(('OK', 200))


def _generate_server_error() -> Response:
Expand Down
117 changes: 106 additions & 11 deletions backend/posts/intelligence.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,44 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf

import requests
import os
import base64
import urllib.parse
from typing import List
from models import PostMetadata
import json

API_BASE = 'https://api.imagga.com/v2'
import IPython.display as display
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import pathlib

API_ENDPOINT_TAGS = f'{API_BASE}/tags'
AUTOTUNE = tf.data.experimental.AUTOTUNE
IMG_WIDTH = 299
IMG_HEIGHT = 299

# TF Model Variables
# data_dir = pathlib.Path("C:/Users/Mr. Jeevs/.keras/datasets/flower_photos")
data_dir = pathlib.Path("C:/Users/Mr. Jeevs/.keras/datasets/train2017")
print(data_dir)

image_count = len(list(data_dir.glob('*.jpg')))
print(image_count)

'''
CLASS_NAMES = np.array([item.name for item in data_dir.glob('*') if item.name != "LICENSE.txt"])
print(CLASS_NAMES)

roses = list(data_dir.glob('roses/*'))
for image_path in roses[:3]:
display.display(Image.open(str(image_path)))
'''

# Imagga Variables
API_BASE = 'https://api.imagga.com/v2'
API_ENDPOINT_TAGS = f'{API_BASE}/tags'
TAG_LIMIT = 10

class PostPredictionData:
Expand All @@ -23,33 +54,97 @@ def __init__(self, confidence: float, tag: str):


def _get_imagga_data(image_url: str):
api_key = os.getenv('IMAGGA_API_KEY', None)
api_key = os.getenv('IMAGGA_API_KEY', None) # Get API Key From Environment Variable
if api_key is None:
raise Exception('API key not provided in environment variables!')
hashed_key = str(base64.b64encode(api_key.encode('utf-8')), 'utf-8')
data = {
'image': image_url,
'limit': TAG_LIMIT,
}
url_query = urllib.parse.quote(image_url) # URL Encode Given Image URL
'''
headers = {
'Authorization': f'Basic {hashed_key}'
}
response = requests.post(API_ENDPOINT_TAGS, data, headers)
'''
access_url = f'https://api.imagga.com/v2/tags?image_url={url_query}&limit={TAG_LIMIT}'

# TODO: Does Not Accept "hashed_key" as a parameter. Figure what "api_secret" is
response = requests.get(access_url, auth=(api_key, hashed_key))
response_json = response.json()
return response_json


def generate_image_metadata(image_url: str) -> List[PostPredictionData]:

"""Generate image metadata.

Returns:
A list of PostPredictionData containing results from hashtag generation.
"""
result_json = _get_imagga_data(imageUrl)
result_json = _get_imagga_data(image_url)
predictions = []
for json_object in result_json.result.tags:
tag = json_object['tag']['en']
confidence = json_object['confidence']
prediction = PostPredictionData(confidence, tag)
predictions.add(prediction)
predictions.append(prediction)
return predictions

def __get__tag__data():
with open("instances_train2017.json") as f:
train_data = json.load(f)

images = train_data["images"]
annotations = train_data["annotations"]

fileNames = []
catIDs = []

for i in images[:10]:
fileNames.append(i["file_name"])
for j in annotations:
if(i["id"] == j["image_id"]):
catIDs.append(j["category_id"])
break

print(fileNames)
print(catIDs)

list_ds = tf.data.Dataset.from_tensor_slices((fileNames, catIDs))

print("8=========================================================>")
for f in list_ds.take(5):
print(f)
print("8=========================================================>")

# Set `num_parallel_calls` so multiple images are loaded/processed in parallel.
labeled_ds = list_ds.map(process_img_label, num_parallel_calls=AUTOTUNE)
print(labeled_ds)
print("8=========================================================>")
thing = labeled_ds.take(1)
print(thing)

m = tf.keras.Sequential([
hub.KerasLayer("https://tfhub.dev/google/imagenet/inception_resnet_v2/classification/4", trainable=True, arguments=dict(batch_norm_momentum=0.99))
])

m.build([None, 299, 299, 3]) # Batch input shape.

def decode_img(img):
# convert the compressed string to a 3D uint8 tensor
img = tf.image.decode_jpeg(img, channels=3)
# Use `convert_image_dtype` to convert to floats in the [0,1] range.
img = tf.image.convert_image_dtype(img, tf.float32)
# resize the image to the desired size.
return tf.image.resize(img, [IMG_WIDTH, IMG_HEIGHT])

def process_img_label(fileName, label):
# load the raw data from the file as a string
img = tf.io.read_file(f'C:/Users/Mr. Jeevs/.keras/datasets/train2017/{fileName}')
img = decode_img(img)
print(img)
print(fileName)
print(label)
return img, label

def get_file_name(data):
return data["file_name"]

__get__tag__data()
3 changes: 1 addition & 2 deletions backend/posts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ class PostMetadata:
machine learning.
"""

def __init__(self, id: str, image_url: str, hashtags: list, caption: str):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI, we're going to have to rewrite a lot of the models with the new spec.

def __init__(self, id: str, image_url: str, hashtags: list):
self.id = id
self.image_url = image_url
self.hashtags = hashtags
self.caption = caption


class AddPostMetadata:
Expand Down
38 changes: 32 additions & 6 deletions backend/posts/posts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
"""Functions related to post management and database communcation."""

from typing import List
from firebase_admin import firestore, initialize_app

import firebase_admin
from firebase_admin import firestore, initialize_app, credentials, storage

from flask import Flask, render_template, request, flash, redirect, url_for
from werkzeug.utils import secure_filename

from google.cloud.exceptions import NotFound
from intelligence import generate_image_metadata
from models import AddPostMetadata, PostMetadata
Expand All @@ -10,14 +16,15 @@
COLLECTION_POSTS = u'/posts'

# Initialize Firebase with Application Default Credentials
initialize_app(name='hashpost')
initialize_app({'storageBucket': 'hashpost.appspot.com'})
bucket = storage.bucket()

db = firestore.client()

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}

def add_post(data: AddPostMetadata) -> str:
def add_post(data: AddPostMetadata) -> PostMetadata:
"""Upload a post's metadata to our database.

Our database is currently Google Cloud Firestore.

Args:
Expand All @@ -29,15 +36,29 @@ def add_post(data: AddPostMetadata) -> str:
Raises:
CreatePostException if an internal server error occurred.
"""

try:
post_data = generate_image_metadata(data.image_url)
(timestamp, doc) = db.collection(COLLECTION_POSTS) \
.add(post_data.to_json())
# TODO: Add timestamp to document
return doc.id
return post_data
except:
raise CreatePostException()

def upload(image):
"""Uploads a file to the bucket."""
if image.filename == '': # If File Name is Empty
flash('No selected file')
return redirect(request.url)
if image and allowed_file(image.filename): # If Image Exists And Is An Allowed Extension
fileName = secure_filename(image.filename) # Store File Name
path = "posts/" + fileName

blob = bucket.blob(path) # Set Destination
blob.upload_from_file(image) # Upload Image File

imgUrl = blob.path # Get Image URL Of BLOB
return imgUrl

def get_posts() -> List[PostMetadata]:
"""Queries all posts that match the given filters.
Expand Down Expand Up @@ -87,3 +108,8 @@ def delete_post(post_id: str):
raise UnknownPostIdException()
except:
raise DeletePostException()

# Helper Functions
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS