diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2018-12-17 20:15:41 +0100 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2018-12-17 20:15:41 +0100 |
| commit | 1c82f7ec6a603978322e16470547731e92e947c6 (patch) | |
| tree | f7dd44bc94b79013636e5d20edb2b85090e129ec | |
| parent | 7eb2b4509802388f2fe980a3477dad006cf81016 (diff) | |
adding verbiage and timing
| -rw-r--r-- | client/faceSearch/faceSearch.result.js | 52 | ||||
| -rw-r--r-- | megapixels/app/models/sql_factory.py | 2 | ||||
| -rw-r--r-- | megapixels/app/server/api.py | 62 | ||||
| -rw-r--r-- | megapixels/app/site/parser.py | 2 | ||||
| -rw-r--r-- | site/assets/css/applets.css | 5 | ||||
| -rw-r--r-- | site/public/test/index.html | 16 | ||||
| -rw-r--r-- | site/public/test/style/index.html | 10 |
7 files changed, 104 insertions, 45 deletions
diff --git a/client/faceSearch/faceSearch.result.js b/client/faceSearch/faceSearch.result.js index 1882def0..bc7831d9 100644 --- a/client/faceSearch/faceSearch.result.js +++ b/client/faceSearch/faceSearch.result.js @@ -4,31 +4,59 @@ import { connect } from 'react-redux' import { courtesyS } from '../util' import * as actions from './faceSearch.actions' +import { Loader } from '../common' 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!", + bbox: ( + <div> + <h2>No face found</h2> + {"Sorry, we didn't detect a face in that image. "} + {"Please choose an image where the face is large and clear."} + </div> + ), + nomatch: ( + <div> + <h2>{"You're clear"}</h2> + {"No images in this dataset match your face. We show only matches above 70% probability."} + </div> + ), + error: ( + <div> + <h2>{"No matches found"}</h2> + {"Sorry, an error occured."} + </div> + ), } class FaceSearchResult extends Component { render() { const { dataset } = this.props.payload - const { distances, results, error } = this.props.result + const { query, distances, results, loading, error } = this.props.result + if (loading) { + return ( + <div className='result'> + <div> + <Loader /><br /> + <h2>Searching...</h2> + </div> + </div> + ) + } if (error) { - let errorMessage = errors[error.message] || errors.default + console.log(error) + let errorMessage = errors[error] || errors.error return ( <div className='result'>{errorMessage}</div> ) } if (!results) { return ( - <div className='result'></div> + <div className='result'>{errors.nomatch}</div> ) } if (!this.props.result.results.length) { return ( - <div className='result'>No results</div> + <div className='result'>{errors.nomatch}</div> ) } const els = results.map((result, i) => { @@ -48,7 +76,15 @@ class FaceSearchResult extends Component { return ( <div className='result'> - <div class='results'> + <div className="about"> + <h2>Did we find you?</h2> + {'These faces matched images in the '} + <b><tt>{dataset}</tt></b> + {' dataset with over 70% probability.'} + <br /> + <small>Query took {query.timing.toFixed(2)} seconds</small> + </div> + <div className='results'> {els} </div> </div> diff --git a/megapixels/app/models/sql_factory.py b/megapixels/app/models/sql_factory.py index 9a44941b..02b722df 100644 --- a/megapixels/app/models/sql_factory.py +++ b/megapixels/app/models/sql_factory.py @@ -98,7 +98,7 @@ class SqlDataset: return None session = Session() # for obj in session.query(table).filter_by(id=id): - print(table) + # print(table) obj = session.query(table).filter(table.id == id).first() session.close() return obj.toJSON() diff --git a/megapixels/app/server/api.py b/megapixels/app/server/api.py index bc60118c..b3447eb1 100644 --- a/megapixels/app/server/api.py +++ b/megapixels/app/server/api.py @@ -15,24 +15,32 @@ from app.utils.im_utils import pil2np sanitize_re = re.compile('[\W]+') valid_exts = ['.gif', '.jpg', '.jpeg', '.png'] +LIMIT = 9 +THRESHOLD = 0.3 + api = Blueprint('api', __name__) faiss_datasets = load_faiss_databases() @api.route('/') def index(): + """List the datasets and their fields""" return jsonify({ 'datasets': list_datasets() }) + @api.route('/dataset/<name>') def show(name): + """Show the data that a dataset will return""" dataset = get_dataset(name) if dataset: return jsonify(dataset.describe()) else: return jsonify({ 'status': 404 }) + @api.route('/dataset/<name>/face/', methods=['POST']) def upload(name): + """Query an image against FAISS and return the matching identities""" start = time.time() dataset = get_dataset(name) if name not in faiss_datasets: @@ -52,31 +60,39 @@ def upload(name): 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_np, largest=True) - if not len(bboxes): + if not bboxes or not len(bboxes): return jsonify({ 'error': 'bbox' }) bbox = bboxes[0] + if not bbox: + return jsonify({ + 'error': 'bbox' + }) + dim = im_np.shape[:2][::-1] bbox = bbox.to_dim(dim) # convert back to real dimensions + print("got bbox") + if not bbox: + return jsonify({ + 'error': 'bbox' + }) - # face recognition/vector + # extract 128-D 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, 10) + # query FAISS + distances, indexes = faiss_dataset.search(query, LIMIT) - if len(indexes) == 0: + if len(indexes) == 0 or len(indexes[0]) == 0: return jsonify({ 'error': 'nomatch' }) @@ -85,36 +101,32 @@ def upload(name): distances = distances[0] indexes = indexes[0] - if len(indexes) == 0: - return jsonify({ - 'error': 'nomatch' - }) - - lookup = {} - ids = [i+1 for i in indexes] + dists = [] + ids = [] for _d, _i in zip(distances, indexes): - lookup[_i+1] = _d + if _d <= THRESHOLD: + dists.append(round(float(_d), 2)) + ids.append(_i+1) - print(distances) - print(indexes) + results = [ dataset.get_identity(_i) for _i in ids ] - # with the result we have an ID - # query the sql dataset for the UUID etc here + print(distances) + print(ids) query = { - 'timing': time.time() - start, + 'timing': round(time.time() - start, 3), } - results = [ dataset.get_identity(id) for id in ids ] - print(results) return jsonify({ + 'query': query, 'results': results, - 'distances': distances.tolist(), - 'indexes': indexes.tolist(), + 'distances': dists, }) + @api.route('/dataset/<name>/name', methods=['GET']) def name_lookup(dataset): + """Find a name in the dataset""" start = time.time() dataset = get_dataset(name) diff --git a/megapixels/app/site/parser.py b/megapixels/app/site/parser.py index ecfae0cb..b3d3a8c2 100644 --- a/megapixels/app/site/parser.py +++ b/megapixels/app/site/parser.py @@ -64,7 +64,7 @@ def format_applet(section, s3_path): else: command = payload[0] opt = None - if command == 'python': + if command == 'python' or command == 'javascript' or command == 'code': return format_section([ section ], s3_path) applet['command'] = command diff --git a/site/assets/css/applets.css b/site/assets/css/applets.css index ecba518c..edd5b709 100644 --- a/site/assets/css/applets.css +++ b/site/assets/css/applets.css @@ -18,6 +18,8 @@ } .results { + margin-top: 10px; + padding-bottom: 10px; display: flex; flex-direction: row; flex-wrap: wrap; @@ -27,6 +29,9 @@ margin-left: 20px; margin-bottom: 40px; font-size: 8pt; + background: #000; + padding: 5px; + font-weight: 500; } .results > div img { margin-bottom: 4px; diff --git a/site/public/test/index.html b/site/public/test/index.html index b4d16036..41f8eda5 100644 --- a/site/public/test/index.html +++ b/site/public/test/index.html @@ -30,14 +30,14 @@ <section><h1>Megapixels UI Tests</h1> <ul> -<li><a href="/test/style">Style Guide</a></li> -<li><a href="/test/csv">CSV</a></li> -<li><a href="/test/datasets/">Dataset list</a></li> -<li><a href="/test/citations/">Citation list</a></li> -<li><a href="/test/map/">Citation map</a></li> -<li><a href="/test/face_search/">Face search</a></li> -<li><a href="/test/name_search/">Name search</a></li> -<li><a href="/test/gallery/">Modal image gallery</a></li> +<li><a href="/test/style/index.html">Style Guide</a></li> +<li><a href="/test/csv/index.html">CSV</a></li> +<li><a href="/test/datasets/index.html">Dataset list</a></li> +<li><a href="/test/citations/index.html">Citation list</a></li> +<li><a href="/test/map/index.html">Citation map</a></li> +<li><a href="/test/face_search/index.html">Face search</a></li> +<li><a href="/test/name_search/index.html">Name search</a></li> +<li><a href="/test/gallery/index.html">Modal image gallery</a></li> </ul> </section> diff --git a/site/public/test/style/index.html b/site/public/test/style/index.html index 6d99a236..ab13a589 100644 --- a/site/public/test/style/index.html +++ b/site/public/test/style/index.html @@ -54,10 +54,16 @@ <div class='image'><img src='https://nyc3.digitaloceanspaces.com/megapixels/v1/site/test/assets/man.jpg' alt='Person 3. Let me tell you about Person 3. This person has a very long description with text which wraps like crazy'><div class='caption'>Person 3. Let me tell you about Person 3. This person has a very long description with text which wraps like crazy</div></div></section><section><blockquote><p>est, qui dolorem ipsum, quia dolor sit amet consectetur adipisci[ng] velit, sed quia non-numquam [do] eius modi tempora inci[di]dunt, ut labore et dolore magnam aliquam quaerat voluptatem.</p> </blockquote> </section><section class='wide'><div class='image'><img src='https://nyc3.digitaloceanspaces.com/megapixels/v1/site/test/assets/wide-test.jpg' alt='ThisĀ image is extremely wide and the text beneath it will wrap but thats fine because it can also contain <a href="https://example.com/">hyperlinks</a>! Yes, you read that right—hyperlinks! Lorem ipsum dolor sit amet ad volotesque sic hoc ad nauseam'><div class='caption'>ThisĀ image is extremely wide and the text beneath it will wrap but that's fine because it can also contain <a href="https://example.com/">hyperlinks</a>! Yes, you read that right—hyperlinks! Lorem ipsum dolor sit amet ad volotesque sic hoc ad nauseam</div></div></section><section><p>Inline <code>code</code> has <code>back-ticks around</code> it.</p> -</section><section class='applet_container'><div class='applet' data-payload='{"command": "javascript", "fields": ["var s = \"JavaScript syntax highlighting\";", "alert(s);"]}'></div></section><section><pre><code class="lang-python">s = "Python syntax highlighting" +</section><section><pre><code class="lang-javascript">var s = "JavaScript syntax highlighting"; +alert(s); +</code></pre> +</section><section><pre><code class="lang-python">s = "Python syntax highlighting" print(s) </code></pre> -</section><section class='applet_container'><div class='applet' data-payload='{"command": "No language indicated, so no syntax highlighting. ", "fields": ["But let's throw in a <b>tag</b>."]}'></div></section><section><p>Horizontal rule</p> +</section><section><pre><code class="lang-code">Generic code block. Note that code blocks that are not so marked will not appear. +But let's throw in a <b>tag</b>. +</code></pre> +</section><section><p>Horizontal rule</p> <hr> <p>Citations below here</p> <div class="footnotes"> |
