From 76f36c6c5dafe754b066903b1ee8ecdd1b92dcab Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Sun, 16 Dec 2018 20:01:23 +0100 Subject: faceSearch client --- site/assets/css/applets.css | 45 +++++++++++++++++++++++++++++++++++++++++ site/assets/css/css.css | 16 +++++++++++---- site/assets/img/icon_camera.svg | 2 +- 3 files changed, 58 insertions(+), 5 deletions(-) (limited to 'site/assets') diff --git a/site/assets/css/applets.css b/site/assets/css/applets.css index fc71ecc4..2b531908 100644 --- a/site/assets/css/applets.css +++ b/site/assets/css/applets.css @@ -1,7 +1,52 @@ .applet { margin-bottom: 40px; + transition: opacity 0.2s cubic-bezier(0,0,1,1); + opacity: 0; } .applet.map { width: 100vw; height: 50vh; +} +.applet.loaded { + opacity: 1; +} + +.row { + display: flex; + flex-direction: row; + justify-content: flex-start; +} + +.query h2 { + margin-top: 0; padding-top: 0; +} +.cta { + padding-left: 20px; +} +.uploadContainer > div { + position: relative; + width: 300px; + height: 300px; + display: flex; + align-items: center; + justify-content: center; + background: #333; + border: 3px dashed #fff; + border-radius: 10px; + opacity: 0.3; + transition: opacity 0.2s cubic-bezier(0,0,1,1); +} +.uploadContainer.active, +.desktop .uploadContainer > div:hover { + opacity: 1; +} +.uploadContainer input { + position: absolute; + top: 0; left: 0; + width: 100%; height: 100%; + opacity: 0; + cursor: pointer; +} +.uploadContainer img { + max-width: 40px; } \ No newline at end of file diff --git a/site/assets/css/css.css b/site/assets/css/css.css index b6742cdc..4f2d7c6e 100644 --- a/site/assets/css/css.css +++ b/site/assets/css/css.css @@ -131,23 +131,31 @@ h1 { padding: 0; transition: color 0.2s cubic-bezier(0,0,1,1); } -h2, h3 { +h2 { + color: #ddd; + font-weight: 300; + font-size: 18pt; + margin: 20px 0 10px; + padding: 0; + transition: color 0.2s cubic-bezier(0,0,1,1); +} +h3 { margin: 0 0 20px 0; padding: 0; font-size: 11pt; font-weight: 500; transition: color 0.2s cubic-bezier(0,0,1,1); } -.content h2 a { +.content h3 a { color: #888; text-decoration: none; } -.desktop .content h2 a:hover { +.desktop .content h3 a:hover { color: #fff; text-decoration: underline; } -th, .gray, h2, h3 { +th, .gray, h3 { font-family: 'Roboto Mono', monospace; font-weight: 400; text-transform: uppercase; diff --git a/site/assets/img/icon_camera.svg b/site/assets/img/icon_camera.svg index b349072e..605fcfe1 100644 --- a/site/assets/img/icon_camera.svg +++ b/site/assets/img/icon_camera.svg @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file -- cgit v1.2.3-70-g09d2 From f968f0e4bcc6e195eb293c4e2b965e8879075d8b Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Sun, 16 Dec 2018 22:35:49 +0100 Subject: ok --- client/index.js | 8 ++++++-- client/tables.js | 2 +- site/assets/css/applets.css | 7 ++++++- webpack.config.prod.js | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) (limited to 'site/assets') diff --git a/client/index.js b/client/index.js index 90fc22e1..2beb5526 100644 --- a/client/index.js +++ b/client/index.js @@ -69,7 +69,8 @@ function runApplets() { dataset = null url = opt } else if (opt.indexOf('assets') === 0) { - url = 'https://nyc3.digitaloceanspaces.com/megapixels/v1' + window.location.pathname + opt + let pathname = window.location.pathname.replace('index.html', '') + url = 'https://nyc3.digitaloceanspaces.com/megapixels/v1' + pathname + opt dataset = null // console.log(url) } else { @@ -80,7 +81,10 @@ function runApplets() { if (!dataset && !url) { const path = window.location.pathname.split('/').filter(s => !!s) if (path.length) { - dataset = path[path.length - 1] + dataset = path.pop() + if (dataset === 'index.html') { + dataset = path.pop() + } // console.log('dataset from path:', dataset) } else { console.log('couldnt determine citations dataset') diff --git a/client/tables.js b/client/tables.js index b2b3d39c..2a2699f9 100644 --- a/client/tables.js +++ b/client/tables.js @@ -68,7 +68,7 @@ export default function append(el, payload) { table.setData(data) el.classList.add('loaded') } catch (e) { - console.error("error parsing json:", payload.url) + console.error("error making json:", payload.url) console.error(e) // console.log(text) } diff --git a/site/assets/css/applets.css b/site/assets/css/applets.css index 2b531908..54508f44 100644 --- a/site/assets/css/applets.css +++ b/site/assets/css/applets.css @@ -22,6 +22,11 @@ } .cta { padding-left: 20px; + font-size: 11pt; +} +.cta ol { + margin: 0; + padding: 0 0 20px 20px; } .uploadContainer > div { position: relative; @@ -49,4 +54,4 @@ } .uploadContainer img { max-width: 40px; -} \ No newline at end of file +} diff --git a/webpack.config.prod.js b/webpack.config.prod.js index 69bc63ec..b9d3f411 100644 --- a/webpack.config.prod.js +++ b/webpack.config.prod.js @@ -10,7 +10,7 @@ module.exports = { main: './client/index.js', }, output: { - path: path.resolve(__dirname, 'dist'), + path: path.resolve(__dirname, 'site/assets/js/dist'), filename: 'index.js', }, plugins: [ -- cgit v1.2.3-70-g09d2 From 4cf8581655c34698f8869bb364b6d436b881d17a Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 17 Dec 2018 00:35:19 +0100 Subject: returning results...! --- client/faceSearch/faceSearch.query.js | 26 +++++++------ megapixels/app/models/sql_factory.py | 64 ++++++++++++++++++++++++++++--- megapixels/app/processors/faiss.py | 58 ++++++++++++++++++++++++++++ megapixels/app/server/api.py | 53 ++++++++++++++++++++----- megapixels/app/server/json_encoder.py | 17 ++++++++ megapixels/commands/faiss/build_faiss.py | 36 +---------------- site/assets/img/ajax-loader.gif | Bin 1849 -> 0 bytes site/assets/img/loader.gif | Bin 0 -> 1849 bytes 8 files changed, 193 insertions(+), 61 deletions(-) create mode 100644 megapixels/app/processors/faiss.py create mode 100644 megapixels/app/server/json_encoder.py delete mode 100644 site/assets/img/ajax-loader.gif create mode 100644 site/assets/img/loader.gif (limited to 'site/assets') diff --git a/client/faceSearch/faceSearch.query.js b/client/faceSearch/faceSearch.query.js index 8302e437..425cb282 100644 --- a/client/faceSearch/faceSearch.query.js +++ b/client/faceSearch/faceSearch.query.js @@ -20,12 +20,12 @@ class FaceSearchQuery extends Component { if (file && file.type.match('image.*')) break } if (!file) return - var fr = new FileReader(); + const fr = new FileReader() fr.onload = () => { fr.onload = null this.setState({ image: fr.result }) } - fr.readAsDataURL(files[0]); + fr.readAsDataURL(files[0]) this.props.actions.upload(this.props.payload, file) } @@ -36,6 +36,7 @@ class FaceSearchQuery extends Component { if (image) { style.backgroundImage = 'url(' + image + ')' style.backgroundSize = 'cover' + style.opacity = 1 } return (
@@ -44,9 +45,8 @@ class FaceSearchQuery extends Component {
- : -
- + :
+ {image ? null : } }
-
+

Search This Dataset

Searching {13456} images

- Use facial recognition to reverse search into the LFW dataset and see if it contains your photos. + {'Use facial recognition to reverse search into the LFW dataset '} + {'and see if it contains your photos.'}

    -
  1. Upload a photo of yourself
  2. -
  3. Use a photo similar to examples below
  4. -
  5. Only matches over 85% will be displayed
  6. -
  7. Read more tips to improve search results
  8. -
  9. Your search data is never stored and immediately cleared once you leave this page.
  10. +
  11. Upload a photo of yourself
  12. +
  13. Use a photo similar to examples below
  14. +
  15. Only matches over 85% will be displayed
  16. +
  17. Read more tips to improve search results
  18. +
  19. {'Your search data is never stored and immediately cleared '} + {'once you leave this page.'}

Read more about privacy. diff --git a/megapixels/app/models/sql_factory.py b/megapixels/app/models/sql_factory.py index e35c3e15..0f7e73a0 100644 --- a/megapixels/app/models/sql_factory.py +++ b/megapixels/app/models/sql_factory.py @@ -19,6 +19,7 @@ connection_url = "mysql+mysqldb://{}:{}@{}/{}".format( datasets = {} loaded = False +Session = None def list_datasets(): return [dataset.describe() for dataset in datasets.values()] @@ -31,10 +32,11 @@ def get_table(name, table_name): return dataset.get_table(table_name) if dataset else None def load_sql_datasets(replace=False, base_model=None): - global datasets, loaded + global datasets, loaded, Session if loaded: return datasets - engine = create_engine(connection_url) if replace else None + engine = create_engine(connection_url) + Session = sessionmaker(bind=engine) for path in glob.iglob(os.path.join(cfg.DIR_FAISS_METADATA, "*")): dataset = load_sql_dataset(path, replace, engine, base_model) datasets[dataset.name] = dataset @@ -79,6 +81,27 @@ class SqlDataset: 'tables': list(self.tables.keys()), } + def get_identity(self, id): + table = self.get_table('identity_meta') + identity = table.query.filter(table.image_id >= id).order_by(table.image_id.asc()).first().toJSON() + print(identity) + return { + 'uuid': self.select('uuids', id), + 'identity': identity, + 'roi': self.select('roi', id), + 'pose': self.select('pose', id), + } + + def select(self, table, id): + table = self.get_table(table) + if not table: + return None + session = Session() + # for obj in session.query(table).filter_by(id=id): + print(table) + obj = session.query(table).filter(table.id == id).first() + return obj.toJSON() + def get_table(self, type): if type in self.tables: return self.tables[type] @@ -102,6 +125,11 @@ class SqlDataset: __tablename__ = self.name + "_uuid" id = Column(Integer, primary_key=True) uuid = Column(String(36), nullable=False) + def toJSON(self): + return { + 'id': self.id, + 'uuid': self.uuid, + } return UUID # ==> roi.csv <== @@ -118,6 +146,17 @@ class SqlDataset: w = Column(Float, nullable=False) x = Column(Float, nullable=False) y = Column(Float, nullable=False) + def toJSON(self): + return { + 'id': self.id, + 'image_index': self.image_index, + 'image_height': self.image_height, + 'image_width': self.image_width, + 'w': self.w, + 'h': self.h, + 'x': self.x, + 'y': self.y, + } return ROI # ==> identity.csv <== @@ -132,6 +171,15 @@ class SqlDataset: gender = Column(String(1), nullable=False) images = Column(Integer, nullable=False) image_id = Column(Integer, nullable=False) + def toJSON(self): + return { + 'id': self.id, + 'image_id': self.image_id, + 'fullname': self.fullname, + 'images': self.images, + 'gender': self.gender, + 'description': self.description, + } return Identity # ==> pose.csv <== @@ -145,8 +193,12 @@ class SqlDataset: pitch = Column(Float, nullable=False) roll = Column(Float, nullable=False) yaw = Column(Float, nullable=False) + def toJSON(self): + return { + 'id': self.id, + 'image_id': self.image_id, + 'pitch': self.pitch, + 'roll': self.roll, + 'yaw': self.yaw, + } return Pose - - -# Session = sessionmaker(bind=engine) -# session = Session() diff --git a/megapixels/app/processors/faiss.py b/megapixels/app/processors/faiss.py new file mode 100644 index 00000000..5156ad71 --- /dev/null +++ b/megapixels/app/processors/faiss.py @@ -0,0 +1,58 @@ +""" +Index all of the FAISS datasets +""" + +import os +import glob +import faiss +import time +import numpy as np + +from app.utils.file_utils import load_recipe, load_csv_safe +from app.settings import app_cfg as cfg + +class DefaultRecipe: + def __init__(self): + self.dim = 128 + self.factory_type = 'Flat' + +def build_all_faiss_databases(): + datasets = [] + for fn in glob.iglob(os.path.join(cfg.DIR_FAISS_METADATA, "*")): + name = os.path.basename(fn) + recipe_fn = os.path.join(cfg.DIR_FAISS_RECIPES, name + ".json") + if os.path.exists(recipe_fn): + build_faiss_database(name, load_recipe(recipe_fn)) + else: + build_faiss_database(name, DefaultRecipe()) + +def build_faiss_database(name, recipe): + vec_fn = os.path.join(cfg.DIR_FAISS_METADATA, name, "vecs.csv") + index_fn = os.path.join(cfg.DIR_FAISS_INDEXES, name + ".index") + + index = faiss.index_factory(recipe.dim, recipe.factory_type) + + keys, rows = load_csv_safe(vec_fn) + feats = np.array([ list(map(float, row[3].split(","))) for row in rows ]).astype('float32') + n, d = feats.shape + + print("{}: training {} x {} dim vectors".format(name, n, d)) + print(recipe.factory_type) + + add_start = time.time() + index.add(feats) + add_end = time.time() + add_time = add_end - add_start + print("{}: add time: {:.1f}s".format(name, add_time)) + + faiss.write_index(index, index_fn) + +def load_faiss_databases(): + faiss_datasets = {} + for fn in glob.iglob(os.path.join(cfg.DIR_FAISS_METADATA, "*")): + name = os.path.basename(fn) + index_fn = os.path.join(cfg.DIR_FAISS_INDEXES, name + ".index") + if os.path.exists(index_fn): + index = faiss.read_index(index_fn) + faiss_datasets[name] = index + return faiss_datasets diff --git a/megapixels/app/server/api.py b/megapixels/app/server/api.py index cf8241bd..36563910 100644 --- a/megapixels/app/server/api.py +++ b/megapixels/app/server/api.py @@ -2,18 +2,23 @@ import os import re import time import dlib +import numpy as np from flask import Blueprint, request, jsonify from PIL import Image # todo: try to remove PIL dependency from app.processors import face_recognition from app.processors import face_detector -from app.models.sql_factory import list_datasets, get_dataset, get_table +from app.processors.faiss import load_faiss_databases +from app.models.sql_factory import load_sql_datasets, list_datasets, get_dataset, get_table +from app.utils.im_utils import pil2np sanitize_re = re.compile('[\W]+') valid_exts = ['.gif', '.jpg', '.jpeg', '.png'] api = Blueprint('api', __name__) +faiss_datasets = load_faiss_databases() + @api.route('/') def index(): return jsonify({ 'datasets': list_datasets() }) @@ -26,10 +31,15 @@ def show(name): else: return jsonify({ 'status': 404 }) -@api.route('/dataset//face', methods=['POST']) +@api.route('/dataset//face/', methods=['POST']) def upload(name): start = time.time() dataset = get_dataset(name) + if name not in faiss_datasets: + return jsonify({ + 'error': 'invalid dataset' + }) + faiss_dataset = faiss_datasets[name] file = request.files['query_img'] fn = file.filename if fn.endswith('blob'): @@ -40,22 +50,46 @@ def upload(name): if ext.lower() not in valid_exts: return jsonify({ 'error': 'not an image' }) - img = Image.open(file.stream).convert('RGB') + im = Image.open(file.stream).convert('RGB') + im_np = pil2np(im) # Face detection detector = face_detector.DetectorDLIBHOG() # get detection as BBox object - bboxes = detector.detect(im, largest=True) + bboxes = detector.detect(im_np, largest=True) bbox = bboxes[0] - dim = im.shape[:2][::-1] + dim = im_np.shape[:2][::-1] bbox = bbox.to_dim(dim) # convert back to real dimensions # face recognition/vector recognition = face_recognition.RecognitionDLIB(gpu=-1) + vec = recognition.vec(im_np, bbox) + + # print(vec) + query = np.array([ vec ]).astype('float32') + + # query FAISS! + distances, indexes = faiss_dataset.search(query, 5) + + if len(indexes) == 0: + print("weird, no results!") + return [] + + # get the results for this single query... + distances = distances[0] + indexes = indexes[0] - # print(vec.shape) - # results = db.search(vec, limit=limit) + if len(indexes) == 0: + print("no results!") + return [] + + lookup = {} + for _d, _i in zip(distances, indexes): + lookup[_i+1] = _d + + print(distances) + print(indexes) # with the result we have an ID # query the sql dataset for the UUID etc here @@ -63,12 +97,13 @@ def upload(name): query = { 'timing': time.time() - start, } - results = [] + results = [ dataset.get_identity(index) for index in indexes ] print(results) return jsonify({ - 'query': query, 'results': results, + # 'distances': distances.tolist(), + # 'indexes': indexes.tolist(), }) @api.route('/dataset//name', methods=['GET']) diff --git a/megapixels/app/server/json_encoder.py b/megapixels/app/server/json_encoder.py new file mode 100644 index 00000000..89af578a --- /dev/null +++ b/megapixels/app/server/json_encoder.py @@ -0,0 +1,17 @@ +from sqlalchemy.ext.declarative import DeclarativeMeta +from flask import json + +class AlchemyEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o.__class__, DeclarativeMeta): + data = {} + fields = o.__json__() if hasattr(o, '__json__') else dir(o) + for field in [f for f in fields if not f.startswith('_') and f not in ['metadata', 'query', 'query_class']]: + value = o.__getattribute__(field) + try: + json.dumps(value) + data[field] = value + except TypeError: + data[field] = None + return data + return json.JSONEncoder.default(self, o) diff --git a/megapixels/commands/faiss/build_faiss.py b/megapixels/commands/faiss/build_faiss.py index ec94c924..fc6b37ce 100644 --- a/megapixels/commands/faiss/build_faiss.py +++ b/megapixels/commands/faiss/build_faiss.py @@ -11,11 +11,7 @@ import numpy as np from app.utils.file_utils import load_recipe, load_csv_safe from app.settings import app_cfg as cfg - -class DefaultRecipe: - def __init__(self): - self.dim = 128 - self.factory_type = 'Flat' +from app.processors.faiss import build_all_faiss_databases @click.command() @click.pass_context @@ -25,32 +21,4 @@ def cli(ctx): - uses the recipe above by default - however you can override this by adding a new recipe in faiss/recipes/{name}.json """ - datasets = [] - for fn in glob.iglob(os.path.join(cfg.DIR_FAISS_METADATA, "*")): - name = os.path.basename(fn) - recipe_fn = os.path.join(cfg.DIR_FAISS_RECIPES, name + ".json") - if os.path.exists(recipe_fn): - build_faiss(name, load_recipe(recipe_fn)) - else: - build_faiss(name, DefaultRecipe()) - -def build_faiss(name, recipe): - vec_fn = os.path.join(cfg.DIR_FAISS_METADATA, name, "vecs.csv") - index_fn = os.path.join(cfg.DIR_FAISS_INDEXES, name + ".index") - - index = faiss.index_factory(recipe.dim, recipe.factory_type) - - keys, rows = load_csv_safe(vec_fn) - feats = np.array([ list(map(float, row[3].split(","))) for row in rows ]).astype('float32') - n, d = feats.shape - - print("{}: training {} x {} dim vectors".format(name, n, d)) - print(recipe.factory_type) - - add_start = time.time() - index.add(feats) - add_end = time.time() - add_time = add_end - add_start - print("{}: add time: {:.1f}s".format(name, add_time)) - - faiss.write_index(index, index_fn) + build_all_faiss_databases() diff --git a/site/assets/img/ajax-loader.gif b/site/assets/img/ajax-loader.gif deleted file mode 100644 index dc21df18..00000000 Binary files a/site/assets/img/ajax-loader.gif and /dev/null differ diff --git a/site/assets/img/loader.gif b/site/assets/img/loader.gif new file mode 100644 index 00000000..dc21df18 Binary files /dev/null and b/site/assets/img/loader.gif differ -- cgit v1.2.3-70-g09d2 From d7df4ee5b9e24a9cdf2bf4d1bc2e73e97352afdc Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 17 Dec 2018 01:02:40 +0100 Subject: searches execute --- client/actions.js | 6 +----- client/faceSearch/faceSearch.result.js | 35 ++++++++++++++++++++++++++++++---- megapixels/app/models/sql_factory.py | 5 +++-- megapixels/app/server/api.py | 9 +++++---- site/assets/css/applets.css | 17 +++++++++++++++++ 5 files changed, 57 insertions(+), 15 deletions(-) (limited to 'site/assets') diff --git a/client/actions.js b/client/actions.js index 37b4eb2e..bb011838 100644 --- a/client/actions.js +++ b/client/actions.js @@ -1,9 +1,5 @@ import * as faceSearch from './faceSearch/faceSearch.actions' -// import * as review from './review/review.actions' -// import * as metadata from './metadata/metadata.actions' export { - // search, - // review, - // metadata, + faceSearch } diff --git a/client/faceSearch/faceSearch.result.js b/client/faceSearch/faceSearch.result.js index 844a5a70..2b223a46 100644 --- a/client/faceSearch/faceSearch.result.js +++ b/client/faceSearch/faceSearch.result.js @@ -1,17 +1,44 @@ import React, { Component } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' +import { courtesyS } from '../util' import * as actions from './faceSearch.actions' class FaceSearchResult extends Component { - componentDidMount() { - } - render() { + const { dataset } = this.props.payload + const { distances, results } = this.props.result + if (!results) { + return ( +

+ ) + } + if (!this.props.result.results.length) { + return ( +
No results
+ ) + } + const els = results.map((result, i) => { + const distance = distances[i] + const { uuid } = result.uuid + const { fullname, gender, description, images } = result.identity + return ( +
+ + {fullname} {'('}{gender}{')'}
+ {description}
+ {courtesyS(images, 'image')}
+ {distance} +
+ ) + }) + return (
- Result here +
+ {els} +
) } diff --git a/megapixels/app/models/sql_factory.py b/megapixels/app/models/sql_factory.py index 0f7e73a0..9a44941b 100644 --- a/megapixels/app/models/sql_factory.py +++ b/megapixels/app/models/sql_factory.py @@ -83,8 +83,8 @@ class SqlDataset: def get_identity(self, id): table = self.get_table('identity_meta') - identity = table.query.filter(table.image_id >= id).order_by(table.image_id.asc()).first().toJSON() - print(identity) + # id += 1 + identity = table.query.filter(table.image_id <= id).order_by(table.image_id.desc()).first().toJSON() return { 'uuid': self.select('uuids', id), 'identity': identity, @@ -100,6 +100,7 @@ class SqlDataset: # for obj in session.query(table).filter_by(id=id): print(table) obj = session.query(table).filter(table.id == id).first() + session.close() return obj.toJSON() def get_table(self, type): diff --git a/megapixels/app/server/api.py b/megapixels/app/server/api.py index 36563910..2f78ecd3 100644 --- a/megapixels/app/server/api.py +++ b/megapixels/app/server/api.py @@ -70,7 +70,7 @@ def upload(name): query = np.array([ vec ]).astype('float32') # query FAISS! - distances, indexes = faiss_dataset.search(query, 5) + distances, indexes = faiss_dataset.search(query, 10) if len(indexes) == 0: print("weird, no results!") @@ -85,6 +85,7 @@ def upload(name): return [] lookup = {} + ids = [i+1 for i in indexes] for _d, _i in zip(distances, indexes): lookup[_i+1] = _d @@ -97,13 +98,13 @@ def upload(name): query = { 'timing': time.time() - start, } - results = [ dataset.get_identity(index) for index in indexes ] + results = [ dataset.get_identity(id) for id in ids ] print(results) return jsonify({ 'results': results, - # 'distances': distances.tolist(), - # 'indexes': indexes.tolist(), + 'distances': distances.tolist(), + 'indexes': indexes.tolist(), }) @api.route('/dataset//name', methods=['GET']) diff --git a/site/assets/css/applets.css b/site/assets/css/applets.css index 54508f44..a01703d5 100644 --- a/site/assets/css/applets.css +++ b/site/assets/css/applets.css @@ -17,6 +17,23 @@ justify-content: flex-start; } +.results { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} +.results > div { + width: 200px; + margin-left: 20px; + margin-bottom: 40px; + font-size: 8pt; +} +.results > div img { + margin-bottom: 4px; +} +.results > div:nth-child(3n+1) { + margin-left: 0; +} .query h2 { margin-top: 0; padding-top: 0; } -- cgit v1.2.3-70-g09d2 From a2d4ad0499e4ee4f3f528b2ed7bc162d61026d09 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 17 Dec 2018 01:13:32 +0100 Subject: build --- client/faceSearch/faceSearch.result.js | 14 +++++++++++++- megapixels/app/server/api.py | 14 ++++++++++---- site/assets/css/applets.css | 3 +++ 3 files changed, 26 insertions(+), 5 deletions(-) (limited to 'site/assets') diff --git a/client/faceSearch/faceSearch.result.js b/client/faceSearch/faceSearch.result.js index 2b223a46..1882def0 100644 --- a/client/faceSearch/faceSearch.result.js +++ b/client/faceSearch/faceSearch.result.js @@ -5,10 +5,22 @@ import { courtesyS } from '../util' import * as actions from './faceSearch.actions' +const errors = { + 'bbox': "Sorry, we couldn't find a face in that image. Please choose an image where the face is large and clear.", + 'nomatch': "We didn't find a match.", + 'default': "There was an error!", +} + class FaceSearchResult extends Component { render() { const { dataset } = this.props.payload - const { distances, results } = this.props.result + const { distances, results, error } = this.props.result + if (error) { + let errorMessage = errors[error.message] || errors.default + return ( +
{errorMessage}
+ ) + } if (!results) { return (
diff --git a/megapixels/app/server/api.py b/megapixels/app/server/api.py index 2f78ecd3..bc60118c 100644 --- a/megapixels/app/server/api.py +++ b/megapixels/app/server/api.py @@ -58,6 +58,10 @@ def upload(name): # get detection as BBox object bboxes = detector.detect(im_np, largest=True) + if not len(bboxes): + return jsonify({ + 'error': 'bbox' + }) bbox = bboxes[0] dim = im_np.shape[:2][::-1] bbox = bbox.to_dim(dim) # convert back to real dimensions @@ -73,16 +77,18 @@ def upload(name): distances, indexes = faiss_dataset.search(query, 10) if len(indexes) == 0: - print("weird, no results!") - return [] + return jsonify({ + 'error': 'nomatch' + }) # get the results for this single query... distances = distances[0] indexes = indexes[0] if len(indexes) == 0: - print("no results!") - return [] + return jsonify({ + 'error': 'nomatch' + }) lookup = {} ids = [i+1 for i in indexes] diff --git a/site/assets/css/applets.css b/site/assets/css/applets.css index a01703d5..ecba518c 100644 --- a/site/assets/css/applets.css +++ b/site/assets/css/applets.css @@ -30,6 +30,9 @@ } .results > div img { margin-bottom: 4px; + width: 200px; + height: 200px; + background: rgba(255,255,255,0.05); } .results > div:nth-child(3n+1) { margin-left: 0; -- cgit v1.2.3-70-g09d2