summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2018-12-17 20:15:41 +0100
committerJules Laplace <julescarbon@gmail.com>2018-12-17 20:15:41 +0100
commit1c82f7ec6a603978322e16470547731e92e947c6 (patch)
treef7dd44bc94b79013636e5d20edb2b85090e129ec
parent7eb2b4509802388f2fe980a3477dad006cf81016 (diff)
adding verbiage and timing
-rw-r--r--client/faceSearch/faceSearch.result.js52
-rw-r--r--megapixels/app/models/sql_factory.py2
-rw-r--r--megapixels/app/server/api.py62
-rw-r--r--megapixels/app/site/parser.py2
-rw-r--r--site/assets/css/applets.css5
-rw-r--r--site/public/test/index.html16
-rw-r--r--site/public/test/style/index.html10
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&mdash;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&mdash;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 = &quot;Python syntax highlighting&quot;
+</section><section><pre><code class="lang-javascript">var s = &quot;JavaScript syntax highlighting&quot;;
+alert(s);
+</code></pre>
+</section><section><pre><code class="lang-python">s = &quot;Python syntax highlighting&quot;
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&#39;s throw in a &lt;b&gt;tag&lt;/b&gt;.
+</code></pre>
+</section><section><p>Horizontal rule</p>
<hr>
<p>Citations below here</p>
<div class="footnotes">